├── .bumpversion.cfg ├── .github └── workflows │ └── main.yml ├── .gitignore ├── .travis.yml ├── Dockerfile ├── HISTORY.md ├── LICENSE ├── README.md ├── docker-compose.yaml ├── notebooks └── valley-vs-schema-vs-schematics.ipynb ├── poetry.lock ├── pyproject.toml ├── requirements.txt └── valley ├── __init__.py ├── declarative.py ├── exceptions.py ├── properties.py ├── schema.py ├── tests ├── __init__.py ├── examples │ ├── __init__.py │ └── example_schemas.py ├── test_declarative.py ├── test_schema.py ├── test_utils.py └── test_validators.py ├── utils ├── __init__.py ├── imports.py └── json_utils.py └── validators.py /.bumpversion.cfg: -------------------------------------------------------------------------------- 1 | [bumpversion] 2 | current_version = 1.4.1 3 | commit = False 4 | tag = False 5 | 6 | [bumpversion:file:setup.py] 7 | 8 | -------------------------------------------------------------------------------- /.github/workflows/main.yml: -------------------------------------------------------------------------------- 1 | # This is a basic workflow to help you get started with Actions 2 | 3 | name: Unittests 4 | 5 | # Controls when the action will run. 6 | on: 7 | # Triggers the workflow on push or pull request events but only for the master branch 8 | push: 9 | branches: [ master, develop ] 10 | pull_request: 11 | branches: [ master, develop ] 12 | 13 | # Allows you to run this workflow manually from the Actions tab 14 | workflow_dispatch: 15 | 16 | # A workflow run is made up of one or more jobs that can run sequentially or in parallel 17 | jobs: 18 | 19 | # This workflow contains a single job called "build" 20 | build: 21 | environment: Master 22 | # The type of runner that the job will run on 23 | runs-on: ubuntu-latest 24 | 25 | # Steps represent a sequence of tasks that will be executed as part of the job 26 | steps: 27 | # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it 28 | - uses: actions/checkout@v2 29 | - run: touch .env 30 | - run: docker-compose pull 31 | 32 | # In this step, this action saves a list of existing images, 33 | # the cache is created without them in the post run. 34 | # It also restores the cache if it exists. 35 | - uses: satackey/action-docker-layer-caching@v0.0.11 36 | # Ignore the failure of a step and avoid terminating the job. 37 | continue-on-error: true 38 | 39 | - run: docker-compose build 40 | 41 | # Runs a single command using the runners shell 42 | - name: Run Unit Tests 43 | run: docker-compose run web poetry run python -m unittest 44 | - name: Build and publish to pypi 45 | if: github.ref == 'refs/heads/master' 46 | uses: JRubics/poetry-publish@v1.13 47 | with: 48 | pypi_token: ${{ secrets.PYPI_TOKEN }} 49 | ignore_dev_requirements: "yes" 50 | - name: Remove .env file 51 | run: rm .env -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | valley.ipynb 6 | # C extensions 7 | *.so 8 | *.idea 9 | # Distribution / packaging 10 | *.ipynb 11 | .Python 12 | env/ 13 | build/ 14 | develop-eggs/ 15 | dist/ 16 | downloads/ 17 | eggs/ 18 | .eggs/ 19 | lib/ 20 | lib64/ 21 | parts/ 22 | sdist/ 23 | var/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | #*.ipynb 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *,cover 47 | .hypothesis/ 48 | 49 | # Translations 50 | *.mo 51 | *.pot 52 | 53 | # Django stuff: 54 | *.log 55 | local_settings.py 56 | 57 | # Flask stuff: 58 | instance/ 59 | .webassets-cache 60 | 61 | # Scrapy stuff: 62 | .scrapy 63 | 64 | # Sphinx documentation 65 | docs/_build/ 66 | 67 | # PyBuilder 68 | target/ 69 | 70 | # IPython Notebook 71 | .ipynb_checkpoints 72 | 73 | # pyenv 74 | .python-version 75 | 76 | # celery beat schedule file 77 | celerybeat-schedule 78 | 79 | # dotenv 80 | .env 81 | 82 | # virtualenv 83 | venv/ 84 | ENV/ 85 | 86 | # Spyder project settings 87 | .spyderproject 88 | 89 | # Rope project settings 90 | .ropeproject 91 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: python 2 | python: 3 | 4 | - "3.6" 5 | 6 | # command to install dependencies 7 | install: "pip install nose coverage" 8 | install: "pip install -r requirements.txt" 9 | # command to run tests 10 | script: nosetests 11 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM capless/capless-docker:jupyter 2 | COPY . /code 3 | RUN pip install --upgrade poetry 4 | RUN poetry install 5 | -------------------------------------------------------------------------------- /HISTORY.md: -------------------------------------------------------------------------------- 1 | # History 2 | 3 | ## 1.0.0 4 | 5 | - Initial Release 6 | 7 | ## 1.0.1 8 | 9 | - Fixed DateTimeProperty and DateTimeVariableMixin 10 | 11 | ## 1.1.0 12 | 13 | - Added OrderedDeclaredVars class 14 | - Added more tests 15 | - Fixed create_error_dict flag on the BaseSchema class 16 | 17 | ## 1.1.1 18 | 19 | - Added choices to BaseProperty kwargs 20 | - Added ChoiceValidator 21 | - Changed the name of create_error_dict and is_valid to _is_valid and _create_error_dict 22 | - Fixed _create_error_dict logic in validate 23 | 24 | ## 1.2.0 25 | 26 | - Added ListProperty 27 | - Added DictProperty 28 | 29 | ## 1.2.1 30 | 31 | - Fixed bugs in ListMixin and DictMixin 32 | 33 | ## 1.4.0 34 | 35 | - Added ValleyEncoder and ValleyDecoder 36 | - Added ForeignProperty and ForeignListProperty 37 | 38 | ## 1.5.0 39 | 40 | - Added MuliProperty 41 | - Fixed validators argument in BaseProperty 42 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | {one line to give the program's name and a brief idea of what it does.} 635 | Copyright (C) {year} {name of author} 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | {project} Copyright (C) {year} {fullname} 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . 675 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ![alt text](https://s3.amazonaws.com/capless/images/valley-small.png "Valley - Extensible Schema Validations and Declarative Syntax Helpers") 2 | 3 | # Valley 4 | 5 | Python extensible schema validations and declarative syntax helpers. 6 | 7 | [![Unittests](https://github.com/capless/valley/actions/workflows/main.yml/badge.svg?branch=master)](https://github.com/capless/valley/actions/workflows/main.yml) 8 | 9 | ## Installation 10 | 11 | ```shell 12 | pip install valley 13 | ``` 14 | 15 | ## Getting Started 16 | 17 | ```python 18 | import valley as v 19 | 20 | 21 | class Animal(v.Schema): 22 | name = v.StringProperty(required=True) 23 | species = v.StringProperty(required=True) 24 | color = v.StringProperty(required=True) 25 | meal_type = v.StringProperty() 26 | age = v.IntegerProperty(required=True) 27 | 28 | frog = Animal(name='Kermit',species='frog',color='green',meal='carnivore',age=1) 29 | frog.validate() 30 | ``` 31 | 32 | -------------------------------------------------------------------------------- /docker-compose.yaml: -------------------------------------------------------------------------------- 1 | version: '3.7' 2 | 3 | services: 4 | web: 5 | restart: always 6 | build: 7 | dockerfile: Dockerfile 8 | context: . 9 | expose: 10 | - "8014" 11 | ports: 12 | - 8014:8888 13 | volumes: 14 | - ./valley/:/code/valley 15 | env_file: .env 16 | working_dir: /code/ 17 | command: /root/.cache/pypoetry/virtualenvs/valley-MATOk_fk-py3.9/bin/jupyter notebook --port=8888 --ip=0.0.0.0 --allow-root 18 | -------------------------------------------------------------------------------- /notebooks/valley-vs-schema-vs-schematics.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 2, 6 | "metadata": { 7 | "collapsed": false, 8 | "deletable": true, 9 | "editable": true 10 | }, 11 | "outputs": [ 12 | { 13 | "ename": "ValueError", 14 | "evalue": "Attempted relative import in non-package", 15 | "output_type": "error", 16 | "traceback": [ 17 | "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", 18 | "\u001b[0;31mValueError\u001b[0m Traceback (most recent call last)", 19 | "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m()\u001b[0m\n\u001b[0;32m----> 1\u001b[0;31m \u001b[0;32mfrom\u001b[0m \u001b[0;34m.\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mcontrib\u001b[0m \u001b[0;32mimport\u001b[0m \u001b[0mSchema\u001b[0m \u001b[0;32mas\u001b[0m \u001b[0mVSchema\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 2\u001b[0m \u001b[0;32mfrom\u001b[0m \u001b[0mvalley\u001b[0m \u001b[0;32mimport\u001b[0m \u001b[0mproperties\u001b[0m \u001b[0;32mas\u001b[0m \u001b[0mvp\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 3\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 4\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 5\u001b[0m \u001b[0;32mclass\u001b[0m \u001b[0mAnimalValley\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mVSchema\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", 20 | "\u001b[0;31mValueError\u001b[0m: Attempted relative import in non-package" 21 | ] 22 | } 23 | ], 24 | "source": [ 25 | "from valley.contrib import Schema as VSchema\n", 26 | "from valley import properties as vp\n", 27 | "\n", 28 | "\n", 29 | "class AnimalValley(VSchema):\n", 30 | " name = vp.CharProperty(required=True)\n", 31 | " species = vp.CharProperty(required=True)\n", 32 | " color = vp.CharProperty()\n", 33 | " age = vp.IntegerProperty(required=True)\n", 34 | " \n", 35 | "valley_frog = AnimalValley(name='Kermit',species='frog',color='green',age=5)" 36 | ] 37 | }, 38 | { 39 | "cell_type": "code", 40 | "execution_count": 6, 41 | "metadata": { 42 | "collapsed": false, 43 | "deletable": true, 44 | "editable": true 45 | }, 46 | "outputs": [], 47 | "source": [ 48 | "from schematics.models import Model\n", 49 | "from schematics import types as stypes\n", 50 | "\n", 51 | "\n", 52 | "class AnimalSchematics(Model):\n", 53 | " name = stypes.StringType(required=True)\n", 54 | " species = stypes.StringType(required=True)\n", 55 | " color = stypes.StringType()\n", 56 | " age = stypes.IntType(required=True)\n", 57 | " \n", 58 | "schematics_frog = AnimalSchematics({'name':'Kermit','species':'frog','color':'green','age':5})" 59 | ] 60 | }, 61 | { 62 | "cell_type": "code", 63 | "execution_count": 8, 64 | "metadata": { 65 | "collapsed": true, 66 | "deletable": true, 67 | "editable": true 68 | }, 69 | "outputs": [], 70 | "source": [ 71 | "schematics_frog.validate()" 72 | ] 73 | }, 74 | { 75 | "cell_type": "code", 76 | "execution_count": 8, 77 | "metadata": { 78 | "collapsed": false, 79 | "deletable": true, 80 | "editable": true 81 | }, 82 | "outputs": [ 83 | { 84 | "data": { 85 | "text/plain": [ 86 | "[{'age': 5, 'color': 'green', 'name': 'Kermit', 'species': 'frog'}]" 87 | ] 88 | }, 89 | "execution_count": 8, 90 | "metadata": {}, 91 | "output_type": "execute_result" 92 | } 93 | ], 94 | "source": [ 95 | "from schema import Schema, Optional\n", 96 | "\n", 97 | "schema_frog = Schema([{\n", 98 | " 'name':str,\n", 99 | " 'species':str,\n", 100 | " 'color':str,\n", 101 | " Optional('age'):int\n", 102 | "}])\n", 103 | "\n", 104 | "p = {'name':'Kermit','species':'frog','color':'green','age':5}\n", 105 | "\n", 106 | "schema_frog.validate([p])" 107 | ] 108 | }, 109 | { 110 | "cell_type": "code", 111 | "execution_count": 1, 112 | "metadata": { 113 | "collapsed": false, 114 | "deletable": true, 115 | "editable": true 116 | }, 117 | "outputs": [ 118 | { 119 | "ename": "ImportError", 120 | "evalue": "No module named valley.contrib", 121 | "output_type": "error", 122 | "traceback": [ 123 | "\u001b[0;31m---------------------------------------------------------------------------\u001b[0m", 124 | "\u001b[0;31mImportError\u001b[0m Traceback (most recent call last)", 125 | "\u001b[0;32m\u001b[0m in \u001b[0;36m\u001b[0;34m()\u001b[0m\n\u001b[1;32m 14\u001b[0m \u001b[0mvalley_frog\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mvalidate\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 15\u001b[0m \"\"\"\n\u001b[0;32m---> 16\u001b[0;31m \u001b[0mtimeit\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mtimeit\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mstmt\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0ms\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0mnumber\u001b[0m\u001b[0;34m=\u001b[0m\u001b[0;36m10000\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m", 126 | "\u001b[0;32m/usr/lib/python2.7/timeit.py\u001b[0m in \u001b[0;36mtimeit\u001b[0;34m(stmt, setup, timer, number)\u001b[0m\n\u001b[1;32m 235\u001b[0m number=default_number):\n\u001b[1;32m 236\u001b[0m \u001b[0;34m\"\"\"Convenience function to create Timer object and call timeit method.\"\"\"\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 237\u001b[0;31m \u001b[0;32mreturn\u001b[0m \u001b[0mTimer\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mstmt\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0msetup\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mtimer\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mtimeit\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mnumber\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 238\u001b[0m \u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 239\u001b[0m def repeat(stmt=\"pass\", setup=\"pass\", timer=default_timer,\n", 127 | "\u001b[0;32m/usr/lib/python2.7/timeit.py\u001b[0m in \u001b[0;36mtimeit\u001b[0;34m(self, number)\u001b[0m\n\u001b[1;32m 200\u001b[0m \u001b[0mgc\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mdisable\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 201\u001b[0m \u001b[0;32mtry\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0;32m--> 202\u001b[0;31m \u001b[0mtiming\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0minner\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0mit\u001b[0m\u001b[0;34m,\u001b[0m \u001b[0mself\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mtimer\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[0m\u001b[1;32m 203\u001b[0m \u001b[0;32mfinally\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n\u001b[1;32m 204\u001b[0m \u001b[0;32mif\u001b[0m \u001b[0mgcold\u001b[0m\u001b[0;34m:\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", 128 | "\u001b[0;32m/usr/lib/python2.7/timeit.py\u001b[0m in \u001b[0;36minner\u001b[0;34m(_it, _timer)\u001b[0m\n", 129 | "\u001b[0;31mImportError\u001b[0m: No module named valley.contrib" 130 | ] 131 | } 132 | ], 133 | "source": [ 134 | "import timeit\n", 135 | "s = \"\"\"\n", 136 | "from valley.contrib import Schema as VSchema\n", 137 | "from valley import properties as vp\n", 138 | "\n", 139 | "\n", 140 | "class AnimalValley(VSchema):\n", 141 | " name = vp.CharProperty(required=True)\n", 142 | " species = vp.CharProperty(required=True)\n", 143 | " color = vp.CharProperty()\n", 144 | " age = vp.IntegerProperty(required=True)\n", 145 | " \n", 146 | "valley_frog = AnimalValley(name='Kermit',species='frog',color='green',age=5)\n", 147 | "valley_frog.validate()\n", 148 | "\"\"\"\n", 149 | "timeit.timeit(stmt=s,number=10000)" 150 | ] 151 | }, 152 | { 153 | "cell_type": "code", 154 | "execution_count": 11, 155 | "metadata": { 156 | "collapsed": false, 157 | "deletable": true, 158 | "editable": true 159 | }, 160 | "outputs": [ 161 | { 162 | "data": { 163 | "text/plain": [ 164 | "5.857553958892822" 165 | ] 166 | }, 167 | "execution_count": 11, 168 | "metadata": {}, 169 | "output_type": "execute_result" 170 | } 171 | ], 172 | "source": [ 173 | "v = \"\"\"\n", 174 | "from schematics.models import Model\n", 175 | "from schematics import types as stypes\n", 176 | "\n", 177 | "\n", 178 | "class AnimalSchematics(Model):\n", 179 | " name = stypes.StringType(required=True)\n", 180 | " species = stypes.StringType(required=True)\n", 181 | " color = stypes.StringType()\n", 182 | " age = stypes.IntType(required=True)\n", 183 | " \n", 184 | "schematics_frog = AnimalSchematics({'name':'Kermit','species':'frog','color':'green','age':5})\n", 185 | "schematics_frog.validate()\n", 186 | "\"\"\"\n", 187 | "timeit.timeit(stmt=v,number=10000)" 188 | ] 189 | }, 190 | { 191 | "cell_type": "code", 192 | "execution_count": 10, 193 | "metadata": { 194 | "collapsed": false, 195 | "deletable": true, 196 | "editable": true 197 | }, 198 | "outputs": [ 199 | { 200 | "data": { 201 | "text/plain": [ 202 | "1.4859449863433838" 203 | ] 204 | }, 205 | "execution_count": 10, 206 | "metadata": {}, 207 | "output_type": "execute_result" 208 | } 209 | ], 210 | "source": [ 211 | "y = \"\"\"\n", 212 | "from schema import Schema, Optional\n", 213 | "\n", 214 | "schema_frog = Schema([{\n", 215 | " 'name':str,\n", 216 | " 'species':str,\n", 217 | " 'color':str,\n", 218 | " Optional('age'):int\n", 219 | "}])\n", 220 | "\n", 221 | "p = {'name':'Kermit','species':'frog','color':'green','age':5}\n", 222 | "\n", 223 | "schema_frog.validate([p])\n", 224 | "\"\"\"\n", 225 | "\n", 226 | "timeit.timeit(stmt=y,number=10000)" 227 | ] 228 | }, 229 | { 230 | "cell_type": "code", 231 | "execution_count": null, 232 | "metadata": { 233 | "collapsed": true, 234 | "deletable": true, 235 | "editable": true 236 | }, 237 | "outputs": [], 238 | "source": [] 239 | } 240 | ], 241 | "metadata": { 242 | "kernelspec": { 243 | "display_name": "Python 2", 244 | "language": "python", 245 | "name": "python2" 246 | }, 247 | "language_info": { 248 | "codemirror_mode": { 249 | "name": "ipython", 250 | "version": 2 251 | }, 252 | "file_extension": ".py", 253 | "mimetype": "text/x-python", 254 | "name": "python", 255 | "nbconvert_exporter": "python", 256 | "pygments_lexer": "ipython2", 257 | "version": "2.7.12+" 258 | } 259 | }, 260 | "nbformat": 4, 261 | "nbformat_minor": 2 262 | } 263 | -------------------------------------------------------------------------------- /poetry.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand. 2 | 3 | [[package]] 4 | name = "anyio" 5 | version = "3.7.1" 6 | description = "High level compatibility layer for multiple asynchronous event loop implementations" 7 | optional = false 8 | python-versions = ">=3.7" 9 | files = [ 10 | {file = "anyio-3.7.1-py3-none-any.whl", hash = "sha256:91dee416e570e92c64041bd18b900d1d6fa78dff7048769ce5ac5ddad004fbb5"}, 11 | {file = "anyio-3.7.1.tar.gz", hash = "sha256:44a3c9aba0f5defa43261a8b3efb97891f2bd7d804e0e1f56419befa1adfc780"}, 12 | ] 13 | 14 | [package.dependencies] 15 | exceptiongroup = {version = "*", markers = "python_version < \"3.11\""} 16 | idna = ">=2.8" 17 | sniffio = ">=1.1" 18 | typing-extensions = {version = "*", markers = "python_version < \"3.8\""} 19 | 20 | [package.extras] 21 | doc = ["Sphinx", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme (>=1.2.2)", "sphinxcontrib-jquery"] 22 | test = ["anyio[trio]", "coverage[toml] (>=4.5)", "hypothesis (>=4.0)", "mock (>=4)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"] 23 | trio = ["trio (<0.22)"] 24 | 25 | [[package]] 26 | name = "appnope" 27 | version = "0.1.3" 28 | description = "Disable App Nap on macOS >= 10.9" 29 | optional = false 30 | python-versions = "*" 31 | files = [ 32 | {file = "appnope-0.1.3-py2.py3-none-any.whl", hash = "sha256:265a455292d0bd8a72453494fa24df5a11eb18373a60c7c0430889f22548605e"}, 33 | {file = "appnope-0.1.3.tar.gz", hash = "sha256:02bd91c4de869fbb1e1c50aafc4098827a7a54ab2f39d9dcba6c9547ed920e24"}, 34 | ] 35 | 36 | [[package]] 37 | name = "argon2-cffi" 38 | version = "23.1.0" 39 | description = "Argon2 for Python" 40 | optional = false 41 | python-versions = ">=3.7" 42 | files = [ 43 | {file = "argon2_cffi-23.1.0-py3-none-any.whl", hash = "sha256:c670642b78ba29641818ab2e68bd4e6a78ba53b7eff7b4c3815ae16abf91c7ea"}, 44 | {file = "argon2_cffi-23.1.0.tar.gz", hash = "sha256:879c3e79a2729ce768ebb7d36d4609e3a78a4ca2ec3a9f12286ca057e3d0db08"}, 45 | ] 46 | 47 | [package.dependencies] 48 | argon2-cffi-bindings = "*" 49 | typing-extensions = {version = "*", markers = "python_version < \"3.8\""} 50 | 51 | [package.extras] 52 | dev = ["argon2-cffi[tests,typing]", "tox (>4)"] 53 | docs = ["furo", "myst-parser", "sphinx", "sphinx-copybutton", "sphinx-notfound-page"] 54 | tests = ["hypothesis", "pytest"] 55 | typing = ["mypy"] 56 | 57 | [[package]] 58 | name = "argon2-cffi-bindings" 59 | version = "21.2.0" 60 | description = "Low-level CFFI bindings for Argon2" 61 | optional = false 62 | python-versions = ">=3.6" 63 | files = [ 64 | {file = "argon2-cffi-bindings-21.2.0.tar.gz", hash = "sha256:bb89ceffa6c791807d1305ceb77dbfacc5aa499891d2c55661c6459651fc39e3"}, 65 | {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:ccb949252cb2ab3a08c02024acb77cfb179492d5701c7cbdbfd776124d4d2367"}, 66 | {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9524464572e12979364b7d600abf96181d3541da11e23ddf565a32e70bd4dc0d"}, 67 | {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b746dba803a79238e925d9046a63aa26bf86ab2a2fe74ce6b009a1c3f5c8f2ae"}, 68 | {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:58ed19212051f49a523abb1dbe954337dc82d947fb6e5a0da60f7c8471a8476c"}, 69 | {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:bd46088725ef7f58b5a1ef7ca06647ebaf0eb4baff7d1d0d177c6cc8744abd86"}, 70 | {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-musllinux_1_1_i686.whl", hash = "sha256:8cd69c07dd875537a824deec19f978e0f2078fdda07fd5c42ac29668dda5f40f"}, 71 | {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:f1152ac548bd5b8bcecfb0b0371f082037e47128653df2e8ba6e914d384f3c3e"}, 72 | {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-win32.whl", hash = "sha256:603ca0aba86b1349b147cab91ae970c63118a0f30444d4bc80355937c950c082"}, 73 | {file = "argon2_cffi_bindings-21.2.0-cp36-abi3-win_amd64.whl", hash = "sha256:b2ef1c30440dbbcba7a5dc3e319408b59676e2e039e2ae11a8775ecf482b192f"}, 74 | {file = "argon2_cffi_bindings-21.2.0-cp38-abi3-macosx_10_9_universal2.whl", hash = "sha256:e415e3f62c8d124ee16018e491a009937f8cf7ebf5eb430ffc5de21b900dad93"}, 75 | {file = "argon2_cffi_bindings-21.2.0-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:3e385d1c39c520c08b53d63300c3ecc28622f076f4c2b0e6d7e796e9f6502194"}, 76 | {file = "argon2_cffi_bindings-21.2.0-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2c3e3cc67fdb7d82c4718f19b4e7a87123caf8a93fde7e23cf66ac0337d3cb3f"}, 77 | {file = "argon2_cffi_bindings-21.2.0-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6a22ad9800121b71099d0fb0a65323810a15f2e292f2ba450810a7316e128ee5"}, 78 | {file = "argon2_cffi_bindings-21.2.0-pp37-pypy37_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f9f8b450ed0547e3d473fdc8612083fd08dd2120d6ac8f73828df9b7d45bb351"}, 79 | {file = "argon2_cffi_bindings-21.2.0-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:93f9bf70084f97245ba10ee36575f0c3f1e7d7724d67d8e5b08e61787c320ed7"}, 80 | {file = "argon2_cffi_bindings-21.2.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:3b9ef65804859d335dc6b31582cad2c5166f0c3e7975f324d9ffaa34ee7e6583"}, 81 | {file = "argon2_cffi_bindings-21.2.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4966ef5848d820776f5f562a7d45fdd70c2f330c961d0d745b784034bd9f48d"}, 82 | {file = "argon2_cffi_bindings-21.2.0-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:20ef543a89dee4db46a1a6e206cd015360e5a75822f76df533845c3cbaf72670"}, 83 | {file = "argon2_cffi_bindings-21.2.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ed2937d286e2ad0cc79a7087d3c272832865f779430e0cc2b4f3718d3159b0cb"}, 84 | {file = "argon2_cffi_bindings-21.2.0-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:5e00316dabdaea0b2dd82d141cc66889ced0cdcbfa599e8b471cf22c620c329a"}, 85 | ] 86 | 87 | [package.dependencies] 88 | cffi = ">=1.0.1" 89 | 90 | [package.extras] 91 | dev = ["cogapp", "pre-commit", "pytest", "wheel"] 92 | tests = ["pytest"] 93 | 94 | [[package]] 95 | name = "attrs" 96 | version = "23.2.0" 97 | description = "Classes Without Boilerplate" 98 | optional = false 99 | python-versions = ">=3.7" 100 | files = [ 101 | {file = "attrs-23.2.0-py3-none-any.whl", hash = "sha256:99b87a485a5820b23b879f04c2305b44b951b502fd64be915879d77a7e8fc6f1"}, 102 | {file = "attrs-23.2.0.tar.gz", hash = "sha256:935dc3b529c262f6cf76e50877d35a4bd3c1de194fd41f47a2b7ae8f19971f30"}, 103 | ] 104 | 105 | [package.dependencies] 106 | importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} 107 | 108 | [package.extras] 109 | cov = ["attrs[tests]", "coverage[toml] (>=5.3)"] 110 | dev = ["attrs[tests]", "pre-commit"] 111 | docs = ["furo", "myst-parser", "sphinx", "sphinx-notfound-page", "sphinxcontrib-towncrier", "towncrier", "zope-interface"] 112 | tests = ["attrs[tests-no-zope]", "zope-interface"] 113 | tests-mypy = ["mypy (>=1.6)", "pytest-mypy-plugins"] 114 | tests-no-zope = ["attrs[tests-mypy]", "cloudpickle", "hypothesis", "pympler", "pytest (>=4.3.0)", "pytest-xdist[psutil]"] 115 | 116 | [[package]] 117 | name = "backcall" 118 | version = "0.2.0" 119 | description = "Specifications for callback functions passed in to an API" 120 | optional = false 121 | python-versions = "*" 122 | files = [ 123 | {file = "backcall-0.2.0-py2.py3-none-any.whl", hash = "sha256:fbbce6a29f263178a1f7915c1940bde0ec2b2a967566fe1c65c1dfb7422bd255"}, 124 | {file = "backcall-0.2.0.tar.gz", hash = "sha256:5cbdbf27be5e7cfadb448baf0aa95508f91f2bbc6c6437cd9cd06e2a4c215e1e"}, 125 | ] 126 | 127 | [[package]] 128 | name = "beautifulsoup4" 129 | version = "4.12.2" 130 | description = "Screen-scraping library" 131 | optional = false 132 | python-versions = ">=3.6.0" 133 | files = [ 134 | {file = "beautifulsoup4-4.12.2-py3-none-any.whl", hash = "sha256:bd2520ca0d9d7d12694a53d44ac482d181b4ec1888909b035a3dbf40d0f57d4a"}, 135 | {file = "beautifulsoup4-4.12.2.tar.gz", hash = "sha256:492bbc69dca35d12daac71c4db1bfff0c876c00ef4a2ffacce226d4638eb72da"}, 136 | ] 137 | 138 | [package.dependencies] 139 | soupsieve = ">1.2" 140 | 141 | [package.extras] 142 | html5lib = ["html5lib"] 143 | lxml = ["lxml"] 144 | 145 | [[package]] 146 | name = "bleach" 147 | version = "6.0.0" 148 | description = "An easy safelist-based HTML-sanitizing tool." 149 | optional = false 150 | python-versions = ">=3.7" 151 | files = [ 152 | {file = "bleach-6.0.0-py3-none-any.whl", hash = "sha256:33c16e3353dbd13028ab4799a0f89a83f113405c766e9c122df8a06f5b85b3f4"}, 153 | {file = "bleach-6.0.0.tar.gz", hash = "sha256:1a1a85c1595e07d8db14c5f09f09e6433502c51c595970edc090551f0db99414"}, 154 | ] 155 | 156 | [package.dependencies] 157 | six = ">=1.9.0" 158 | webencodings = "*" 159 | 160 | [package.extras] 161 | css = ["tinycss2 (>=1.1.0,<1.2)"] 162 | 163 | [[package]] 164 | name = "cffi" 165 | version = "1.15.1" 166 | description = "Foreign Function Interface for Python calling C code." 167 | optional = false 168 | python-versions = "*" 169 | files = [ 170 | {file = "cffi-1.15.1-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:a66d3508133af6e8548451b25058d5812812ec3798c886bf38ed24a98216fab2"}, 171 | {file = "cffi-1.15.1-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:470c103ae716238bbe698d67ad020e1db9d9dba34fa5a899b5e21577e6d52ed2"}, 172 | {file = "cffi-1.15.1-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:9ad5db27f9cabae298d151c85cf2bad1d359a1b9c686a275df03385758e2f914"}, 173 | {file = "cffi-1.15.1-cp27-cp27m-win32.whl", hash = "sha256:b3bbeb01c2b273cca1e1e0c5df57f12dce9a4dd331b4fa1635b8bec26350bde3"}, 174 | {file = "cffi-1.15.1-cp27-cp27m-win_amd64.whl", hash = "sha256:e00b098126fd45523dd056d2efba6c5a63b71ffe9f2bbe1a4fe1716e1d0c331e"}, 175 | {file = "cffi-1.15.1-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:d61f4695e6c866a23a21acab0509af1cdfd2c013cf256bbf5b6b5e2695827162"}, 176 | {file = "cffi-1.15.1-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:ed9cb427ba5504c1dc15ede7d516b84757c3e3d7868ccc85121d9310d27eed0b"}, 177 | {file = "cffi-1.15.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:39d39875251ca8f612b6f33e6b1195af86d1b3e60086068be9cc053aa4376e21"}, 178 | {file = "cffi-1.15.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:285d29981935eb726a4399badae8f0ffdff4f5050eaa6d0cfc3f64b857b77185"}, 179 | {file = "cffi-1.15.1-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3eb6971dcff08619f8d91607cfc726518b6fa2a9eba42856be181c6d0d9515fd"}, 180 | {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:21157295583fe8943475029ed5abdcf71eb3911894724e360acff1d61c1d54bc"}, 181 | {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5635bd9cb9731e6d4a1132a498dd34f764034a8ce60cef4f5319c0541159392f"}, 182 | {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:2012c72d854c2d03e45d06ae57f40d78e5770d252f195b93f581acf3ba44496e"}, 183 | {file = "cffi-1.15.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dd86c085fae2efd48ac91dd7ccffcfc0571387fe1193d33b6394db7ef31fe2a4"}, 184 | {file = "cffi-1.15.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:fa6693661a4c91757f4412306191b6dc88c1703f780c8234035eac011922bc01"}, 185 | {file = "cffi-1.15.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:59c0b02d0a6c384d453fece7566d1c7e6b7bae4fc5874ef2ef46d56776d61c9e"}, 186 | {file = "cffi-1.15.1-cp310-cp310-win32.whl", hash = "sha256:cba9d6b9a7d64d4bd46167096fc9d2f835e25d7e4c121fb2ddfc6528fb0413b2"}, 187 | {file = "cffi-1.15.1-cp310-cp310-win_amd64.whl", hash = "sha256:ce4bcc037df4fc5e3d184794f27bdaab018943698f4ca31630bc7f84a7b69c6d"}, 188 | {file = "cffi-1.15.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3d08afd128ddaa624a48cf2b859afef385b720bb4b43df214f85616922e6a5ac"}, 189 | {file = "cffi-1.15.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:3799aecf2e17cf585d977b780ce79ff0dc9b78d799fc694221ce814c2c19db83"}, 190 | {file = "cffi-1.15.1-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a591fe9e525846e4d154205572a029f653ada1a78b93697f3b5a8f1f2bc055b9"}, 191 | {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3548db281cd7d2561c9ad9984681c95f7b0e38881201e157833a2342c30d5e8c"}, 192 | {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:91fc98adde3d7881af9b59ed0294046f3806221863722ba7d8d120c575314325"}, 193 | {file = "cffi-1.15.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:94411f22c3985acaec6f83c6df553f2dbe17b698cc7f8ae751ff2237d96b9e3c"}, 194 | {file = "cffi-1.15.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:03425bdae262c76aad70202debd780501fabeaca237cdfddc008987c0e0f59ef"}, 195 | {file = "cffi-1.15.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:cc4d65aeeaa04136a12677d3dd0b1c0c94dc43abac5860ab33cceb42b801c1e8"}, 196 | {file = "cffi-1.15.1-cp311-cp311-win32.whl", hash = "sha256:a0f100c8912c114ff53e1202d0078b425bee3649ae34d7b070e9697f93c5d52d"}, 197 | {file = "cffi-1.15.1-cp311-cp311-win_amd64.whl", hash = "sha256:04ed324bda3cda42b9b695d51bb7d54b680b9719cfab04227cdd1e04e5de3104"}, 198 | {file = "cffi-1.15.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50a74364d85fd319352182ef59c5c790484a336f6db772c1a9231f1c3ed0cbd7"}, 199 | {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e263d77ee3dd201c3a142934a086a4450861778baaeeb45db4591ef65550b0a6"}, 200 | {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:cec7d9412a9102bdc577382c3929b337320c4c4c4849f2c5cdd14d7368c5562d"}, 201 | {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:4289fc34b2f5316fbb762d75362931e351941fa95fa18789191b33fc4cf9504a"}, 202 | {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:173379135477dc8cac4bc58f45db08ab45d228b3363adb7af79436135d028405"}, 203 | {file = "cffi-1.15.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:6975a3fac6bc83c4a65c9f9fcab9e47019a11d3d2cf7f3c0d03431bf145a941e"}, 204 | {file = "cffi-1.15.1-cp36-cp36m-win32.whl", hash = "sha256:2470043b93ff09bf8fb1d46d1cb756ce6132c54826661a32d4e4d132e1977adf"}, 205 | {file = "cffi-1.15.1-cp36-cp36m-win_amd64.whl", hash = "sha256:30d78fbc8ebf9c92c9b7823ee18eb92f2e6ef79b45ac84db507f52fbe3ec4497"}, 206 | {file = "cffi-1.15.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:198caafb44239b60e252492445da556afafc7d1e3ab7a1fb3f0584ef6d742375"}, 207 | {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:5ef34d190326c3b1f822a5b7a45f6c4535e2f47ed06fec77d3d799c450b2651e"}, 208 | {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8102eaf27e1e448db915d08afa8b41d6c7ca7a04b7d73af6514df10a3e74bd82"}, 209 | {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5df2768244d19ab7f60546d0c7c63ce1581f7af8b5de3eb3004b9b6fc8a9f84b"}, 210 | {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a8c4917bd7ad33e8eb21e9a5bbba979b49d9a97acb3a803092cbc1133e20343c"}, 211 | {file = "cffi-1.15.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0e2642fe3142e4cc4af0799748233ad6da94c62a8bec3a6648bf8ee68b1c7426"}, 212 | {file = "cffi-1.15.1-cp37-cp37m-win32.whl", hash = "sha256:e229a521186c75c8ad9490854fd8bbdd9a0c9aa3a524326b55be83b54d4e0ad9"}, 213 | {file = "cffi-1.15.1-cp37-cp37m-win_amd64.whl", hash = "sha256:a0b71b1b8fbf2b96e41c4d990244165e2c9be83d54962a9a1d118fd8657d2045"}, 214 | {file = "cffi-1.15.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:320dab6e7cb2eacdf0e658569d2575c4dad258c0fcc794f46215e1e39f90f2c3"}, 215 | {file = "cffi-1.15.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1e74c6b51a9ed6589199c787bf5f9875612ca4a8a0785fb2d4a84429badaf22a"}, 216 | {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5c84c68147988265e60416b57fc83425a78058853509c1b0629c180094904a5"}, 217 | {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3b926aa83d1edb5aa5b427b4053dc420ec295a08e40911296b9eb1b6170f6cca"}, 218 | {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:87c450779d0914f2861b8526e035c5e6da0a3199d8f1add1a665e1cbc6fc6d02"}, 219 | {file = "cffi-1.15.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f2c9f67e9821cad2e5f480bc8d83b8742896f1242dba247911072d4fa94c192"}, 220 | {file = "cffi-1.15.1-cp38-cp38-win32.whl", hash = "sha256:8b7ee99e510d7b66cdb6c593f21c043c248537a32e0bedf02e01e9553a172314"}, 221 | {file = "cffi-1.15.1-cp38-cp38-win_amd64.whl", hash = "sha256:00a9ed42e88df81ffae7a8ab6d9356b371399b91dbdf0c3cb1e84c03a13aceb5"}, 222 | {file = "cffi-1.15.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:54a2db7b78338edd780e7ef7f9f6c442500fb0d41a5a4ea24fff1c929d5af585"}, 223 | {file = "cffi-1.15.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:fcd131dd944808b5bdb38e6f5b53013c5aa4f334c5cad0c72742f6eba4b73db0"}, 224 | {file = "cffi-1.15.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7473e861101c9e72452f9bf8acb984947aa1661a7704553a9f6e4baa5ba64415"}, 225 | {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6c9a799e985904922a4d207a94eae35c78ebae90e128f0c4e521ce339396be9d"}, 226 | {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3bcde07039e586f91b45c88f8583ea7cf7a0770df3a1649627bf598332cb6984"}, 227 | {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:33ab79603146aace82c2427da5ca6e58f2b3f2fb5da893ceac0c42218a40be35"}, 228 | {file = "cffi-1.15.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d598b938678ebf3c67377cdd45e09d431369c3b1a5b331058c338e201f12b27"}, 229 | {file = "cffi-1.15.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:db0fbb9c62743ce59a9ff687eb5f4afbe77e5e8403d6697f7446e5f609976f76"}, 230 | {file = "cffi-1.15.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:98d85c6a2bef81588d9227dde12db8a7f47f639f4a17c9ae08e773aa9c697bf3"}, 231 | {file = "cffi-1.15.1-cp39-cp39-win32.whl", hash = "sha256:40f4774f5a9d4f5e344f31a32b5096977b5d48560c5592e2f3d2c4374bd543ee"}, 232 | {file = "cffi-1.15.1-cp39-cp39-win_amd64.whl", hash = "sha256:70df4e3b545a17496c9b3f41f5115e69a4f2e77e94e1d2a8e1070bc0c38c8a3c"}, 233 | {file = "cffi-1.15.1.tar.gz", hash = "sha256:d400bfb9a37b1351253cb402671cea7e89bdecc294e8016a707f6d1d8ac934f9"}, 234 | ] 235 | 236 | [package.dependencies] 237 | pycparser = "*" 238 | 239 | [[package]] 240 | name = "colorama" 241 | version = "0.4.6" 242 | description = "Cross-platform colored terminal text." 243 | optional = false 244 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" 245 | files = [ 246 | {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, 247 | {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, 248 | ] 249 | 250 | [[package]] 251 | name = "comm" 252 | version = "0.1.4" 253 | description = "Jupyter Python Comm implementation, for usage in ipykernel, xeus-python etc." 254 | optional = false 255 | python-versions = ">=3.6" 256 | files = [ 257 | {file = "comm-0.1.4-py3-none-any.whl", hash = "sha256:6d52794cba11b36ed9860999cd10fd02d6b2eac177068fdd585e1e2f8a96e67a"}, 258 | {file = "comm-0.1.4.tar.gz", hash = "sha256:354e40a59c9dd6db50c5cc6b4acc887d82e9603787f83b68c01a80a923984d15"}, 259 | ] 260 | 261 | [package.dependencies] 262 | traitlets = ">=4" 263 | 264 | [package.extras] 265 | lint = ["black (>=22.6.0)", "mdformat (>0.7)", "mdformat-gfm (>=0.3.5)", "ruff (>=0.0.156)"] 266 | test = ["pytest"] 267 | typing = ["mypy (>=0.990)"] 268 | 269 | [[package]] 270 | name = "debugpy" 271 | version = "1.7.0" 272 | description = "An implementation of the Debug Adapter Protocol for Python" 273 | optional = false 274 | python-versions = ">=3.7" 275 | files = [ 276 | {file = "debugpy-1.7.0-cp310-cp310-macosx_11_0_x86_64.whl", hash = "sha256:17ad9a681aca1704c55b9a5edcb495fa8f599e4655c9872b7f9cf3dc25890d48"}, 277 | {file = "debugpy-1.7.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1285920a3f9a75f5d1acf59ab1b9da9ae6eb9a05884cd7674f95170c9cafa4de"}, 278 | {file = "debugpy-1.7.0-cp310-cp310-win32.whl", hash = "sha256:a6f43a681c5025db1f1c0568069d1d1bad306a02e7c36144912b26d9c90e4724"}, 279 | {file = "debugpy-1.7.0-cp310-cp310-win_amd64.whl", hash = "sha256:9e9571d831ad3c75b5fb6f3efcb71c471cf2a74ba84af6ac1c79ce00683bed4b"}, 280 | {file = "debugpy-1.7.0-cp311-cp311-macosx_11_0_universal2.whl", hash = "sha256:538765a41198aa88cc089295b39c7322dd598f9ef1d52eaae12145c63bf9430a"}, 281 | {file = "debugpy-1.7.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:c7e8cf91f8f3f9b5fad844dd88427b85d398bda1e2a0cd65d5a21312fcbc0c6f"}, 282 | {file = "debugpy-1.7.0-cp311-cp311-win32.whl", hash = "sha256:18a69f8e142a716310dd0af6d7db08992aed99e2606108732efde101e7c65e2a"}, 283 | {file = "debugpy-1.7.0-cp311-cp311-win_amd64.whl", hash = "sha256:7515a5ba5ee9bfe956685909c5f28734c1cecd4ee813523363acfe3ca824883a"}, 284 | {file = "debugpy-1.7.0-cp37-cp37m-macosx_11_0_x86_64.whl", hash = "sha256:bc8da67ade39d9e75608cdb8601d07e63a4e85966e0572c981f14e2cf42bcdef"}, 285 | {file = "debugpy-1.7.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a5036e918c6ba8fc4c4f1fd0207d81db634431a02f0dc2ba51b12fd793c8c9de"}, 286 | {file = "debugpy-1.7.0-cp37-cp37m-win32.whl", hash = "sha256:d5be95b3946a4d7b388e45068c7b75036ac5a610f41014aee6cafcd5506423ad"}, 287 | {file = "debugpy-1.7.0-cp37-cp37m-win_amd64.whl", hash = "sha256:0e90314a078d4e3f009520c8387aba8f74c3034645daa7a332a3d1bb81335756"}, 288 | {file = "debugpy-1.7.0-cp38-cp38-macosx_11_0_x86_64.whl", hash = "sha256:1565fd904f9571c430adca597771255cff4f92171486fced6f765dcbdfc8ec8d"}, 289 | {file = "debugpy-1.7.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6516f36a2e95b3be27f171f12b641e443863f4ad5255d0fdcea6ae0be29bb912"}, 290 | {file = "debugpy-1.7.0-cp38-cp38-win32.whl", hash = "sha256:2b0e489613bc066051439df04c56777ec184b957d6810cb65f235083aef7a0dc"}, 291 | {file = "debugpy-1.7.0-cp38-cp38-win_amd64.whl", hash = "sha256:7bf0b4bbd841b2397b6a8de15da9227f1164f6d43ceee971c50194eaed930a9d"}, 292 | {file = "debugpy-1.7.0-cp39-cp39-macosx_11_0_x86_64.whl", hash = "sha256:ad22e1095b9977af432465c1e09132ba176e18df3834b1efcab1a449346b350b"}, 293 | {file = "debugpy-1.7.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f625e427f21423e5874139db529e18cb2966bdfcc1cb87a195538c5b34d163d1"}, 294 | {file = "debugpy-1.7.0-cp39-cp39-win32.whl", hash = "sha256:18bca8429d6632e2d3435055416d2d88f0309cc39709f4f6355c8d412cc61f24"}, 295 | {file = "debugpy-1.7.0-cp39-cp39-win_amd64.whl", hash = "sha256:dc8a12ac8b97ef3d6973c6679a093138c7c9b03eb685f0e253269a195f651559"}, 296 | {file = "debugpy-1.7.0-py2.py3-none-any.whl", hash = "sha256:f6de2e6f24f62969e0f0ef682d78c98161c4dca29e9fb05df4d2989005005502"}, 297 | {file = "debugpy-1.7.0.zip", hash = "sha256:676911c710e85567b17172db934a71319ed9d995104610ce23fd74a07f66e6f6"}, 298 | ] 299 | 300 | [[package]] 301 | name = "decorator" 302 | version = "5.1.1" 303 | description = "Decorators for Humans" 304 | optional = false 305 | python-versions = ">=3.5" 306 | files = [ 307 | {file = "decorator-5.1.1-py3-none-any.whl", hash = "sha256:b8c3f85900b9dc423225913c5aace94729fe1fa9763b38939a95226f02d37186"}, 308 | {file = "decorator-5.1.1.tar.gz", hash = "sha256:637996211036b6385ef91435e4fae22989472f9d571faba8927ba8253acbc330"}, 309 | ] 310 | 311 | [[package]] 312 | name = "defusedxml" 313 | version = "0.7.1" 314 | description = "XML bomb protection for Python stdlib modules" 315 | optional = false 316 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 317 | files = [ 318 | {file = "defusedxml-0.7.1-py2.py3-none-any.whl", hash = "sha256:a352e7e428770286cc899e2542b6cdaedb2b4953ff269a210103ec58f6198a61"}, 319 | {file = "defusedxml-0.7.1.tar.gz", hash = "sha256:1bb3032db185915b62d7c6209c5a8792be6a32ab2fedacc84e01b52c51aa3e69"}, 320 | ] 321 | 322 | [[package]] 323 | name = "entrypoints" 324 | version = "0.4" 325 | description = "Discover and load entry points from installed packages." 326 | optional = false 327 | python-versions = ">=3.6" 328 | files = [ 329 | {file = "entrypoints-0.4-py3-none-any.whl", hash = "sha256:f174b5ff827504fd3cd97cc3f8649f3693f51538c7e4bdf3ef002c8429d42f9f"}, 330 | {file = "entrypoints-0.4.tar.gz", hash = "sha256:b706eddaa9218a19ebcd67b56818f05bb27589b1ca9e8d797b74affad4ccacd4"}, 331 | ] 332 | 333 | [[package]] 334 | name = "envs" 335 | version = "1.4" 336 | description = "Easy access of environment variables from Python with support for strings, booleans, list, tuples, and dicts." 337 | optional = false 338 | python-versions = ">=3.6,<4.0" 339 | files = [ 340 | {file = "envs-1.4-py3-none-any.whl", hash = "sha256:4a1fcf85e4d4443e77c348ff7cdd3bfc4c0178b181d447057de342e4172e5ed1"}, 341 | {file = "envs-1.4.tar.gz", hash = "sha256:9d8435c6985d1cdd68299e04c58e2bdb8ae6cf66b2596a8079e6f9a93f2a0398"}, 342 | ] 343 | 344 | [package.extras] 345 | cli = ["Jinja2[cli] (>=3.0.3,<4.0.0)", "click[cli] (>=8.0.3,<9.0.0)", "terminaltables[cli] (>=3.1.10,<4.0.0)"] 346 | 347 | [[package]] 348 | name = "exceptiongroup" 349 | version = "1.2.0" 350 | description = "Backport of PEP 654 (exception groups)" 351 | optional = false 352 | python-versions = ">=3.7" 353 | files = [ 354 | {file = "exceptiongroup-1.2.0-py3-none-any.whl", hash = "sha256:4bfd3996ac73b41e9b9628b04e079f193850720ea5945fc96a08633c66912f14"}, 355 | {file = "exceptiongroup-1.2.0.tar.gz", hash = "sha256:91f5c769735f051a4290d52edd0858999b57e5876e9f85937691bd4c9fa3ed68"}, 356 | ] 357 | 358 | [package.extras] 359 | test = ["pytest (>=6)"] 360 | 361 | [[package]] 362 | name = "fastjsonschema" 363 | version = "2.19.1" 364 | description = "Fastest Python implementation of JSON schema" 365 | optional = false 366 | python-versions = "*" 367 | files = [ 368 | {file = "fastjsonschema-2.19.1-py3-none-any.whl", hash = "sha256:3672b47bc94178c9f23dbb654bf47440155d4db9df5f7bc47643315f9c405cd0"}, 369 | {file = "fastjsonschema-2.19.1.tar.gz", hash = "sha256:e3126a94bdc4623d3de4485f8d468a12f02a67921315ddc87836d6e456dc789d"}, 370 | ] 371 | 372 | [package.extras] 373 | devel = ["colorama", "json-spec", "jsonschema", "pylint", "pytest", "pytest-benchmark", "pytest-cache", "validictory"] 374 | 375 | [[package]] 376 | name = "idna" 377 | version = "3.6" 378 | description = "Internationalized Domain Names in Applications (IDNA)" 379 | optional = false 380 | python-versions = ">=3.5" 381 | files = [ 382 | {file = "idna-3.6-py3-none-any.whl", hash = "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f"}, 383 | {file = "idna-3.6.tar.gz", hash = "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca"}, 384 | ] 385 | 386 | [[package]] 387 | name = "importlib-metadata" 388 | version = "6.7.0" 389 | description = "Read metadata from Python packages" 390 | optional = false 391 | python-versions = ">=3.7" 392 | files = [ 393 | {file = "importlib_metadata-6.7.0-py3-none-any.whl", hash = "sha256:cb52082e659e97afc5dac71e79de97d8681de3aa07ff18578330904a9d18e5b5"}, 394 | {file = "importlib_metadata-6.7.0.tar.gz", hash = "sha256:1aaf550d4f73e5d6783e7acb77aec43d49da8017410afae93822cc9cca98c4d4"}, 395 | ] 396 | 397 | [package.dependencies] 398 | typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""} 399 | zipp = ">=0.5" 400 | 401 | [package.extras] 402 | docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] 403 | perf = ["ipython"] 404 | testing = ["flufl.flake8", "importlib-resources (>=1.3)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-mypy (>=0.9.1)", "pytest-perf (>=0.9.2)", "pytest-ruff"] 405 | 406 | [[package]] 407 | name = "importlib-resources" 408 | version = "5.12.0" 409 | description = "Read resources from Python packages" 410 | optional = false 411 | python-versions = ">=3.7" 412 | files = [ 413 | {file = "importlib_resources-5.12.0-py3-none-any.whl", hash = "sha256:7b1deeebbf351c7578e09bf2f63fa2ce8b5ffec296e0d349139d43cca061a81a"}, 414 | {file = "importlib_resources-5.12.0.tar.gz", hash = "sha256:4be82589bf5c1d7999aedf2a45159d10cb3ca4f19b2271f8792bc8e6da7b22f6"}, 415 | ] 416 | 417 | [package.dependencies] 418 | zipp = {version = ">=3.1.0", markers = "python_version < \"3.10\""} 419 | 420 | [package.extras] 421 | docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] 422 | testing = ["flake8 (<5)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] 423 | 424 | [[package]] 425 | name = "ipykernel" 426 | version = "6.16.2" 427 | description = "IPython Kernel for Jupyter" 428 | optional = false 429 | python-versions = ">=3.7" 430 | files = [ 431 | {file = "ipykernel-6.16.2-py3-none-any.whl", hash = "sha256:67daf93e5b52456cd8eea87a8b59405d2bb80ae411864a1ea206c3631d8179af"}, 432 | {file = "ipykernel-6.16.2.tar.gz", hash = "sha256:463f3d87a92e99969b1605cb7a5b4d7b36b7145a0e72d06e65918a6ddefbe630"}, 433 | ] 434 | 435 | [package.dependencies] 436 | appnope = {version = "*", markers = "platform_system == \"Darwin\""} 437 | debugpy = ">=1.0" 438 | ipython = ">=7.23.1" 439 | jupyter-client = ">=6.1.12" 440 | matplotlib-inline = ">=0.1" 441 | nest-asyncio = "*" 442 | packaging = "*" 443 | psutil = "*" 444 | pyzmq = ">=17" 445 | tornado = ">=6.1" 446 | traitlets = ">=5.1.0" 447 | 448 | [package.extras] 449 | docs = ["myst-parser", "pydata-sphinx-theme", "sphinx", "sphinxcontrib-github-alt"] 450 | test = ["flaky", "ipyparallel", "pre-commit", "pytest (>=7.0)", "pytest-cov", "pytest-timeout"] 451 | 452 | [[package]] 453 | name = "ipython" 454 | version = "7.34.0" 455 | description = "IPython: Productive Interactive Computing" 456 | optional = false 457 | python-versions = ">=3.7" 458 | files = [ 459 | {file = "ipython-7.34.0-py3-none-any.whl", hash = "sha256:c175d2440a1caff76116eb719d40538fbb316e214eda85c5515c303aacbfb23e"}, 460 | {file = "ipython-7.34.0.tar.gz", hash = "sha256:af3bdb46aa292bce5615b1b2ebc76c2080c5f77f54bda2ec72461317273e7cd6"}, 461 | ] 462 | 463 | [package.dependencies] 464 | appnope = {version = "*", markers = "sys_platform == \"darwin\""} 465 | backcall = "*" 466 | colorama = {version = "*", markers = "sys_platform == \"win32\""} 467 | decorator = "*" 468 | jedi = ">=0.16" 469 | matplotlib-inline = "*" 470 | pexpect = {version = ">4.3", markers = "sys_platform != \"win32\""} 471 | pickleshare = "*" 472 | prompt-toolkit = ">=2.0.0,<3.0.0 || >3.0.0,<3.0.1 || >3.0.1,<3.1.0" 473 | pygments = "*" 474 | setuptools = ">=18.5" 475 | traitlets = ">=4.2" 476 | 477 | [package.extras] 478 | all = ["Sphinx (>=1.3)", "ipykernel", "ipyparallel", "ipywidgets", "nbconvert", "nbformat", "nose (>=0.10.1)", "notebook", "numpy (>=1.17)", "pygments", "qtconsole", "requests", "testpath"] 479 | doc = ["Sphinx (>=1.3)"] 480 | kernel = ["ipykernel"] 481 | nbconvert = ["nbconvert"] 482 | nbformat = ["nbformat"] 483 | notebook = ["ipywidgets", "notebook"] 484 | parallel = ["ipyparallel"] 485 | qtconsole = ["qtconsole"] 486 | test = ["ipykernel", "nbformat", "nose (>=0.10.1)", "numpy (>=1.17)", "pygments", "requests", "testpath"] 487 | 488 | [[package]] 489 | name = "ipython-genutils" 490 | version = "0.2.0" 491 | description = "Vestigial utilities from IPython" 492 | optional = false 493 | python-versions = "*" 494 | files = [ 495 | {file = "ipython_genutils-0.2.0-py2.py3-none-any.whl", hash = "sha256:72dd37233799e619666c9f639a9da83c34013a73e8bbc79a7a6348d93c61fab8"}, 496 | {file = "ipython_genutils-0.2.0.tar.gz", hash = "sha256:eb2e116e75ecef9d4d228fdc66af54269afa26ab4463042e33785b887c628ba8"}, 497 | ] 498 | 499 | [[package]] 500 | name = "ipywidgets" 501 | version = "8.1.1" 502 | description = "Jupyter interactive widgets" 503 | optional = false 504 | python-versions = ">=3.7" 505 | files = [ 506 | {file = "ipywidgets-8.1.1-py3-none-any.whl", hash = "sha256:2b88d728656aea3bbfd05d32c747cfd0078f9d7e159cf982433b58ad717eed7f"}, 507 | {file = "ipywidgets-8.1.1.tar.gz", hash = "sha256:40211efb556adec6fa450ccc2a77d59ca44a060f4f9f136833df59c9f538e6e8"}, 508 | ] 509 | 510 | [package.dependencies] 511 | comm = ">=0.1.3" 512 | ipython = ">=6.1.0" 513 | jupyterlab-widgets = ">=3.0.9,<3.1.0" 514 | traitlets = ">=4.3.1" 515 | widgetsnbextension = ">=4.0.9,<4.1.0" 516 | 517 | [package.extras] 518 | test = ["ipykernel", "jsonschema", "pytest (>=3.6.0)", "pytest-cov", "pytz"] 519 | 520 | [[package]] 521 | name = "jedi" 522 | version = "0.19.1" 523 | description = "An autocompletion tool for Python that can be used for text editors." 524 | optional = false 525 | python-versions = ">=3.6" 526 | files = [ 527 | {file = "jedi-0.19.1-py2.py3-none-any.whl", hash = "sha256:e983c654fe5c02867aef4cdfce5a2fbb4a50adc0af145f70504238f18ef5e7e0"}, 528 | {file = "jedi-0.19.1.tar.gz", hash = "sha256:cf0496f3651bc65d7174ac1b7d043eff454892c708a87d1b683e57b569927ffd"}, 529 | ] 530 | 531 | [package.dependencies] 532 | parso = ">=0.8.3,<0.9.0" 533 | 534 | [package.extras] 535 | docs = ["Jinja2 (==2.11.3)", "MarkupSafe (==1.1.1)", "Pygments (==2.8.1)", "alabaster (==0.7.12)", "babel (==2.9.1)", "chardet (==4.0.0)", "commonmark (==0.8.1)", "docutils (==0.17.1)", "future (==0.18.2)", "idna (==2.10)", "imagesize (==1.2.0)", "mock (==1.0.1)", "packaging (==20.9)", "pyparsing (==2.4.7)", "pytz (==2021.1)", "readthedocs-sphinx-ext (==2.1.4)", "recommonmark (==0.5.0)", "requests (==2.25.1)", "six (==1.15.0)", "snowballstemmer (==2.1.0)", "sphinx (==1.8.5)", "sphinx-rtd-theme (==0.4.3)", "sphinxcontrib-serializinghtml (==1.1.4)", "sphinxcontrib-websupport (==1.2.4)", "urllib3 (==1.26.4)"] 536 | qa = ["flake8 (==5.0.4)", "mypy (==0.971)", "types-setuptools (==67.2.0.1)"] 537 | testing = ["Django", "attrs", "colorama", "docopt", "pytest (<7.0.0)"] 538 | 539 | [[package]] 540 | name = "jinja2" 541 | version = "3.1.2" 542 | description = "A very fast and expressive template engine." 543 | optional = false 544 | python-versions = ">=3.7" 545 | files = [ 546 | {file = "Jinja2-3.1.2-py3-none-any.whl", hash = "sha256:6088930bfe239f0e6710546ab9c19c9ef35e29792895fed6e6e31a023a182a61"}, 547 | {file = "Jinja2-3.1.2.tar.gz", hash = "sha256:31351a702a408a9e7595a8fc6150fc3f43bb6bf7e319770cbc0db9df9437e852"}, 548 | ] 549 | 550 | [package.dependencies] 551 | MarkupSafe = ">=2.0" 552 | 553 | [package.extras] 554 | i18n = ["Babel (>=2.7)"] 555 | 556 | [[package]] 557 | name = "jsonschema" 558 | version = "4.17.3" 559 | description = "An implementation of JSON Schema validation for Python" 560 | optional = false 561 | python-versions = ">=3.7" 562 | files = [ 563 | {file = "jsonschema-4.17.3-py3-none-any.whl", hash = "sha256:a870ad254da1a8ca84b6a2905cac29d265f805acc57af304784962a2aa6508f6"}, 564 | {file = "jsonschema-4.17.3.tar.gz", hash = "sha256:0f864437ab8b6076ba6707453ef8f98a6a0d512a80e93f8abdb676f737ecb60d"}, 565 | ] 566 | 567 | [package.dependencies] 568 | attrs = ">=17.4.0" 569 | importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} 570 | importlib-resources = {version = ">=1.4.0", markers = "python_version < \"3.9\""} 571 | pkgutil-resolve-name = {version = ">=1.3.10", markers = "python_version < \"3.9\""} 572 | pyrsistent = ">=0.14.0,<0.17.0 || >0.17.0,<0.17.1 || >0.17.1,<0.17.2 || >0.17.2" 573 | typing-extensions = {version = "*", markers = "python_version < \"3.8\""} 574 | 575 | [package.extras] 576 | format = ["fqdn", "idna", "isoduration", "jsonpointer (>1.13)", "rfc3339-validator", "rfc3987", "uri-template", "webcolors (>=1.11)"] 577 | format-nongpl = ["fqdn", "idna", "isoduration", "jsonpointer (>1.13)", "rfc3339-validator", "rfc3986-validator (>0.1.0)", "uri-template", "webcolors (>=1.11)"] 578 | 579 | [[package]] 580 | name = "jupyter" 581 | version = "1.0.0" 582 | description = "Jupyter metapackage. Install all the Jupyter components in one go." 583 | optional = false 584 | python-versions = "*" 585 | files = [ 586 | {file = "jupyter-1.0.0-py2.py3-none-any.whl", hash = "sha256:5b290f93b98ffbc21c0c7e749f054b3267782166d72fa5e3ed1ed4eaf34a2b78"}, 587 | {file = "jupyter-1.0.0.tar.gz", hash = "sha256:d9dc4b3318f310e34c82951ea5d6683f67bed7def4b259fafbfe4f1beb1d8e5f"}, 588 | {file = "jupyter-1.0.0.zip", hash = "sha256:3e1f86076bbb7c8c207829390305a2b1fe836d471ed54be66a3b8c41e7f46cc7"}, 589 | ] 590 | 591 | [package.dependencies] 592 | ipykernel = "*" 593 | ipywidgets = "*" 594 | jupyter-console = "*" 595 | nbconvert = "*" 596 | notebook = "*" 597 | qtconsole = "*" 598 | 599 | [[package]] 600 | name = "jupyter-client" 601 | version = "7.4.9" 602 | description = "Jupyter protocol implementation and client libraries" 603 | optional = false 604 | python-versions = ">=3.7" 605 | files = [ 606 | {file = "jupyter_client-7.4.9-py3-none-any.whl", hash = "sha256:214668aaea208195f4c13d28eb272ba79f945fc0cf3f11c7092c20b2ca1980e7"}, 607 | {file = "jupyter_client-7.4.9.tar.gz", hash = "sha256:52be28e04171f07aed8f20e1616a5a552ab9fee9cbbe6c1896ae170c3880d392"}, 608 | ] 609 | 610 | [package.dependencies] 611 | entrypoints = "*" 612 | jupyter-core = ">=4.9.2" 613 | nest-asyncio = ">=1.5.4" 614 | python-dateutil = ">=2.8.2" 615 | pyzmq = ">=23.0" 616 | tornado = ">=6.2" 617 | traitlets = "*" 618 | 619 | [package.extras] 620 | doc = ["ipykernel", "myst-parser", "sphinx (>=1.3.6)", "sphinx-rtd-theme", "sphinxcontrib-github-alt"] 621 | test = ["codecov", "coverage", "ipykernel (>=6.12)", "ipython", "mypy", "pre-commit", "pytest", "pytest-asyncio (>=0.18)", "pytest-cov", "pytest-timeout"] 622 | 623 | [[package]] 624 | name = "jupyter-console" 625 | version = "6.6.3" 626 | description = "Jupyter terminal console" 627 | optional = false 628 | python-versions = ">=3.7" 629 | files = [ 630 | {file = "jupyter_console-6.6.3-py3-none-any.whl", hash = "sha256:309d33409fcc92ffdad25f0bcdf9a4a9daa61b6f341177570fdac03de5352485"}, 631 | {file = "jupyter_console-6.6.3.tar.gz", hash = "sha256:566a4bf31c87adbfadf22cdf846e3069b59a71ed5da71d6ba4d8aaad14a53539"}, 632 | ] 633 | 634 | [package.dependencies] 635 | ipykernel = ">=6.14" 636 | ipython = "*" 637 | jupyter-client = ">=7.0.0" 638 | jupyter-core = ">=4.12,<5.0.dev0 || >=5.1.dev0" 639 | prompt-toolkit = ">=3.0.30" 640 | pygments = "*" 641 | pyzmq = ">=17" 642 | traitlets = ">=5.4" 643 | 644 | [package.extras] 645 | test = ["flaky", "pexpect", "pytest"] 646 | 647 | [[package]] 648 | name = "jupyter-core" 649 | version = "4.12.0" 650 | description = "Jupyter core package. A base package on which Jupyter projects rely." 651 | optional = false 652 | python-versions = ">=3.7" 653 | files = [ 654 | {file = "jupyter_core-4.12.0-py3-none-any.whl", hash = "sha256:a54672c539333258495579f6964144924e0aa7b07f7069947bef76d7ea5cb4c1"}, 655 | {file = "jupyter_core-4.12.0.tar.gz", hash = "sha256:87f39d7642412ae8a52291cc68e71ac01dfa2c735df2701f8108251d51b4f460"}, 656 | ] 657 | 658 | [package.dependencies] 659 | pywin32 = {version = ">=1.0", markers = "sys_platform == \"win32\" and platform_python_implementation != \"PyPy\""} 660 | traitlets = "*" 661 | 662 | [package.extras] 663 | test = ["ipykernel", "pre-commit", "pytest", "pytest-cov", "pytest-timeout"] 664 | 665 | [[package]] 666 | name = "jupyter-server" 667 | version = "1.24.0" 668 | description = "The backend—i.e. core services, APIs, and REST endpoints—to Jupyter web applications." 669 | optional = false 670 | python-versions = ">=3.7" 671 | files = [ 672 | {file = "jupyter_server-1.24.0-py3-none-any.whl", hash = "sha256:c88ddbe862966ea1aea8c3ccb89a5903abd8fbcfe5cd14090ef549d403332c37"}, 673 | {file = "jupyter_server-1.24.0.tar.gz", hash = "sha256:23368e8e214baf82b313d4c5a0d828ca73015e1a192ce3829bd74e62fab8d046"}, 674 | ] 675 | 676 | [package.dependencies] 677 | anyio = ">=3.1.0,<4" 678 | argon2-cffi = "*" 679 | jinja2 = "*" 680 | jupyter-client = ">=6.1.12" 681 | jupyter-core = ">=4.12,<5.0.dev0 || >=5.1.dev0" 682 | nbconvert = ">=6.4.4" 683 | nbformat = ">=5.2.0" 684 | packaging = "*" 685 | prometheus-client = "*" 686 | pywinpty = {version = "*", markers = "os_name == \"nt\""} 687 | pyzmq = ">=17" 688 | Send2Trash = "*" 689 | terminado = ">=0.8.3" 690 | tornado = ">=6.1.0" 691 | traitlets = ">=5.1" 692 | websocket-client = "*" 693 | 694 | [package.extras] 695 | test = ["coverage", "ipykernel", "pre-commit", "pytest (>=7.0)", "pytest-console-scripts", "pytest-cov", "pytest-mock", "pytest-timeout", "pytest-tornasync", "requests"] 696 | 697 | [[package]] 698 | name = "jupyterlab-pygments" 699 | version = "0.2.2" 700 | description = "Pygments theme using JupyterLab CSS variables" 701 | optional = false 702 | python-versions = ">=3.7" 703 | files = [ 704 | {file = "jupyterlab_pygments-0.2.2-py2.py3-none-any.whl", hash = "sha256:2405800db07c9f770863bcf8049a529c3dd4d3e28536638bd7c1c01d2748309f"}, 705 | {file = "jupyterlab_pygments-0.2.2.tar.gz", hash = "sha256:7405d7fde60819d905a9fa8ce89e4cd830e318cdad22a0030f7a901da705585d"}, 706 | ] 707 | 708 | [[package]] 709 | name = "jupyterlab-widgets" 710 | version = "3.0.9" 711 | description = "Jupyter interactive widgets for JupyterLab" 712 | optional = false 713 | python-versions = ">=3.7" 714 | files = [ 715 | {file = "jupyterlab_widgets-3.0.9-py3-none-any.whl", hash = "sha256:3cf5bdf5b897bf3bccf1c11873aa4afd776d7430200f765e0686bd352487b58d"}, 716 | {file = "jupyterlab_widgets-3.0.9.tar.gz", hash = "sha256:6005a4e974c7beee84060fdfba341a3218495046de8ae3ec64888e5fe19fdb4c"}, 717 | ] 718 | 719 | [[package]] 720 | name = "markupsafe" 721 | version = "2.1.3" 722 | description = "Safely add untrusted strings to HTML/XML markup." 723 | optional = false 724 | python-versions = ">=3.7" 725 | files = [ 726 | {file = "MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:cd0f502fe016460680cd20aaa5a76d241d6f35a1c3350c474bac1273803893fa"}, 727 | {file = "MarkupSafe-2.1.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e09031c87a1e51556fdcb46e5bd4f59dfb743061cf93c4d6831bf894f125eb57"}, 728 | {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:68e78619a61ecf91e76aa3e6e8e33fc4894a2bebe93410754bd28fce0a8a4f9f"}, 729 | {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:65c1a9bcdadc6c28eecee2c119465aebff8f7a584dd719facdd9e825ec61ab52"}, 730 | {file = "MarkupSafe-2.1.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:525808b8019e36eb524b8c68acdd63a37e75714eac50e988180b169d64480a00"}, 731 | {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:962f82a3086483f5e5f64dbad880d31038b698494799b097bc59c2edf392fce6"}, 732 | {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:aa7bd130efab1c280bed0f45501b7c8795f9fdbeb02e965371bbef3523627779"}, 733 | {file = "MarkupSafe-2.1.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c9c804664ebe8f83a211cace637506669e7890fec1b4195b505c214e50dd4eb7"}, 734 | {file = "MarkupSafe-2.1.3-cp310-cp310-win32.whl", hash = "sha256:10bbfe99883db80bdbaff2dcf681dfc6533a614f700da1287707e8a5d78a8431"}, 735 | {file = "MarkupSafe-2.1.3-cp310-cp310-win_amd64.whl", hash = "sha256:1577735524cdad32f9f694208aa75e422adba74f1baee7551620e43a3141f559"}, 736 | {file = "MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ad9e82fb8f09ade1c3e1b996a6337afac2b8b9e365f926f5a61aacc71adc5b3c"}, 737 | {file = "MarkupSafe-2.1.3-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:3c0fae6c3be832a0a0473ac912810b2877c8cb9d76ca48de1ed31e1c68386575"}, 738 | {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b076b6226fb84157e3f7c971a47ff3a679d837cf338547532ab866c57930dbee"}, 739 | {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bfce63a9e7834b12b87c64d6b155fdd9b3b96191b6bd334bf37db7ff1fe457f2"}, 740 | {file = "MarkupSafe-2.1.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:338ae27d6b8745585f87218a3f23f1512dbf52c26c28e322dbe54bcede54ccb9"}, 741 | {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e4dd52d80b8c83fdce44e12478ad2e85c64ea965e75d66dbeafb0a3e77308fcc"}, 742 | {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:df0be2b576a7abbf737b1575f048c23fb1d769f267ec4358296f31c2479db8f9"}, 743 | {file = "MarkupSafe-2.1.3-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:5bbe06f8eeafd38e5d0a4894ffec89378b6c6a625ff57e3028921f8ff59318ac"}, 744 | {file = "MarkupSafe-2.1.3-cp311-cp311-win32.whl", hash = "sha256:dd15ff04ffd7e05ffcb7fe79f1b98041b8ea30ae9234aed2a9168b5797c3effb"}, 745 | {file = "MarkupSafe-2.1.3-cp311-cp311-win_amd64.whl", hash = "sha256:134da1eca9ec0ae528110ccc9e48041e0828d79f24121a1a146161103c76e686"}, 746 | {file = "MarkupSafe-2.1.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8e254ae696c88d98da6555f5ace2279cf7cd5b3f52be2b5cf97feafe883b58d2"}, 747 | {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cb0932dc158471523c9637e807d9bfb93e06a95cbf010f1a38b98623b929ef2b"}, 748 | {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9402b03f1a1b4dc4c19845e5c749e3ab82d5078d16a2a4c2cd2df62d57bb0707"}, 749 | {file = "MarkupSafe-2.1.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ca379055a47383d02a5400cb0d110cef0a776fc644cda797db0c5696cfd7e18e"}, 750 | {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:b7ff0f54cb4ff66dd38bebd335a38e2c22c41a8ee45aa608efc890ac3e3931bc"}, 751 | {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:c011a4149cfbcf9f03994ec2edffcb8b1dc2d2aede7ca243746df97a5d41ce48"}, 752 | {file = "MarkupSafe-2.1.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:56d9f2ecac662ca1611d183feb03a3fa4406469dafe241673d521dd5ae92a155"}, 753 | {file = "MarkupSafe-2.1.3-cp37-cp37m-win32.whl", hash = "sha256:8758846a7e80910096950b67071243da3e5a20ed2546e6392603c096778d48e0"}, 754 | {file = "MarkupSafe-2.1.3-cp37-cp37m-win_amd64.whl", hash = "sha256:787003c0ddb00500e49a10f2844fac87aa6ce977b90b0feaaf9de23c22508b24"}, 755 | {file = "MarkupSafe-2.1.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:2ef12179d3a291be237280175b542c07a36e7f60718296278d8593d21ca937d4"}, 756 | {file = "MarkupSafe-2.1.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:2c1b19b3aaacc6e57b7e25710ff571c24d6c3613a45e905b1fde04d691b98ee0"}, 757 | {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8afafd99945ead6e075b973fefa56379c5b5c53fd8937dad92c662da5d8fd5ee"}, 758 | {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8c41976a29d078bb235fea9b2ecd3da465df42a562910f9022f1a03107bd02be"}, 759 | {file = "MarkupSafe-2.1.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d080e0a5eb2529460b30190fcfcc4199bd7f827663f858a226a81bc27beaa97e"}, 760 | {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:69c0f17e9f5a7afdf2cc9fb2d1ce6aabdb3bafb7f38017c0b77862bcec2bbad8"}, 761 | {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:504b320cd4b7eff6f968eddf81127112db685e81f7e36e75f9f84f0df46041c3"}, 762 | {file = "MarkupSafe-2.1.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:42de32b22b6b804f42c5d98be4f7e5e977ecdd9ee9b660fda1a3edf03b11792d"}, 763 | {file = "MarkupSafe-2.1.3-cp38-cp38-win32.whl", hash = "sha256:ceb01949af7121f9fc39f7d27f91be8546f3fb112c608bc4029aef0bab86a2a5"}, 764 | {file = "MarkupSafe-2.1.3-cp38-cp38-win_amd64.whl", hash = "sha256:1b40069d487e7edb2676d3fbdb2b0829ffa2cd63a2ec26c4938b2d34391b4ecc"}, 765 | {file = "MarkupSafe-2.1.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8023faf4e01efadfa183e863fefde0046de576c6f14659e8782065bcece22198"}, 766 | {file = "MarkupSafe-2.1.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6b2b56950d93e41f33b4223ead100ea0fe11f8e6ee5f641eb753ce4b77a7042b"}, 767 | {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9dcdfd0eaf283af041973bff14a2e143b8bd64e069f4c383416ecd79a81aab58"}, 768 | {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:05fb21170423db021895e1ea1e1f3ab3adb85d1c2333cbc2310f2a26bc77272e"}, 769 | {file = "MarkupSafe-2.1.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:282c2cb35b5b673bbcadb33a585408104df04f14b2d9b01d4c345a3b92861c2c"}, 770 | {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:ab4a0df41e7c16a1392727727e7998a467472d0ad65f3ad5e6e765015df08636"}, 771 | {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:7ef3cb2ebbf91e330e3bb937efada0edd9003683db6b57bb108c4001f37a02ea"}, 772 | {file = "MarkupSafe-2.1.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:0a4e4a1aff6c7ac4cd55792abf96c915634c2b97e3cc1c7129578aa68ebd754e"}, 773 | {file = "MarkupSafe-2.1.3-cp39-cp39-win32.whl", hash = "sha256:fec21693218efe39aa7f8599346e90c705afa52c5b31ae019b2e57e8f6542bb2"}, 774 | {file = "MarkupSafe-2.1.3-cp39-cp39-win_amd64.whl", hash = "sha256:3fd4abcb888d15a94f32b75d8fd18ee162ca0c064f35b11134be77050296d6ba"}, 775 | {file = "MarkupSafe-2.1.3.tar.gz", hash = "sha256:af598ed32d6ae86f1b747b82783958b1a4ab8f617b06fe68795c7f026abbdcad"}, 776 | ] 777 | 778 | [[package]] 779 | name = "matplotlib-inline" 780 | version = "0.1.6" 781 | description = "Inline Matplotlib backend for Jupyter" 782 | optional = false 783 | python-versions = ">=3.5" 784 | files = [ 785 | {file = "matplotlib-inline-0.1.6.tar.gz", hash = "sha256:f887e5f10ba98e8d2b150ddcf4702c1e5f8b3a20005eb0f74bfdbd360ee6f304"}, 786 | {file = "matplotlib_inline-0.1.6-py3-none-any.whl", hash = "sha256:f1f41aab5328aa5aaea9b16d083b128102f8712542f819fe7e6a420ff581b311"}, 787 | ] 788 | 789 | [package.dependencies] 790 | traitlets = "*" 791 | 792 | [[package]] 793 | name = "mistune" 794 | version = "3.0.2" 795 | description = "A sane and fast Markdown parser with useful plugins and renderers" 796 | optional = false 797 | python-versions = ">=3.7" 798 | files = [ 799 | {file = "mistune-3.0.2-py3-none-any.whl", hash = "sha256:71481854c30fdbc938963d3605b72501f5c10a9320ecd412c121c163a1c7d205"}, 800 | {file = "mistune-3.0.2.tar.gz", hash = "sha256:fc7f93ded930c92394ef2cb6f04a8aabab4117a91449e72dcc8dfa646a508be8"}, 801 | ] 802 | 803 | [[package]] 804 | name = "nbclassic" 805 | version = "1.0.0" 806 | description = "Jupyter Notebook as a Jupyter Server extension." 807 | optional = false 808 | python-versions = ">=3.7" 809 | files = [ 810 | {file = "nbclassic-1.0.0-py3-none-any.whl", hash = "sha256:f99e4769b4750076cd4235c044b61232110733322384a94a63791d2e7beacc66"}, 811 | {file = "nbclassic-1.0.0.tar.gz", hash = "sha256:0ae11eb2319455d805596bf320336cda9554b41d99ab9a3c31bf8180bffa30e3"}, 812 | ] 813 | 814 | [package.dependencies] 815 | argon2-cffi = "*" 816 | ipykernel = "*" 817 | ipython-genutils = "*" 818 | jinja2 = "*" 819 | jupyter-client = ">=6.1.1" 820 | jupyter-core = ">=4.6.1" 821 | jupyter-server = ">=1.8" 822 | nbconvert = ">=5" 823 | nbformat = "*" 824 | nest-asyncio = ">=1.5" 825 | notebook-shim = ">=0.2.3" 826 | prometheus-client = "*" 827 | pyzmq = ">=17" 828 | Send2Trash = ">=1.8.0" 829 | terminado = ">=0.8.3" 830 | tornado = ">=6.1" 831 | traitlets = ">=4.2.1" 832 | 833 | [package.extras] 834 | docs = ["myst-parser", "nbsphinx", "sphinx", "sphinx-rtd-theme", "sphinxcontrib-github-alt"] 835 | json-logging = ["json-logging"] 836 | test = ["coverage", "nbval", "pytest", "pytest-cov", "pytest-jupyter", "pytest-playwright", "pytest-tornasync", "requests", "requests-unixsocket", "testpath"] 837 | 838 | [[package]] 839 | name = "nbclient" 840 | version = "0.7.4" 841 | description = "A client library for executing notebooks. Formerly nbconvert's ExecutePreprocessor." 842 | optional = false 843 | python-versions = ">=3.7.0" 844 | files = [ 845 | {file = "nbclient-0.7.4-py3-none-any.whl", hash = "sha256:c817c0768c5ff0d60e468e017613e6eae27b6fa31e43f905addd2d24df60c125"}, 846 | {file = "nbclient-0.7.4.tar.gz", hash = "sha256:d447f0e5a4cfe79d462459aec1b3dc5c2e9152597262be8ee27f7d4c02566a0d"}, 847 | ] 848 | 849 | [package.dependencies] 850 | jupyter-client = ">=6.1.12" 851 | jupyter-core = ">=4.12,<5.0.dev0 || >=5.1.dev0" 852 | nbformat = ">=5.1" 853 | traitlets = ">=5.3" 854 | 855 | [package.extras] 856 | dev = ["pre-commit"] 857 | docs = ["autodoc-traits", "mock", "moto", "myst-parser", "nbclient[test]", "sphinx (>=1.7)", "sphinx-book-theme", "sphinxcontrib-spelling"] 858 | test = ["flaky", "ipykernel", "ipython", "ipywidgets", "nbconvert (>=7.0.0)", "pytest (>=7.0)", "pytest-asyncio", "pytest-cov (>=4.0)", "testpath", "xmltodict"] 859 | 860 | [[package]] 861 | name = "nbconvert" 862 | version = "7.6.0" 863 | description = "Converting Jupyter Notebooks" 864 | optional = false 865 | python-versions = ">=3.7" 866 | files = [ 867 | {file = "nbconvert-7.6.0-py3-none-any.whl", hash = "sha256:5a445c6794b0791984bc5436608fe2c066cb43c83920c7bc91bde3b765e9a264"}, 868 | {file = "nbconvert-7.6.0.tar.gz", hash = "sha256:24fcf27efdef2b51d7f090cc5ce5a9b178766a55be513c4ebab08c91899ab550"}, 869 | ] 870 | 871 | [package.dependencies] 872 | beautifulsoup4 = "*" 873 | bleach = "!=5.0.0" 874 | defusedxml = "*" 875 | importlib-metadata = {version = ">=3.6", markers = "python_version < \"3.10\""} 876 | jinja2 = ">=3.0" 877 | jupyter-core = ">=4.7" 878 | jupyterlab-pygments = "*" 879 | markupsafe = ">=2.0" 880 | mistune = ">=2.0.3,<4" 881 | nbclient = ">=0.5.0" 882 | nbformat = ">=5.7" 883 | packaging = "*" 884 | pandocfilters = ">=1.4.1" 885 | pygments = ">=2.4.1" 886 | tinycss2 = "*" 887 | traitlets = ">=5.1" 888 | 889 | [package.extras] 890 | all = ["nbconvert[docs,qtpdf,serve,test,webpdf]"] 891 | docs = ["ipykernel", "ipython", "myst-parser", "nbsphinx (>=0.2.12)", "pydata-sphinx-theme", "sphinx (==5.0.2)", "sphinxcontrib-spelling"] 892 | qtpdf = ["nbconvert[qtpng]"] 893 | qtpng = ["pyqtwebengine (>=5.15)"] 894 | serve = ["tornado (>=6.1)"] 895 | test = ["ipykernel", "ipywidgets (>=7)", "pre-commit", "pytest", "pytest-dependency"] 896 | webpdf = ["pyppeteer (>=1,<1.1)"] 897 | 898 | [[package]] 899 | name = "nbformat" 900 | version = "5.8.0" 901 | description = "The Jupyter Notebook format" 902 | optional = false 903 | python-versions = ">=3.7" 904 | files = [ 905 | {file = "nbformat-5.8.0-py3-none-any.whl", hash = "sha256:d910082bd3e0bffcf07eabf3683ed7dda0727a326c446eeb2922abe102e65162"}, 906 | {file = "nbformat-5.8.0.tar.gz", hash = "sha256:46dac64c781f1c34dfd8acba16547024110348f9fc7eab0f31981c2a3dc48d1f"}, 907 | ] 908 | 909 | [package.dependencies] 910 | fastjsonschema = "*" 911 | importlib-metadata = {version = ">=3.6", markers = "python_version < \"3.8\""} 912 | jsonschema = ">=2.6" 913 | jupyter-core = "*" 914 | traitlets = ">=5.1" 915 | 916 | [package.extras] 917 | docs = ["myst-parser", "pydata-sphinx-theme", "sphinx", "sphinxcontrib-github-alt", "sphinxcontrib-spelling"] 918 | test = ["pep440", "pre-commit", "pytest", "testpath"] 919 | 920 | [[package]] 921 | name = "nest-asyncio" 922 | version = "1.5.8" 923 | description = "Patch asyncio to allow nested event loops" 924 | optional = false 925 | python-versions = ">=3.5" 926 | files = [ 927 | {file = "nest_asyncio-1.5.8-py3-none-any.whl", hash = "sha256:accda7a339a70599cb08f9dd09a67e0c2ef8d8d6f4c07f96ab203f2ae254e48d"}, 928 | {file = "nest_asyncio-1.5.8.tar.gz", hash = "sha256:25aa2ca0d2a5b5531956b9e273b45cf664cae2b145101d73b86b199978d48fdb"}, 929 | ] 930 | 931 | [[package]] 932 | name = "notebook" 933 | version = "6.5.6" 934 | description = "A web-based notebook environment for interactive computing" 935 | optional = false 936 | python-versions = ">=3.7" 937 | files = [ 938 | {file = "notebook-6.5.6-py3-none-any.whl", hash = "sha256:c1e2eb2e3b6079a0552a04974883a48d04c3c05792170d64a4b23d707d453181"}, 939 | {file = "notebook-6.5.6.tar.gz", hash = "sha256:b4625a4b7a597839dd3156b140d5ba2c7123761f98245a3290f67a8b8ee048d9"}, 940 | ] 941 | 942 | [package.dependencies] 943 | argon2-cffi = "*" 944 | ipykernel = "*" 945 | ipython-genutils = "*" 946 | jinja2 = "*" 947 | jupyter-client = ">=5.3.4,<8" 948 | jupyter-core = ">=4.6.1" 949 | nbclassic = ">=0.4.7" 950 | nbconvert = ">=5" 951 | nbformat = "*" 952 | nest-asyncio = ">=1.5" 953 | prometheus-client = "*" 954 | pyzmq = ">=17,<25" 955 | Send2Trash = ">=1.8.0" 956 | terminado = ">=0.8.3" 957 | tornado = ">=6.1" 958 | traitlets = ">=4.2.1" 959 | 960 | [package.extras] 961 | docs = ["myst-parser", "nbsphinx", "sphinx", "sphinx-rtd-theme", "sphinxcontrib-github-alt"] 962 | json-logging = ["json-logging"] 963 | test = ["coverage", "nbval", "pytest", "pytest-cov", "requests", "requests-unixsocket", "selenium (==4.1.5)", "testpath"] 964 | 965 | [[package]] 966 | name = "notebook-shim" 967 | version = "0.2.3" 968 | description = "A shim layer for notebook traits and config" 969 | optional = false 970 | python-versions = ">=3.7" 971 | files = [ 972 | {file = "notebook_shim-0.2.3-py3-none-any.whl", hash = "sha256:a83496a43341c1674b093bfcebf0fe8e74cbe7eda5fd2bbc56f8e39e1486c0c7"}, 973 | {file = "notebook_shim-0.2.3.tar.gz", hash = "sha256:f69388ac283ae008cd506dda10d0288b09a017d822d5e8c7129a152cbd3ce7e9"}, 974 | ] 975 | 976 | [package.dependencies] 977 | jupyter-server = ">=1.8,<3" 978 | 979 | [package.extras] 980 | test = ["pytest", "pytest-console-scripts", "pytest-jupyter", "pytest-tornasync"] 981 | 982 | [[package]] 983 | name = "packaging" 984 | version = "23.2" 985 | description = "Core utilities for Python packages" 986 | optional = false 987 | python-versions = ">=3.7" 988 | files = [ 989 | {file = "packaging-23.2-py3-none-any.whl", hash = "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7"}, 990 | {file = "packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5"}, 991 | ] 992 | 993 | [[package]] 994 | name = "pandocfilters" 995 | version = "1.5.0" 996 | description = "Utilities for writing pandoc filters in python" 997 | optional = false 998 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 999 | files = [ 1000 | {file = "pandocfilters-1.5.0-py2.py3-none-any.whl", hash = "sha256:33aae3f25fd1a026079f5d27bdd52496f0e0803b3469282162bafdcbdf6ef14f"}, 1001 | {file = "pandocfilters-1.5.0.tar.gz", hash = "sha256:0b679503337d233b4339a817bfc8c50064e2eff681314376a47cb582305a7a38"}, 1002 | ] 1003 | 1004 | [[package]] 1005 | name = "parso" 1006 | version = "0.8.3" 1007 | description = "A Python Parser" 1008 | optional = false 1009 | python-versions = ">=3.6" 1010 | files = [ 1011 | {file = "parso-0.8.3-py2.py3-none-any.whl", hash = "sha256:c001d4636cd3aecdaf33cbb40aebb59b094be2a74c556778ef5576c175e19e75"}, 1012 | {file = "parso-0.8.3.tar.gz", hash = "sha256:8c07be290bb59f03588915921e29e8a50002acaf2cdc5fa0e0114f91709fafa0"}, 1013 | ] 1014 | 1015 | [package.extras] 1016 | qa = ["flake8 (==3.8.3)", "mypy (==0.782)"] 1017 | testing = ["docopt", "pytest (<6.0.0)"] 1018 | 1019 | [[package]] 1020 | name = "pexpect" 1021 | version = "4.9.0" 1022 | description = "Pexpect allows easy control of interactive console applications." 1023 | optional = false 1024 | python-versions = "*" 1025 | files = [ 1026 | {file = "pexpect-4.9.0-py2.py3-none-any.whl", hash = "sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523"}, 1027 | {file = "pexpect-4.9.0.tar.gz", hash = "sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f"}, 1028 | ] 1029 | 1030 | [package.dependencies] 1031 | ptyprocess = ">=0.5" 1032 | 1033 | [[package]] 1034 | name = "pickleshare" 1035 | version = "0.7.5" 1036 | description = "Tiny 'shelve'-like database with concurrency support" 1037 | optional = false 1038 | python-versions = "*" 1039 | files = [ 1040 | {file = "pickleshare-0.7.5-py2.py3-none-any.whl", hash = "sha256:9649af414d74d4df115d5d718f82acb59c9d418196b7b4290ed47a12ce62df56"}, 1041 | {file = "pickleshare-0.7.5.tar.gz", hash = "sha256:87683d47965c1da65cdacaf31c8441d12b8044cdec9aca500cd78fc2c683afca"}, 1042 | ] 1043 | 1044 | [[package]] 1045 | name = "pkgutil-resolve-name" 1046 | version = "1.3.10" 1047 | description = "Resolve a name to an object." 1048 | optional = false 1049 | python-versions = ">=3.6" 1050 | files = [ 1051 | {file = "pkgutil_resolve_name-1.3.10-py3-none-any.whl", hash = "sha256:ca27cc078d25c5ad71a9de0a7a330146c4e014c2462d9af19c6b828280649c5e"}, 1052 | {file = "pkgutil_resolve_name-1.3.10.tar.gz", hash = "sha256:357d6c9e6a755653cfd78893817c0853af365dd51ec97f3d358a819373bbd174"}, 1053 | ] 1054 | 1055 | [[package]] 1056 | name = "prometheus-client" 1057 | version = "0.17.1" 1058 | description = "Python client for the Prometheus monitoring system." 1059 | optional = false 1060 | python-versions = ">=3.6" 1061 | files = [ 1062 | {file = "prometheus_client-0.17.1-py3-none-any.whl", hash = "sha256:e537f37160f6807b8202a6fc4764cdd19bac5480ddd3e0d463c3002b34462101"}, 1063 | {file = "prometheus_client-0.17.1.tar.gz", hash = "sha256:21e674f39831ae3f8acde238afd9a27a37d0d2fb5a28ea094f0ce25d2cbf2091"}, 1064 | ] 1065 | 1066 | [package.extras] 1067 | twisted = ["twisted"] 1068 | 1069 | [[package]] 1070 | name = "prompt-toolkit" 1071 | version = "3.0.43" 1072 | description = "Library for building powerful interactive command lines in Python" 1073 | optional = false 1074 | python-versions = ">=3.7.0" 1075 | files = [ 1076 | {file = "prompt_toolkit-3.0.43-py3-none-any.whl", hash = "sha256:a11a29cb3bf0a28a387fe5122cdb649816a957cd9261dcedf8c9f1fef33eacf6"}, 1077 | {file = "prompt_toolkit-3.0.43.tar.gz", hash = "sha256:3527b7af26106cbc65a040bcc84839a3566ec1b051bb0bfe953631e704b0ff7d"}, 1078 | ] 1079 | 1080 | [package.dependencies] 1081 | wcwidth = "*" 1082 | 1083 | [[package]] 1084 | name = "psutil" 1085 | version = "5.9.7" 1086 | description = "Cross-platform lib for process and system monitoring in Python." 1087 | optional = false 1088 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" 1089 | files = [ 1090 | {file = "psutil-5.9.7-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:0bd41bf2d1463dfa535942b2a8f0e958acf6607ac0be52265ab31f7923bcd5e6"}, 1091 | {file = "psutil-5.9.7-cp27-cp27m-manylinux2010_i686.whl", hash = "sha256:5794944462509e49d4d458f4dbfb92c47539e7d8d15c796f141f474010084056"}, 1092 | {file = "psutil-5.9.7-cp27-cp27m-manylinux2010_x86_64.whl", hash = "sha256:fe361f743cb3389b8efda21980d93eb55c1f1e3898269bc9a2a1d0bb7b1f6508"}, 1093 | {file = "psutil-5.9.7-cp27-cp27mu-manylinux2010_i686.whl", hash = "sha256:e469990e28f1ad738f65a42dcfc17adaed9d0f325d55047593cb9033a0ab63df"}, 1094 | {file = "psutil-5.9.7-cp27-cp27mu-manylinux2010_x86_64.whl", hash = "sha256:3c4747a3e2ead1589e647e64aad601981f01b68f9398ddf94d01e3dc0d1e57c7"}, 1095 | {file = "psutil-5.9.7-cp27-none-win32.whl", hash = "sha256:1d4bc4a0148fdd7fd8f38e0498639ae128e64538faa507df25a20f8f7fb2341c"}, 1096 | {file = "psutil-5.9.7-cp27-none-win_amd64.whl", hash = "sha256:4c03362e280d06bbbfcd52f29acd79c733e0af33d707c54255d21029b8b32ba6"}, 1097 | {file = "psutil-5.9.7-cp36-abi3-macosx_10_9_x86_64.whl", hash = "sha256:ea36cc62e69a13ec52b2f625c27527f6e4479bca2b340b7a452af55b34fcbe2e"}, 1098 | {file = "psutil-5.9.7-cp36-abi3-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1132704b876e58d277168cd729d64750633d5ff0183acf5b3c986b8466cd0284"}, 1099 | {file = "psutil-5.9.7-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe8b7f07948f1304497ce4f4684881250cd859b16d06a1dc4d7941eeb6233bfe"}, 1100 | {file = "psutil-5.9.7-cp36-cp36m-win32.whl", hash = "sha256:b27f8fdb190c8c03914f908a4555159327d7481dac2f01008d483137ef3311a9"}, 1101 | {file = "psutil-5.9.7-cp36-cp36m-win_amd64.whl", hash = "sha256:44969859757f4d8f2a9bd5b76eba8c3099a2c8cf3992ff62144061e39ba8568e"}, 1102 | {file = "psutil-5.9.7-cp37-abi3-win32.whl", hash = "sha256:c727ca5a9b2dd5193b8644b9f0c883d54f1248310023b5ad3e92036c5e2ada68"}, 1103 | {file = "psutil-5.9.7-cp37-abi3-win_amd64.whl", hash = "sha256:f37f87e4d73b79e6c5e749440c3113b81d1ee7d26f21c19c47371ddea834f414"}, 1104 | {file = "psutil-5.9.7-cp38-abi3-macosx_11_0_arm64.whl", hash = "sha256:032f4f2c909818c86cea4fe2cc407f1c0f0cde8e6c6d702b28b8ce0c0d143340"}, 1105 | {file = "psutil-5.9.7.tar.gz", hash = "sha256:3f02134e82cfb5d089fddf20bb2e03fd5cd52395321d1c8458a9e58500ff417c"}, 1106 | ] 1107 | 1108 | [package.extras] 1109 | test = ["enum34", "ipaddress", "mock", "pywin32", "wmi"] 1110 | 1111 | [[package]] 1112 | name = "ptyprocess" 1113 | version = "0.7.0" 1114 | description = "Run a subprocess in a pseudo terminal" 1115 | optional = false 1116 | python-versions = "*" 1117 | files = [ 1118 | {file = "ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35"}, 1119 | {file = "ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220"}, 1120 | ] 1121 | 1122 | [[package]] 1123 | name = "py" 1124 | version = "1.11.0" 1125 | description = "library with cross-python path, ini-parsing, io, code, log facilities" 1126 | optional = false 1127 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" 1128 | files = [ 1129 | {file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"}, 1130 | {file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"}, 1131 | ] 1132 | 1133 | [[package]] 1134 | name = "pycparser" 1135 | version = "2.21" 1136 | description = "C parser in Python" 1137 | optional = false 1138 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 1139 | files = [ 1140 | {file = "pycparser-2.21-py2.py3-none-any.whl", hash = "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9"}, 1141 | {file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"}, 1142 | ] 1143 | 1144 | [[package]] 1145 | name = "pygments" 1146 | version = "2.17.2" 1147 | description = "Pygments is a syntax highlighting package written in Python." 1148 | optional = false 1149 | python-versions = ">=3.7" 1150 | files = [ 1151 | {file = "pygments-2.17.2-py3-none-any.whl", hash = "sha256:b27c2826c47d0f3219f29554824c30c5e8945175d888647acd804ddd04af846c"}, 1152 | {file = "pygments-2.17.2.tar.gz", hash = "sha256:da46cec9fd2de5be3a8a784f434e4c4ab670b4ff54d605c4c2717e9d49c4c367"}, 1153 | ] 1154 | 1155 | [package.extras] 1156 | plugins = ["importlib-metadata"] 1157 | windows-terminal = ["colorama (>=0.4.6)"] 1158 | 1159 | [[package]] 1160 | name = "pyrsistent" 1161 | version = "0.19.3" 1162 | description = "Persistent/Functional/Immutable data structures" 1163 | optional = false 1164 | python-versions = ">=3.7" 1165 | files = [ 1166 | {file = "pyrsistent-0.19.3-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:20460ac0ea439a3e79caa1dbd560344b64ed75e85d8703943e0b66c2a6150e4a"}, 1167 | {file = "pyrsistent-0.19.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4c18264cb84b5e68e7085a43723f9e4c1fd1d935ab240ce02c0324a8e01ccb64"}, 1168 | {file = "pyrsistent-0.19.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4b774f9288dda8d425adb6544e5903f1fb6c273ab3128a355c6b972b7df39dcf"}, 1169 | {file = "pyrsistent-0.19.3-cp310-cp310-win32.whl", hash = "sha256:5a474fb80f5e0d6c9394d8db0fc19e90fa540b82ee52dba7d246a7791712f74a"}, 1170 | {file = "pyrsistent-0.19.3-cp310-cp310-win_amd64.whl", hash = "sha256:49c32f216c17148695ca0e02a5c521e28a4ee6c5089f97e34fe24163113722da"}, 1171 | {file = "pyrsistent-0.19.3-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:f0774bf48631f3a20471dd7c5989657b639fd2d285b861237ea9e82c36a415a9"}, 1172 | {file = "pyrsistent-0.19.3-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3ab2204234c0ecd8b9368dbd6a53e83c3d4f3cab10ecaf6d0e772f456c442393"}, 1173 | {file = "pyrsistent-0.19.3-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e42296a09e83028b3476f7073fcb69ffebac0e66dbbfd1bd847d61f74db30f19"}, 1174 | {file = "pyrsistent-0.19.3-cp311-cp311-win32.whl", hash = "sha256:64220c429e42a7150f4bfd280f6f4bb2850f95956bde93c6fda1b70507af6ef3"}, 1175 | {file = "pyrsistent-0.19.3-cp311-cp311-win_amd64.whl", hash = "sha256:016ad1afadf318eb7911baa24b049909f7f3bb2c5b1ed7b6a8f21db21ea3faa8"}, 1176 | {file = "pyrsistent-0.19.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c4db1bd596fefd66b296a3d5d943c94f4fac5bcd13e99bffe2ba6a759d959a28"}, 1177 | {file = "pyrsistent-0.19.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aeda827381f5e5d65cced3024126529ddc4289d944f75e090572c77ceb19adbf"}, 1178 | {file = "pyrsistent-0.19.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:42ac0b2f44607eb92ae88609eda931a4f0dfa03038c44c772e07f43e738bcac9"}, 1179 | {file = "pyrsistent-0.19.3-cp37-cp37m-win32.whl", hash = "sha256:e8f2b814a3dc6225964fa03d8582c6e0b6650d68a232df41e3cc1b66a5d2f8d1"}, 1180 | {file = "pyrsistent-0.19.3-cp37-cp37m-win_amd64.whl", hash = "sha256:c9bb60a40a0ab9aba40a59f68214eed5a29c6274c83b2cc206a359c4a89fa41b"}, 1181 | {file = "pyrsistent-0.19.3-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:a2471f3f8693101975b1ff85ffd19bb7ca7dd7c38f8a81701f67d6b4f97b87d8"}, 1182 | {file = "pyrsistent-0.19.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:cc5d149f31706762c1f8bda2e8c4f8fead6e80312e3692619a75301d3dbb819a"}, 1183 | {file = "pyrsistent-0.19.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3311cb4237a341aa52ab8448c27e3a9931e2ee09561ad150ba94e4cfd3fc888c"}, 1184 | {file = "pyrsistent-0.19.3-cp38-cp38-win32.whl", hash = "sha256:f0e7c4b2f77593871e918be000b96c8107da48444d57005b6a6bc61fb4331b2c"}, 1185 | {file = "pyrsistent-0.19.3-cp38-cp38-win_amd64.whl", hash = "sha256:c147257a92374fde8498491f53ffa8f4822cd70c0d85037e09028e478cababb7"}, 1186 | {file = "pyrsistent-0.19.3-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:b735e538f74ec31378f5a1e3886a26d2ca6351106b4dfde376a26fc32a044edc"}, 1187 | {file = "pyrsistent-0.19.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:99abb85579e2165bd8522f0c0138864da97847875ecbd45f3e7e2af569bfc6f2"}, 1188 | {file = "pyrsistent-0.19.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3a8cb235fa6d3fd7aae6a4f1429bbb1fec1577d978098da1252f0489937786f3"}, 1189 | {file = "pyrsistent-0.19.3-cp39-cp39-win32.whl", hash = "sha256:c74bed51f9b41c48366a286395c67f4e894374306b197e62810e0fdaf2364da2"}, 1190 | {file = "pyrsistent-0.19.3-cp39-cp39-win_amd64.whl", hash = "sha256:878433581fc23e906d947a6814336eee031a00e6defba224234169ae3d3d6a98"}, 1191 | {file = "pyrsistent-0.19.3-py3-none-any.whl", hash = "sha256:ccf0d6bd208f8111179f0c26fdf84ed7c3891982f2edaeae7422575f47e66b64"}, 1192 | {file = "pyrsistent-0.19.3.tar.gz", hash = "sha256:1a2994773706bbb4995c31a97bc94f1418314923bd1048c6d964837040376440"}, 1193 | ] 1194 | 1195 | [[package]] 1196 | name = "python-dateutil" 1197 | version = "2.8.2" 1198 | description = "Extensions to the standard Python datetime module" 1199 | optional = false 1200 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" 1201 | files = [ 1202 | {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, 1203 | {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, 1204 | ] 1205 | 1206 | [package.dependencies] 1207 | six = ">=1.5" 1208 | 1209 | [[package]] 1210 | name = "pywin32" 1211 | version = "306" 1212 | description = "Python for Window Extensions" 1213 | optional = false 1214 | python-versions = "*" 1215 | files = [ 1216 | {file = "pywin32-306-cp310-cp310-win32.whl", hash = "sha256:06d3420a5155ba65f0b72f2699b5bacf3109f36acbe8923765c22938a69dfc8d"}, 1217 | {file = "pywin32-306-cp310-cp310-win_amd64.whl", hash = "sha256:84f4471dbca1887ea3803d8848a1616429ac94a4a8d05f4bc9c5dcfd42ca99c8"}, 1218 | {file = "pywin32-306-cp311-cp311-win32.whl", hash = "sha256:e65028133d15b64d2ed8f06dd9fbc268352478d4f9289e69c190ecd6818b6407"}, 1219 | {file = "pywin32-306-cp311-cp311-win_amd64.whl", hash = "sha256:a7639f51c184c0272e93f244eb24dafca9b1855707d94c192d4a0b4c01e1100e"}, 1220 | {file = "pywin32-306-cp311-cp311-win_arm64.whl", hash = "sha256:70dba0c913d19f942a2db25217d9a1b726c278f483a919f1abfed79c9cf64d3a"}, 1221 | {file = "pywin32-306-cp312-cp312-win32.whl", hash = "sha256:383229d515657f4e3ed1343da8be101000562bf514591ff383ae940cad65458b"}, 1222 | {file = "pywin32-306-cp312-cp312-win_amd64.whl", hash = "sha256:37257794c1ad39ee9be652da0462dc2e394c8159dfd913a8a4e8eb6fd346da0e"}, 1223 | {file = "pywin32-306-cp312-cp312-win_arm64.whl", hash = "sha256:5821ec52f6d321aa59e2db7e0a35b997de60c201943557d108af9d4ae1ec7040"}, 1224 | {file = "pywin32-306-cp37-cp37m-win32.whl", hash = "sha256:1c73ea9a0d2283d889001998059f5eaaba3b6238f767c9cf2833b13e6a685f65"}, 1225 | {file = "pywin32-306-cp37-cp37m-win_amd64.whl", hash = "sha256:72c5f621542d7bdd4fdb716227be0dd3f8565c11b280be6315b06ace35487d36"}, 1226 | {file = "pywin32-306-cp38-cp38-win32.whl", hash = "sha256:e4c092e2589b5cf0d365849e73e02c391c1349958c5ac3e9d5ccb9a28e017b3a"}, 1227 | {file = "pywin32-306-cp38-cp38-win_amd64.whl", hash = "sha256:e8ac1ae3601bee6ca9f7cb4b5363bf1c0badb935ef243c4733ff9a393b1690c0"}, 1228 | {file = "pywin32-306-cp39-cp39-win32.whl", hash = "sha256:e25fd5b485b55ac9c057f67d94bc203f3f6595078d1fb3b458c9c28b7153a802"}, 1229 | {file = "pywin32-306-cp39-cp39-win_amd64.whl", hash = "sha256:39b61c15272833b5c329a2989999dcae836b1eed650252ab1b7bfbe1d59f30f4"}, 1230 | ] 1231 | 1232 | [[package]] 1233 | name = "pywinpty" 1234 | version = "2.0.10" 1235 | description = "Pseudo terminal support for Windows from Python." 1236 | optional = false 1237 | python-versions = ">=3.7" 1238 | files = [ 1239 | {file = "pywinpty-2.0.10-cp310-none-win_amd64.whl", hash = "sha256:4c7d06ad10f6e92bc850a467f26d98f4f30e73d2fe5926536308c6ae0566bc16"}, 1240 | {file = "pywinpty-2.0.10-cp311-none-win_amd64.whl", hash = "sha256:7ffbd66310b83e42028fc9df7746118978d94fba8c1ebf15a7c1275fdd80b28a"}, 1241 | {file = "pywinpty-2.0.10-cp37-none-win_amd64.whl", hash = "sha256:38cb924f2778b5751ef91a75febd114776b3af0ae411bc667be45dd84fc881d3"}, 1242 | {file = "pywinpty-2.0.10-cp38-none-win_amd64.whl", hash = "sha256:902d79444b29ad1833b8d5c3c9aabdfd428f4f068504430df18074007c8c0de8"}, 1243 | {file = "pywinpty-2.0.10-cp39-none-win_amd64.whl", hash = "sha256:3c46aef80dd50979aff93de199e4a00a8ee033ba7a03cadf0a91fed45f0c39d7"}, 1244 | {file = "pywinpty-2.0.10.tar.gz", hash = "sha256:cdbb5694cf8c7242c2ecfaca35c545d31fa5d5814c3d67a4e628f803f680ebea"}, 1245 | ] 1246 | 1247 | [[package]] 1248 | name = "pyzmq" 1249 | version = "24.0.1" 1250 | description = "Python bindings for 0MQ" 1251 | optional = false 1252 | python-versions = ">=3.6" 1253 | files = [ 1254 | {file = "pyzmq-24.0.1-cp310-cp310-macosx_10_15_universal2.whl", hash = "sha256:28b119ba97129d3001673a697b7cce47fe6de1f7255d104c2f01108a5179a066"}, 1255 | {file = "pyzmq-24.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:bcbebd369493d68162cddb74a9c1fcebd139dfbb7ddb23d8f8e43e6c87bac3a6"}, 1256 | {file = "pyzmq-24.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ae61446166983c663cee42c852ed63899e43e484abf080089f771df4b9d272ef"}, 1257 | {file = "pyzmq-24.0.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:87f7ac99b15270db8d53f28c3c7b968612993a90a5cf359da354efe96f5372b4"}, 1258 | {file = "pyzmq-24.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:9dca7c3956b03b7663fac4d150f5e6d4f6f38b2462c1e9afd83bcf7019f17913"}, 1259 | {file = "pyzmq-24.0.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:8c78bfe20d4c890cb5580a3b9290f700c570e167d4cdcc55feec07030297a5e3"}, 1260 | {file = "pyzmq-24.0.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:48f721f070726cd2a6e44f3c33f8ee4b24188e4b816e6dd8ba542c8c3bb5b246"}, 1261 | {file = "pyzmq-24.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:afe1f3bc486d0ce40abb0a0c9adb39aed3bbac36ebdc596487b0cceba55c21c1"}, 1262 | {file = "pyzmq-24.0.1-cp310-cp310-win32.whl", hash = "sha256:3e6192dbcefaaa52ed81be88525a54a445f4b4fe2fffcae7fe40ebb58bd06bfd"}, 1263 | {file = "pyzmq-24.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:86de64468cad9c6d269f32a6390e210ca5ada568c7a55de8e681ca3b897bb340"}, 1264 | {file = "pyzmq-24.0.1-cp311-cp311-macosx_10_15_universal2.whl", hash = "sha256:838812c65ed5f7c2bd11f7b098d2e5d01685a3f6d1f82849423b570bae698c00"}, 1265 | {file = "pyzmq-24.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:dfb992dbcd88d8254471760879d48fb20836d91baa90f181c957122f9592b3dc"}, 1266 | {file = "pyzmq-24.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7abddb2bd5489d30ffeb4b93a428130886c171b4d355ccd226e83254fcb6b9ef"}, 1267 | {file = "pyzmq-24.0.1-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:94010bd61bc168c103a5b3b0f56ed3b616688192db7cd5b1d626e49f28ff51b3"}, 1268 | {file = "pyzmq-24.0.1-cp311-cp311-manylinux_2_28_x86_64.whl", hash = "sha256:8242543c522d84d033fe79be04cb559b80d7eb98ad81b137ff7e0a9020f00ace"}, 1269 | {file = "pyzmq-24.0.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:ccb94342d13e3bf3ffa6e62f95b5e3f0bc6bfa94558cb37f4b3d09d6feb536ff"}, 1270 | {file = "pyzmq-24.0.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:6640f83df0ae4ae1104d4c62b77e9ef39be85ebe53f636388707d532bee2b7b8"}, 1271 | {file = "pyzmq-24.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:a180dbd5ea5d47c2d3b716d5c19cc3fb162d1c8db93b21a1295d69585bfddac1"}, 1272 | {file = "pyzmq-24.0.1-cp311-cp311-win32.whl", hash = "sha256:624321120f7e60336be8ec74a172ae7fba5c3ed5bf787cc85f7e9986c9e0ebc2"}, 1273 | {file = "pyzmq-24.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:1724117bae69e091309ffb8255412c4651d3f6355560d9af312d547f6c5bc8b8"}, 1274 | {file = "pyzmq-24.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:15975747462ec49fdc863af906bab87c43b2491403ab37a6d88410635786b0f4"}, 1275 | {file = "pyzmq-24.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b947e264f0e77d30dcbccbb00f49f900b204b922eb0c3a9f0afd61aaa1cedc3d"}, 1276 | {file = "pyzmq-24.0.1-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:0ec91f1bad66f3ee8c6deb65fa1fe418e8ad803efedd69c35f3b5502f43bd1dc"}, 1277 | {file = "pyzmq-24.0.1-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:db03704b3506455d86ec72c3358a779e9b1d07b61220dfb43702b7b668edcd0d"}, 1278 | {file = "pyzmq-24.0.1-cp36-cp36m-musllinux_1_1_aarch64.whl", hash = "sha256:e7e66b4e403c2836ac74f26c4b65d8ac0ca1eef41dfcac2d013b7482befaad83"}, 1279 | {file = "pyzmq-24.0.1-cp36-cp36m-musllinux_1_1_i686.whl", hash = "sha256:7a23ccc1083c260fa9685c93e3b170baba45aeed4b524deb3f426b0c40c11639"}, 1280 | {file = "pyzmq-24.0.1-cp36-cp36m-musllinux_1_1_x86_64.whl", hash = "sha256:fa0ae3275ef706c0309556061185dd0e4c4cd3b7d6f67ae617e4e677c7a41e2e"}, 1281 | {file = "pyzmq-24.0.1-cp36-cp36m-win32.whl", hash = "sha256:f01de4ec083daebf210531e2cca3bdb1608dbbbe00a9723e261d92087a1f6ebc"}, 1282 | {file = "pyzmq-24.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:de4217b9eb8b541cf2b7fde4401ce9d9a411cc0af85d410f9d6f4333f43640be"}, 1283 | {file = "pyzmq-24.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:78068e8678ca023594e4a0ab558905c1033b2d3e806a0ad9e3094e231e115a33"}, 1284 | {file = "pyzmq-24.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:77c2713faf25a953c69cf0f723d1b7dd83827b0834e6c41e3fb3bbc6765914a1"}, 1285 | {file = "pyzmq-24.0.1-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:8bb4af15f305056e95ca1bd086239b9ebc6ad55e9f49076d27d80027f72752f6"}, 1286 | {file = "pyzmq-24.0.1-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:0f14cffd32e9c4c73da66db97853a6aeceaac34acdc0fae9e5bbc9370281864c"}, 1287 | {file = "pyzmq-24.0.1-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:0108358dab8c6b27ff6b985c2af4b12665c1bc659648284153ee501000f5c107"}, 1288 | {file = "pyzmq-24.0.1-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:d66689e840e75221b0b290b0befa86f059fb35e1ee6443bce51516d4d61b6b99"}, 1289 | {file = "pyzmq-24.0.1-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ae08ac90aa8fa14caafc7a6251bd218bf6dac518b7bff09caaa5e781119ba3f2"}, 1290 | {file = "pyzmq-24.0.1-cp37-cp37m-win32.whl", hash = "sha256:8421aa8c9b45ea608c205db9e1c0c855c7e54d0e9c2c2f337ce024f6843cab3b"}, 1291 | {file = "pyzmq-24.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:54d8b9c5e288362ec8595c1d98666d36f2070fd0c2f76e2b3c60fbad9bd76227"}, 1292 | {file = "pyzmq-24.0.1-cp38-cp38-macosx_10_15_universal2.whl", hash = "sha256:acbd0a6d61cc954b9f535daaa9ec26b0a60a0d4353c5f7c1438ebc88a359a47e"}, 1293 | {file = "pyzmq-24.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:47b11a729d61a47df56346283a4a800fa379ae6a85870d5a2e1e4956c828eedc"}, 1294 | {file = "pyzmq-24.0.1-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:abe6eb10122f0d746a0d510c2039ae8edb27bc9af29f6d1b05a66cc2401353ff"}, 1295 | {file = "pyzmq-24.0.1-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:07bec1a1b22dacf718f2c0e71b49600bb6a31a88f06527dfd0b5aababe3fa3f7"}, 1296 | {file = "pyzmq-24.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f0d945a85b70da97ae86113faf9f1b9294efe66bd4a5d6f82f2676d567338b66"}, 1297 | {file = "pyzmq-24.0.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:1b7928bb7580736ffac5baf814097be342ba08d3cfdfb48e52773ec959572287"}, 1298 | {file = "pyzmq-24.0.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:b946da90dc2799bcafa682692c1d2139b2a96ec3c24fa9fc6f5b0da782675330"}, 1299 | {file = "pyzmq-24.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:c8840f064b1fb377cffd3efeaad2b190c14d4c8da02316dae07571252d20b31f"}, 1300 | {file = "pyzmq-24.0.1-cp38-cp38-win32.whl", hash = "sha256:4854f9edc5208f63f0841c0c667260ae8d6846cfa233c479e29fdc85d42ebd58"}, 1301 | {file = "pyzmq-24.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:42d4f97b9795a7aafa152a36fe2ad44549b83a743fd3e77011136def512e6c2a"}, 1302 | {file = "pyzmq-24.0.1-cp39-cp39-macosx_10_15_universal2.whl", hash = "sha256:52afb0ac962963fff30cf1be775bc51ae083ef4c1e354266ab20e5382057dd62"}, 1303 | {file = "pyzmq-24.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8bad8210ad4df68c44ff3685cca3cda448ee46e20d13edcff8909eba6ec01ca4"}, 1304 | {file = "pyzmq-24.0.1-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:dabf1a05318d95b1537fd61d9330ef4313ea1216eea128a17615038859da3b3b"}, 1305 | {file = "pyzmq-24.0.1-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:5bd3d7dfd9cd058eb68d9a905dec854f86649f64d4ddf21f3ec289341386c44b"}, 1306 | {file = "pyzmq-24.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8012bce6836d3f20a6c9599f81dfa945f433dab4dbd0c4917a6fb1f998ab33d"}, 1307 | {file = "pyzmq-24.0.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:c31805d2c8ade9b11feca4674eee2b9cce1fec3e8ddb7bbdd961a09dc76a80ea"}, 1308 | {file = "pyzmq-24.0.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:3104f4b084ad5d9c0cb87445cc8cfd96bba710bef4a66c2674910127044df209"}, 1309 | {file = "pyzmq-24.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:df0841f94928f8af9c7a1f0aaaffba1fb74607af023a152f59379c01c53aee58"}, 1310 | {file = "pyzmq-24.0.1-cp39-cp39-win32.whl", hash = "sha256:a435ef8a3bd95c8a2d316d6e0ff70d0db524f6037411652803e118871d703333"}, 1311 | {file = "pyzmq-24.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:2032d9cb994ce3b4cba2b8dfae08c7e25bc14ba484c770d4d3be33c27de8c45b"}, 1312 | {file = "pyzmq-24.0.1-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:bb5635c851eef3a7a54becde6da99485eecf7d068bd885ac8e6d173c4ecd68b0"}, 1313 | {file = "pyzmq-24.0.1-pp37-pypy37_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:83ea1a398f192957cb986d9206ce229efe0ee75e3c6635baff53ddf39bd718d5"}, 1314 | {file = "pyzmq-24.0.1-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:941fab0073f0a54dc33d1a0460cb04e0d85893cb0c5e1476c785000f8b359409"}, 1315 | {file = "pyzmq-24.0.1-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0e8f482c44ccb5884bf3f638f29bea0f8dc68c97e38b2061769c4cb697f6140d"}, 1316 | {file = "pyzmq-24.0.1-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:613010b5d17906c4367609e6f52e9a2595e35d5cc27d36ff3f1b6fa6e954d944"}, 1317 | {file = "pyzmq-24.0.1-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:65c94410b5a8355cfcf12fd600a313efee46ce96a09e911ea92cf2acf6708804"}, 1318 | {file = "pyzmq-24.0.1-pp38-pypy38_pp73-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:20e7eeb1166087db636c06cae04a1ef59298627f56fb17da10528ab52a14c87f"}, 1319 | {file = "pyzmq-24.0.1-pp38-pypy38_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:a2712aee7b3834ace51738c15d9ee152cc5a98dc7d57dd93300461b792ab7b43"}, 1320 | {file = "pyzmq-24.0.1-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1a7c280185c4da99e0cc06c63bdf91f5b0b71deb70d8717f0ab870a43e376db8"}, 1321 | {file = "pyzmq-24.0.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:858375573c9225cc8e5b49bfac846a77b696b8d5e815711b8d4ba3141e6e8879"}, 1322 | {file = "pyzmq-24.0.1-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:80093b595921eed1a2cead546a683b9e2ae7f4a4592bb2ab22f70d30174f003a"}, 1323 | {file = "pyzmq-24.0.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f3f3154fde2b1ff3aa7b4f9326347ebc89c8ef425ca1db8f665175e6d3bd42f"}, 1324 | {file = "pyzmq-24.0.1-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:abb756147314430bee5d10919b8493c0ccb109ddb7f5dfd2fcd7441266a25b75"}, 1325 | {file = "pyzmq-24.0.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:44e706bac34e9f50779cb8c39f10b53a4d15aebb97235643d3112ac20bd577b4"}, 1326 | {file = "pyzmq-24.0.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:687700f8371643916a1d2c61f3fdaa630407dd205c38afff936545d7b7466066"}, 1327 | {file = "pyzmq-24.0.1.tar.gz", hash = "sha256:216f5d7dbb67166759e59b0479bca82b8acf9bed6015b526b8eb10143fb08e77"}, 1328 | ] 1329 | 1330 | [package.dependencies] 1331 | cffi = {version = "*", markers = "implementation_name == \"pypy\""} 1332 | py = {version = "*", markers = "implementation_name == \"pypy\""} 1333 | 1334 | [[package]] 1335 | name = "qtconsole" 1336 | version = "5.4.4" 1337 | description = "Jupyter Qt console" 1338 | optional = false 1339 | python-versions = ">= 3.7" 1340 | files = [ 1341 | {file = "qtconsole-5.4.4-py3-none-any.whl", hash = "sha256:a3b69b868e041c2c698bdc75b0602f42e130ffb256d6efa48f9aa756c97672aa"}, 1342 | {file = "qtconsole-5.4.4.tar.gz", hash = "sha256:b7ffb53d74f23cee29f4cdb55dd6fabc8ec312d94f3c46ba38e1dde458693dfb"}, 1343 | ] 1344 | 1345 | [package.dependencies] 1346 | ipykernel = ">=4.1" 1347 | ipython-genutils = "*" 1348 | jupyter-client = ">=4.1" 1349 | jupyter-core = "*" 1350 | packaging = "*" 1351 | pygments = "*" 1352 | pyzmq = ">=17.1" 1353 | qtpy = ">=2.4.0" 1354 | traitlets = "<5.2.1 || >5.2.1,<5.2.2 || >5.2.2" 1355 | 1356 | [package.extras] 1357 | doc = ["Sphinx (>=1.3)"] 1358 | test = ["flaky", "pytest", "pytest-qt"] 1359 | 1360 | [[package]] 1361 | name = "qtpy" 1362 | version = "2.4.1" 1363 | description = "Provides an abstraction layer on top of the various Qt bindings (PyQt5/6 and PySide2/6)." 1364 | optional = false 1365 | python-versions = ">=3.7" 1366 | files = [ 1367 | {file = "QtPy-2.4.1-py3-none-any.whl", hash = "sha256:1c1d8c4fa2c884ae742b069151b0abe15b3f70491f3972698c683b8e38de839b"}, 1368 | {file = "QtPy-2.4.1.tar.gz", hash = "sha256:a5a15ffd519550a1361bdc56ffc07fda56a6af7292f17c7b395d4083af632987"}, 1369 | ] 1370 | 1371 | [package.dependencies] 1372 | packaging = "*" 1373 | 1374 | [package.extras] 1375 | test = ["pytest (>=6,!=7.0.0,!=7.0.1)", "pytest-cov (>=3.0.0)", "pytest-qt"] 1376 | 1377 | [[package]] 1378 | name = "send2trash" 1379 | version = "1.8.2" 1380 | description = "Send file to trash natively under Mac OS X, Windows and Linux" 1381 | optional = false 1382 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" 1383 | files = [ 1384 | {file = "Send2Trash-1.8.2-py3-none-any.whl", hash = "sha256:a384719d99c07ce1eefd6905d2decb6f8b7ed054025bb0e618919f945de4f679"}, 1385 | {file = "Send2Trash-1.8.2.tar.gz", hash = "sha256:c132d59fa44b9ca2b1699af5c86f57ce9f4c5eb56629d5d55fbb7a35f84e2312"}, 1386 | ] 1387 | 1388 | [package.extras] 1389 | nativelib = ["pyobjc-framework-Cocoa", "pywin32"] 1390 | objc = ["pyobjc-framework-Cocoa"] 1391 | win32 = ["pywin32"] 1392 | 1393 | [[package]] 1394 | name = "setuptools" 1395 | version = "68.0.0" 1396 | description = "Easily download, build, install, upgrade, and uninstall Python packages" 1397 | optional = false 1398 | python-versions = ">=3.7" 1399 | files = [ 1400 | {file = "setuptools-68.0.0-py3-none-any.whl", hash = "sha256:11e52c67415a381d10d6b462ced9cfb97066179f0e871399e006c4ab101fc85f"}, 1401 | {file = "setuptools-68.0.0.tar.gz", hash = "sha256:baf1fdb41c6da4cd2eae722e135500da913332ab3f2f5c7d33af9b492acb5235"}, 1402 | ] 1403 | 1404 | [package.extras] 1405 | docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-hoverxref (<2)", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (==0.8.3)", "sphinx-reredirects", "sphinxcontrib-towncrier"] 1406 | testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pip-run (>=8.8)", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] 1407 | testing-integration = ["build[virtualenv]", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] 1408 | 1409 | [[package]] 1410 | name = "six" 1411 | version = "1.16.0" 1412 | description = "Python 2 and 3 compatibility utilities" 1413 | optional = false 1414 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" 1415 | files = [ 1416 | {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, 1417 | {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, 1418 | ] 1419 | 1420 | [[package]] 1421 | name = "sniffio" 1422 | version = "1.3.0" 1423 | description = "Sniff out which async library your code is running under" 1424 | optional = false 1425 | python-versions = ">=3.7" 1426 | files = [ 1427 | {file = "sniffio-1.3.0-py3-none-any.whl", hash = "sha256:eecefdce1e5bbfb7ad2eeaabf7c1eeb404d7757c379bd1f7e5cce9d8bf425384"}, 1428 | {file = "sniffio-1.3.0.tar.gz", hash = "sha256:e60305c5e5d314f5389259b7f22aaa33d8f7dee49763119234af3755c55b9101"}, 1429 | ] 1430 | 1431 | [[package]] 1432 | name = "soupsieve" 1433 | version = "2.4.1" 1434 | description = "A modern CSS selector implementation for Beautiful Soup." 1435 | optional = false 1436 | python-versions = ">=3.7" 1437 | files = [ 1438 | {file = "soupsieve-2.4.1-py3-none-any.whl", hash = "sha256:1c1bfee6819544a3447586c889157365a27e10d88cde3ad3da0cf0ddf646feb8"}, 1439 | {file = "soupsieve-2.4.1.tar.gz", hash = "sha256:89d12b2d5dfcd2c9e8c22326da9d9aa9cb3dfab0a83a024f05704076ee8d35ea"}, 1440 | ] 1441 | 1442 | [[package]] 1443 | name = "terminado" 1444 | version = "0.17.1" 1445 | description = "Tornado websocket backend for the Xterm.js Javascript terminal emulator library." 1446 | optional = false 1447 | python-versions = ">=3.7" 1448 | files = [ 1449 | {file = "terminado-0.17.1-py3-none-any.whl", hash = "sha256:8650d44334eba354dd591129ca3124a6ba42c3d5b70df5051b6921d506fdaeae"}, 1450 | {file = "terminado-0.17.1.tar.gz", hash = "sha256:6ccbbcd3a4f8a25a5ec04991f39a0b8db52dfcd487ea0e578d977e6752380333"}, 1451 | ] 1452 | 1453 | [package.dependencies] 1454 | ptyprocess = {version = "*", markers = "os_name != \"nt\""} 1455 | pywinpty = {version = ">=1.1.0", markers = "os_name == \"nt\""} 1456 | tornado = ">=6.1.0" 1457 | 1458 | [package.extras] 1459 | docs = ["myst-parser", "pydata-sphinx-theme", "sphinx"] 1460 | test = ["pre-commit", "pytest (>=7.0)", "pytest-timeout"] 1461 | 1462 | [[package]] 1463 | name = "tinycss2" 1464 | version = "1.2.1" 1465 | description = "A tiny CSS parser" 1466 | optional = false 1467 | python-versions = ">=3.7" 1468 | files = [ 1469 | {file = "tinycss2-1.2.1-py3-none-any.whl", hash = "sha256:2b80a96d41e7c3914b8cda8bc7f705a4d9c49275616e886103dd839dfc847847"}, 1470 | {file = "tinycss2-1.2.1.tar.gz", hash = "sha256:8cff3a8f066c2ec677c06dbc7b45619804a6938478d9d73c284b29d14ecb0627"}, 1471 | ] 1472 | 1473 | [package.dependencies] 1474 | webencodings = ">=0.4" 1475 | 1476 | [package.extras] 1477 | doc = ["sphinx", "sphinx_rtd_theme"] 1478 | test = ["flake8", "isort", "pytest"] 1479 | 1480 | [[package]] 1481 | name = "tornado" 1482 | version = "6.2" 1483 | description = "Tornado is a Python web framework and asynchronous networking library, originally developed at FriendFeed." 1484 | optional = false 1485 | python-versions = ">= 3.7" 1486 | files = [ 1487 | {file = "tornado-6.2-cp37-abi3-macosx_10_9_universal2.whl", hash = "sha256:20f638fd8cc85f3cbae3c732326e96addff0a15e22d80f049e00121651e82e72"}, 1488 | {file = "tornado-6.2-cp37-abi3-macosx_10_9_x86_64.whl", hash = "sha256:87dcafae3e884462f90c90ecc200defe5e580a7fbbb4365eda7c7c1eb809ebc9"}, 1489 | {file = "tornado-6.2-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ba09ef14ca9893954244fd872798b4ccb2367c165946ce2dd7376aebdde8e3ac"}, 1490 | {file = "tornado-6.2-cp37-abi3-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b8150f721c101abdef99073bf66d3903e292d851bee51910839831caba341a75"}, 1491 | {file = "tornado-6.2-cp37-abi3-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d3a2f5999215a3a06a4fc218026cd84c61b8b2b40ac5296a6db1f1451ef04c1e"}, 1492 | {file = "tornado-6.2-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:5f8c52d219d4995388119af7ccaa0bcec289535747620116a58d830e7c25d8a8"}, 1493 | {file = "tornado-6.2-cp37-abi3-musllinux_1_1_i686.whl", hash = "sha256:6fdfabffd8dfcb6cf887428849d30cf19a3ea34c2c248461e1f7d718ad30b66b"}, 1494 | {file = "tornado-6.2-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:1d54d13ab8414ed44de07efecb97d4ef7c39f7438cf5e976ccd356bebb1b5fca"}, 1495 | {file = "tornado-6.2-cp37-abi3-win32.whl", hash = "sha256:5c87076709343557ef8032934ce5f637dbb552efa7b21d08e89ae7619ed0eb23"}, 1496 | {file = "tornado-6.2-cp37-abi3-win_amd64.whl", hash = "sha256:e5f923aa6a47e133d1cf87d60700889d7eae68988704e20c75fb2d65677a8e4b"}, 1497 | {file = "tornado-6.2.tar.gz", hash = "sha256:9b630419bde84ec666bfd7ea0a4cb2a8a651c2d5cccdbdd1972a0c859dfc3c13"}, 1498 | ] 1499 | 1500 | [[package]] 1501 | name = "traitlets" 1502 | version = "5.9.0" 1503 | description = "Traitlets Python configuration system" 1504 | optional = false 1505 | python-versions = ">=3.7" 1506 | files = [ 1507 | {file = "traitlets-5.9.0-py3-none-any.whl", hash = "sha256:9e6ec080259b9a5940c797d58b613b5e31441c2257b87c2e795c5228ae80d2d8"}, 1508 | {file = "traitlets-5.9.0.tar.gz", hash = "sha256:f6cde21a9c68cf756af02035f72d5a723bf607e862e7be33ece505abf4a3bad9"}, 1509 | ] 1510 | 1511 | [package.extras] 1512 | docs = ["myst-parser", "pydata-sphinx-theme", "sphinx"] 1513 | test = ["argcomplete (>=2.0)", "pre-commit", "pytest", "pytest-mock"] 1514 | 1515 | [[package]] 1516 | name = "typing-extensions" 1517 | version = "4.7.1" 1518 | description = "Backported and Experimental Type Hints for Python 3.7+" 1519 | optional = false 1520 | python-versions = ">=3.7" 1521 | files = [ 1522 | {file = "typing_extensions-4.7.1-py3-none-any.whl", hash = "sha256:440d5dd3af93b060174bf433bccd69b0babc3b15b1a8dca43789fd7f61514b36"}, 1523 | {file = "typing_extensions-4.7.1.tar.gz", hash = "sha256:b75ddc264f0ba5615db7ba217daeb99701ad295353c45f9e95963337ceeeffb2"}, 1524 | ] 1525 | 1526 | [[package]] 1527 | name = "wcwidth" 1528 | version = "0.2.13" 1529 | description = "Measures the displayed width of unicode strings in a terminal" 1530 | optional = false 1531 | python-versions = "*" 1532 | files = [ 1533 | {file = "wcwidth-0.2.13-py2.py3-none-any.whl", hash = "sha256:3da69048e4540d84af32131829ff948f1e022c1c6bdb8d6102117aac784f6859"}, 1534 | {file = "wcwidth-0.2.13.tar.gz", hash = "sha256:72ea0c06399eb286d978fdedb6923a9eb47e1c486ce63e9b4e64fc18303972b5"}, 1535 | ] 1536 | 1537 | [[package]] 1538 | name = "webencodings" 1539 | version = "0.5.1" 1540 | description = "Character encoding aliases for legacy web content" 1541 | optional = false 1542 | python-versions = "*" 1543 | files = [ 1544 | {file = "webencodings-0.5.1-py2.py3-none-any.whl", hash = "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78"}, 1545 | {file = "webencodings-0.5.1.tar.gz", hash = "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923"}, 1546 | ] 1547 | 1548 | [[package]] 1549 | name = "websocket-client" 1550 | version = "1.6.1" 1551 | description = "WebSocket client for Python with low level API options" 1552 | optional = false 1553 | python-versions = ">=3.7" 1554 | files = [ 1555 | {file = "websocket-client-1.6.1.tar.gz", hash = "sha256:c951af98631d24f8df89ab1019fc365f2227c0892f12fd150e935607c79dd0dd"}, 1556 | {file = "websocket_client-1.6.1-py3-none-any.whl", hash = "sha256:f1f9f2ad5291f0225a49efad77abf9e700b6fef553900623060dad6e26503b9d"}, 1557 | ] 1558 | 1559 | [package.extras] 1560 | docs = ["Sphinx (>=3.4)", "sphinx-rtd-theme (>=0.5)"] 1561 | optional = ["python-socks", "wsaccel"] 1562 | test = ["websockets"] 1563 | 1564 | [[package]] 1565 | name = "widgetsnbextension" 1566 | version = "4.0.9" 1567 | description = "Jupyter interactive widgets for Jupyter Notebook" 1568 | optional = false 1569 | python-versions = ">=3.7" 1570 | files = [ 1571 | {file = "widgetsnbextension-4.0.9-py3-none-any.whl", hash = "sha256:91452ca8445beb805792f206e560c1769284267a30ceb1cec9f5bcc887d15175"}, 1572 | {file = "widgetsnbextension-4.0.9.tar.gz", hash = "sha256:3c1f5e46dc1166dfd40a42d685e6a51396fd34ff878742a3e47c6f0cc4a2a385"}, 1573 | ] 1574 | 1575 | [[package]] 1576 | name = "zipp" 1577 | version = "3.15.0" 1578 | description = "Backport of pathlib-compatible object wrapper for zip files" 1579 | optional = false 1580 | python-versions = ">=3.7" 1581 | files = [ 1582 | {file = "zipp-3.15.0-py3-none-any.whl", hash = "sha256:48904fc76a60e542af151aded95726c1a5c34ed43ab4134b597665c86d7ad556"}, 1583 | {file = "zipp-3.15.0.tar.gz", hash = "sha256:112929ad649da941c23de50f356a2b5570c954b65150642bccdd66bf194d224b"}, 1584 | ] 1585 | 1586 | [package.extras] 1587 | docs = ["furo", "jaraco.packaging (>=9)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] 1588 | testing = ["big-O", "flake8 (<5)", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-black (>=0.3.7)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=1.3)", "pytest-flake8", "pytest-mypy (>=0.9.1)"] 1589 | 1590 | [metadata] 1591 | lock-version = "2.0" 1592 | python-versions = "^3.7" 1593 | content-hash = "453446dbf56500d60f2d8c45401706668a308b8ae3842af493371c38a22bcd3b" 1594 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "valley" 3 | version = "1.7.1" 4 | description = "Python extensible schema validations and declarative syntax helpers." 5 | authors = ["Brian Jinwright"] 6 | license = "Apache-2.0" 7 | readme = "README.md" 8 | homepage = "https://github.com/capless/valley" 9 | repository = "https://github.com/capless/valley" 10 | classifiers = [ 11 | "Programming Language :: Python", 12 | "Topic :: Software Development :: Libraries :: Python Modules", 13 | "Environment :: Web Environment", 14 | ] 15 | keywords=['validations,schema,declarative'] 16 | 17 | [tool.poetry.dependencies] 18 | python = "^3.7" 19 | envs = "^1.4" 20 | 21 | [tool.poetry.dev-dependencies] 22 | jupyter = "^1.0.0" 23 | 24 | [build-system] 25 | requires = ["poetry>=0.12"] 26 | build-backend = "poetry.masonry.api" -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /valley/__init__.py: -------------------------------------------------------------------------------- 1 | from .properties import * 2 | from .schema import Schema -------------------------------------------------------------------------------- /valley/declarative.py: -------------------------------------------------------------------------------- 1 | import collections 2 | from typing import Any, Dict, List, Type 3 | 4 | class DeclaredVars(object): 5 | """ 6 | A class to handle declared variables in a declarative manner. 7 | 8 | Attributes: 9 | base_field_class (Type[Any]): The base class type for field variables. 10 | """ 11 | base_field_class: Type[Any] = None 12 | 13 | def get_base_fields(self, bases: tuple, attrs: Dict[str, Any]) -> Dict[str, Any]: 14 | """ 15 | Collects and returns base fields from the given attributes and bases. 16 | 17 | Args: 18 | bases (tuple): A tuple of base classes. 19 | attrs (Dict[str, Any]): A dictionary of attributes. 20 | 21 | Returns: 22 | Dict[str, Any]: A dictionary of base field properties. 23 | """ 24 | # Collect keys to move to properties without mutating attrs during iteration 25 | keys_to_move: List[str] = [name for name, obj in attrs.items() if isinstance(obj, self.base_field_class)] 26 | 27 | # Safely populate properties without mutating attrs during iteration 28 | properties: Dict[str, Any] = {name: attrs.pop(name) for name in keys_to_move} 29 | 30 | # Loop over bases and update properties 31 | for base in bases: 32 | base_properties: Dict[str, Any] = getattr(base, '_base_properties', {}) 33 | if base_properties: 34 | properties.update(base_properties) 35 | 36 | return properties 37 | 38 | 39 | class DeclarativeVariablesMetaclass(type): 40 | """ 41 | A metaclass for handling declarative variables. 42 | 43 | Attributes: 44 | declared_vars_class (Type[DeclaredVars]): The declared variables class. 45 | """ 46 | declared_vars_class: Type[DeclaredVars] = None 47 | 48 | def __new__(cls, name: str, bases: tuple, attrs: Dict[str, Any]) -> Type: 49 | """ 50 | Creates a new class instance with base properties. 51 | 52 | Args: 53 | name (str): The name of the class. 54 | bases (tuple): A tuple of base classes. 55 | attrs (Dict[str, Any]): A dictionary of attributes. 56 | 57 | Returns: 58 | Type: The newly created class. 59 | """ 60 | attrs['_base_properties'] = cls.declared_vars_class().get_base_fields(bases, attrs) 61 | new_class: Type = super(DeclarativeVariablesMetaclass, cls).__new__(cls, name, bases, attrs) 62 | return new_class 63 | 64 | @classmethod 65 | def __prepare__(mcls, cls: str, bases: tuple) -> collections.OrderedDict: 66 | """ 67 | Prepares the class namespace. 68 | 69 | Args: 70 | cls (str): The name of the class. 71 | bases (tuple): A tuple of base classes. 72 | 73 | Returns: 74 | collections.OrderedDict: An ordered dictionary for the class namespace. 75 | """ 76 | return collections.OrderedDict() 77 | -------------------------------------------------------------------------------- /valley/exceptions.py: -------------------------------------------------------------------------------- 1 | class ValidationException(Exception): 2 | """ 3 | Exception raised when a validation error occurs. 4 | """ 5 | 6 | def __init__(self, msg): 7 | self.error_msg = msg 8 | 9 | def __str__(self): 10 | return self.error_msg 11 | -------------------------------------------------------------------------------- /valley/properties.py: -------------------------------------------------------------------------------- 1 | import json 2 | from collections.abc import Callable 3 | from typing import Any, Optional, Type, List, Dict 4 | 5 | from valley.utils.json_utils import ValleyEncoder 6 | from .validators import ( 7 | RequiredValidator, StringValidator, MaxLengthValidator, MinLengthValidator, 8 | IntegerValidator, MaxValueValidator, MinValueValidator, FloatValidator, 9 | DateValidator, DateTimeValidator, BooleanValidator, SlugValidator, 10 | EmailValidator, DictValidator, ChoiceValidator, ListValidator, 11 | ForeignValidator, ForeignListValidator, MultiValidator 12 | ) 13 | 14 | __all__ = [ 15 | 'BaseProperty', 'StringProperty', 'IntegerProperty', 'FloatProperty', 16 | 'BooleanProperty', 'DateProperty', 'DateTimeProperty', 'SlugProperty', 17 | 'EmailProperty', 'DictProperty', 'ListProperty', 'ForeignProperty', 18 | 'ForeignListProperty', 'MultiProperty' 19 | ] 20 | 21 | class BaseProperty: 22 | """ 23 | Base class for defining properties in the Valley library. 24 | 25 | Attributes: 26 | default_value (Any): The default value for the property. 27 | required (bool): Indicates whether the property is required. 28 | validators (List[Callable]): A list of validators for the property. 29 | choices (Optional[List[Any]]): A list of choices for the property value. 30 | kwargs (dict): Additional keyword arguments. 31 | 32 | """ 33 | default_value: Any = None 34 | allow_required: bool = True 35 | 36 | def __init__(self, default_value: Any = None, required: bool = False, 37 | validators: Optional[List[Callable]] = None, 38 | choices: Optional[Dict[str, Any]] = None, **kwargs): 39 | self.default_value = default_value 40 | self.required = required 41 | self.validators = validators if validators is not None else [] 42 | self.choices = choices 43 | self.kwargs = kwargs 44 | self.get_validators() 45 | 46 | def get_validators(self) -> None: 47 | """ 48 | Initialize the validators for the property based on its configuration. 49 | """ 50 | if self.required: 51 | self.validators.insert(0, RequiredValidator()) 52 | if self.choices: 53 | self.validators.insert(0, ChoiceValidator(self.choices)) 54 | 55 | def validate(self, value: Any, key: str) -> None: 56 | """ 57 | Validate the value of the property. 58 | 59 | Args: 60 | value (Any): The value to be validated. 61 | key (str): The key associated with the property. 62 | 63 | Raises: 64 | ValidationException: If the value does not pass the validation checks. 65 | """ 66 | if not value and not isinstance(self.get_default_value(), type(None)): 67 | value = self.get_default_value() 68 | for validator in self.validators: 69 | validator.validate(value, key) 70 | 71 | def get_default_value(self) -> Any: 72 | """ 73 | Get the default value of the property. 74 | 75 | Returns: 76 | Any: The default value. 77 | """ 78 | default = self.default_value 79 | if isinstance(default, Callable): 80 | default = default() 81 | return default 82 | 83 | def get_db_value(self, value: Any) -> Any: 84 | """ 85 | Get the database value of the property. 86 | 87 | Args: 88 | value (Any): The value to be converted. 89 | 90 | Returns: 91 | Any: The converted value suitable for database storage. 92 | """ 93 | if not value: 94 | return 95 | return value 96 | 97 | def get_python_value(self, value: Any) -> Any: 98 | """ 99 | Get the Python value of the property. 100 | 101 | Args: 102 | value (Any): The value to be converted. 103 | 104 | Returns: 105 | Any: The converted value suitable for Python. 106 | """ 107 | return value 108 | 109 | 110 | class StringProperty(BaseProperty): 111 | """ 112 | A property that represents a string value. 113 | 114 | Inherits from BaseProperty and adds string-specific validators. 115 | """ 116 | 117 | def get_validators(self) -> None: 118 | """ 119 | Initialize the validators for the CharProperty. 120 | """ 121 | super().get_validators() 122 | self.validators.append(StringValidator()) 123 | if 'min_length' in self.kwargs: 124 | self.validators.append(MinLengthValidator(self.kwargs['min_length'])) 125 | if 'max_length' in self.kwargs: 126 | self.validators.append(MaxLengthValidator(self.kwargs['max_length'])) 127 | 128 | def get_python_value(self, value: Any) -> Optional[str]: 129 | """ 130 | Convert the value to a string if it is not None. 131 | 132 | Args: 133 | value (Any): The value to be converted. 134 | 135 | Returns: 136 | Optional[str]: The converted value as a string or None. 137 | """ 138 | if value is not None: 139 | return str(value) 140 | return value 141 | 142 | 143 | class IntegerProperty(BaseProperty): 144 | """ 145 | A property that represents an integer value. 146 | 147 | Inherits from BaseProperty and adds integer-specific validators. 148 | """ 149 | 150 | def get_validators(self) -> None: 151 | """ 152 | Initialize the validators for the IntegerProperty. 153 | """ 154 | super().get_validators() 155 | self.validators.append(IntegerValidator()) 156 | if 'min_value' in self.kwargs: 157 | self.validators.append(MinValueValidator(self.kwargs['min_value'])) 158 | if 'max_value' in self.kwargs: 159 | self.validators.append(MaxValueValidator(self.kwargs['max_value'])) 160 | 161 | 162 | class FloatProperty(BaseProperty): 163 | """ 164 | A property that represents a floating-point value. 165 | 166 | Inherits from BaseProperty and adds float-specific validators. 167 | """ 168 | 169 | def get_validators(self) -> None: 170 | """ 171 | Initialize the validators for the FloatProperty. 172 | """ 173 | super().get_validators() 174 | self.validators.append(FloatValidator()) 175 | 176 | 177 | class BooleanProperty(BaseProperty): 178 | """ 179 | A property that represents a boolean value. 180 | 181 | Inherits from BaseProperty and adds boolean-specific validators. 182 | """ 183 | 184 | def get_validators(self) -> None: 185 | """ 186 | Initialize the validators for the BooleanProperty. 187 | """ 188 | super().get_validators() 189 | self.validators.append(BooleanValidator()) 190 | 191 | def get_default_value(self) -> bool: 192 | """ 193 | Get the default value of the BooleanProperty. 194 | 195 | Returns: 196 | bool: The default boolean value. 197 | """ 198 | default = self.default_value 199 | return bool(default) 200 | 201 | 202 | class DateProperty(BaseProperty): 203 | """ 204 | A property that represents a date value. 205 | 206 | Inherits from BaseProperty and adds date-specific validators. 207 | """ 208 | 209 | def get_validators(self) -> None: 210 | """ 211 | Initialize the validators for the DateProperty. 212 | """ 213 | super().get_validators() 214 | self.validators.append(DateValidator()) 215 | 216 | 217 | class DateTimeProperty(BaseProperty): 218 | """ 219 | A property that represents a datetime value. 220 | 221 | Inherits from BaseProperty and adds datetime-specific validators. 222 | """ 223 | 224 | def get_validators(self) -> None: 225 | """ 226 | Initialize the validators for the DateTimeProperty. 227 | """ 228 | super().get_validators() 229 | self.validators.append(DateTimeValidator()) 230 | 231 | 232 | class SlugProperty(StringProperty): 233 | """ 234 | A property that represents a slug (URL-friendly string). 235 | 236 | Inherits from CharProperty and adds slug-specific validators. 237 | """ 238 | 239 | def get_validators(self) -> None: 240 | """ 241 | Initialize the validators for the SlugProperty. 242 | """ 243 | super().get_validators() 244 | self.validators.append(SlugValidator()) 245 | 246 | def get_python_value(self, value: Any) -> Optional[str]: 247 | """ 248 | Convert the value to a slug string if it is not None. 249 | 250 | Args: 251 | value (Any): The value to be converted. 252 | 253 | Returns: 254 | Optional[str]: The converted value as a slug string or None. 255 | """ 256 | if value is not None: 257 | return str(value) 258 | return value 259 | 260 | 261 | class EmailProperty(BaseProperty): 262 | """ 263 | A property that represents an email address. 264 | 265 | Inherits from BaseProperty and adds email-specific validators. 266 | """ 267 | 268 | def get_validators(self) -> None: 269 | """ 270 | Initialize the validators for the EmailProperty. 271 | """ 272 | super().get_validators() 273 | self.validators.append(EmailValidator()) 274 | 275 | 276 | class DictProperty(BaseProperty): 277 | """ 278 | A property that represents a dictionary. 279 | 280 | Inherits from BaseProperty and adds dictionary-specific validators. 281 | """ 282 | 283 | def get_validators(self) -> None: 284 | """ 285 | Initialize the validators for the DictProperty. 286 | """ 287 | super().get_validators() 288 | self.validators.append(DictValidator()) 289 | 290 | 291 | class ListProperty(BaseProperty): 292 | """ 293 | A property that represents a list. 294 | 295 | Inherits from BaseProperty and adds list-specific validators. 296 | """ 297 | 298 | def get_validators(self) -> None: 299 | """ 300 | Initialize the validators for the ListProperty. 301 | """ 302 | super().get_validators() 303 | self.validators.append(ListValidator()) 304 | 305 | 306 | class ForeignProperty(BaseProperty): 307 | def __init__(self, foreign_class: Type, return_type: str = 'single', return_prop: Optional[str] = None, 308 | **kwargs: Any): 309 | """ 310 | Initialize a ForeignProperty. 311 | 312 | Args: 313 | foreign_class (Type): The class of the foreign object. 314 | return_type (str, optional): The type of return value. Defaults to 'single'. 315 | return_prop (Optional[str], optional): The property name to return. Required if return_type is 'single'. Defaults to None. 316 | **kwargs: Additional keyword arguments. 317 | """ 318 | self.foreign_class = foreign_class 319 | self.return_type = return_type 320 | self.return_prop = return_prop 321 | super().__init__(**kwargs) 322 | 323 | def get_validators(self) -> None: 324 | """ 325 | Get validators for the foreign property, adding ForeignValidator to the list. 326 | """ 327 | super().get_validators() 328 | self.validators.insert(0, ForeignValidator(self.foreign_class)) 329 | 330 | def get_db_value(self, value: Any) -> Any: 331 | """ 332 | Get the database value for the property. 333 | 334 | Args: 335 | value (Any): The value to be processed. 336 | 337 | Returns: 338 | Any: The processed value. 339 | """ 340 | if not value: 341 | return None 342 | if self.return_type == 'single': 343 | if not self.return_prop: 344 | raise ValueError('ForeignProperty requires the return_prop argument if return_type is "single"') 345 | return value._data[self.return_prop] 346 | if self.return_type == 'dict': 347 | return value._data 348 | if self.return_type == 'json': 349 | return json.dumps(value, cls=ValleyEncoder) 350 | else: 351 | return value 352 | 353 | 354 | class ForeignListProperty(ListProperty): 355 | def __init__(self, foreign_class: Type, **kwargs: Any): 356 | """ 357 | Initialize a ForeignListProperty. 358 | 359 | Args: 360 | foreign_class (Type): The class of the foreign object. 361 | **kwargs: Additional keyword arguments. 362 | """ 363 | self.foreign_class = foreign_class 364 | super().__init__(**kwargs) 365 | 366 | def get_validators(self) -> None: 367 | """ 368 | Get validators for the foreign list property, adding ForeignListValidator to the list. 369 | """ 370 | super().get_validators() 371 | self.validators.insert(len(self.validators), ForeignListValidator(self.foreign_class)) 372 | 373 | def get_db_value(self, value: Any) -> Any: 374 | """ 375 | Get the database value for the list property. 376 | 377 | Args: 378 | value (Any): The value to be processed. 379 | 380 | Returns: 381 | Any: The processed value. 382 | """ 383 | if not value: 384 | return None 385 | if self.return_type == 'list': 386 | return [obj._data for obj in value] 387 | if self.return_type == 'json': 388 | return json.dumps(value, cls=ValleyEncoder) 389 | else: 390 | return value 391 | 392 | 393 | class MultiProperty(BaseProperty): 394 | def get_validators(self) -> None: 395 | """ 396 | Get validators for the multi property, encapsulating existing validators in a MultiValidator. 397 | """ 398 | super().get_validators() 399 | self.validators = [MultiValidator(self.validators)] 400 | -------------------------------------------------------------------------------- /valley/schema.py: -------------------------------------------------------------------------------- 1 | import json 2 | from typing import Any, Dict 3 | 4 | from valley.declarative import DeclaredVars as DV, \ 5 | DeclarativeVariablesMetaclass as DVM 6 | from valley.exceptions import ValidationException 7 | from valley.properties import BaseProperty 8 | 9 | 10 | class BaseSchema: 11 | """ 12 | Base class for all Valley Schema classes. 13 | 14 | This class provides the basic functionality for schema validation, data serialization, and attribute management. 15 | 16 | Attributes: 17 | _data (Dict[str, Any]): Stores the data associated with the schema's properties. 18 | _errors (Dict[str, str]): Stores any validation errors. 19 | _is_valid (bool): Indicates whether the schema is valid. 20 | cleaned_data (Dict[str, Any]): Stores the cleaned data after validation. 21 | """ 22 | 23 | def __init__(self, **kwargs: Any) -> None: 24 | """ 25 | Initializes a new instance of the BaseSchema. 26 | 27 | Args: 28 | **kwargs: Arbitrary keyword arguments that represent the schema properties. 29 | """ 30 | self._data: Dict[str, Any] = {} 31 | self._errors: Dict[str, str] = {} 32 | self._is_valid: bool = False 33 | self.cleaned_data: Dict[str, Any] = {} 34 | self._init_schema(kwargs) 35 | 36 | def _init_schema(self, kwargs: Dict[str, Any]) -> None: 37 | """ 38 | Initializes schema properties with provided values or default values. 39 | 40 | Args: 41 | kwargs (Dict[str, Any]): The keyword arguments for schema properties. 42 | """ 43 | for key, prop in self._base_properties.items(): 44 | value = kwargs.get(key, prop.get_default_value()) 45 | try: 46 | self._data[key] = prop.get_python_value(value) 47 | except ValueError: 48 | self._data[key] = value 49 | 50 | for i in self.BUILTIN_DOC_ATTRS: 51 | if i in kwargs: 52 | self._data[i] = kwargs[i] 53 | 54 | def __getattr__(self, name: str) -> Any: 55 | """ 56 | Provides dynamic access to schema properties. 57 | 58 | Args: 59 | name (str): The name of the attribute. 60 | 61 | Returns: 62 | Any: The value of the schema property. 63 | 64 | Raises: 65 | AttributeError: If the attribute is not a schema property. 66 | """ 67 | if name in self._base_properties: 68 | return self._base_properties[name].get_python_value(self._data.get(name)) 69 | raise AttributeError(f"'{self.__class__.__name__}' object has no attribute '{name}'") 70 | 71 | def __setattr__(self, name: str, value: Any) -> None: 72 | """ 73 | Sets the value for a schema property or a regular attribute. 74 | 75 | Args: 76 | name (str): The name of the attribute. 77 | value (Any): The value to set for the attribute. 78 | """ 79 | if name in self._base_properties: 80 | self._data[name] = value 81 | else: 82 | super().__setattr__(name, value) 83 | 84 | def validate(self) -> None: 85 | """ 86 | Validates the schema properties against their defined constraints. 87 | 88 | This method updates the _is_valid flag and populates the cleaned_data attribute. 89 | """ 90 | self._errors = {} 91 | data = self._data.copy() 92 | 93 | for key, prop in self._base_properties.items(): 94 | value = data.get(key) 95 | prop_validate = getattr(self, f'{key}_validate', None) 96 | 97 | try: 98 | prop.validate(value, key) 99 | if callable(prop_validate): 100 | prop_validate(value) 101 | except ValidationException as e: 102 | self._handle_validation_error(key, e) 103 | 104 | self._is_valid = not bool(self._errors) 105 | self.cleaned_data = data 106 | 107 | def _handle_validation_error(self, key: str, error: ValidationException) -> None: 108 | """ 109 | Handles validation errors either by raising them or storing them in the _errors dictionary. 110 | 111 | Args: 112 | key (str): The property key associated with the validation error. 113 | error (ValidationException): The validation exception raised during property validation. 114 | """ 115 | if self._create_error_dict: 116 | self._errors[key] = error.error_msg 117 | else: 118 | raise error 119 | 120 | def to_json(self) -> str: 121 | """ 122 | Serializes the schema data to a JSON string. 123 | 124 | Returns: 125 | str: A JSON string representation of the schema data. 126 | """ 127 | return json.dumps(self._data) 128 | 129 | def to_dict(self) -> Dict[str, Any]: 130 | """ 131 | Converts the schema data to a dictionary. 132 | 133 | Returns: 134 | Dict[str, Any]: A dictionary representation of the schema data. 135 | """ 136 | return self._data 137 | 138 | 139 | class DeclaredVars(DV): 140 | """ 141 | A class that stores the schema properties. 142 | 143 | Attributes: 144 | base_field_class (BaseProperty): The base field class for the schema. 145 | base_field_type (str): The name of the attribute that stores the schema properties. 146 | """ 147 | base_field_class = BaseProperty 148 | base_field_type = '_base_properties' 149 | 150 | 151 | class DeclarativeVariablesMetaclass(DVM): 152 | declared_vars_class = DeclaredVars 153 | 154 | 155 | class Schema(BaseSchema, metaclass=DeclarativeVariablesMetaclass): 156 | BUILTIN_DOC_ATTRS = [] 157 | -------------------------------------------------------------------------------- /valley/tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qwigo/valley/79a0337ab6c41a25c0bd64e1f023a73abfcbb9ab/valley/tests/__init__.py -------------------------------------------------------------------------------- /valley/tests/examples/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qwigo/valley/79a0337ab6c41a25c0bd64e1f023a73abfcbb9ab/valley/tests/examples/__init__.py -------------------------------------------------------------------------------- /valley/tests/examples/example_schemas.py: -------------------------------------------------------------------------------- 1 | import valley 2 | 3 | 4 | class Student(valley.Schema): 5 | _create_error_dict = True 6 | name = valley.StringProperty(required=True, min_length=5, max_length=20) 7 | slug = valley.SlugProperty(required=True, min_length=5, max_length=25) 8 | email = valley.EmailProperty(required=True) 9 | age = valley.IntegerProperty(min_value=5, max_value=18) 10 | gpa = valley.FloatProperty(min_value=0, max_value=4.5) 11 | date = valley.DateProperty(required=False) 12 | datetime = valley.DateTimeProperty(required=False) 13 | active = valley.BooleanProperty() 14 | 15 | 16 | class StudentB(Student): 17 | _create_error_dict = False 18 | 19 | 20 | class NameSchema(valley.Schema): 21 | _create_error_dict = True 22 | name = valley.StringProperty(required=True) 23 | 24 | def __unicode__(self): 25 | return self.name 26 | 27 | 28 | class Breed(NameSchema): 29 | pass 30 | 31 | 32 | class Food(NameSchema): 33 | pass 34 | 35 | 36 | class Dog(NameSchema): 37 | breed = valley.ForeignProperty(Breed, required=True) 38 | 39 | 40 | class Troop(NameSchema): 41 | dogs = valley.ForeignListProperty(Dog) 42 | primary_breed = valley.ForeignProperty(Breed) 43 | 44 | 45 | cocker = Breed(name='Cocker Spaniel') 46 | 47 | cockapoo = Breed(name='Cockapoo') 48 | 49 | bruno = Dog(name='Bruno', breed=cocker) 50 | 51 | blitz = Dog(name='Blitz', breed=cockapoo) 52 | 53 | durham = Troop(name='Durham', dogs=[bruno, blitz], primary_breed=cocker) 54 | -------------------------------------------------------------------------------- /valley/tests/test_declarative.py: -------------------------------------------------------------------------------- 1 | import collections 2 | import unittest 3 | 4 | from valley.schema import BaseSchema 5 | from valley.declarative import DeclaredVars, DeclarativeVariablesMetaclass 6 | from valley.properties import BaseProperty 7 | 8 | 9 | class TestField(BaseProperty): 10 | pass 11 | 12 | class TestDeclaredVars(DeclaredVars): 13 | base_field_class = TestField 14 | 15 | 16 | class TestDeclarativeVariablesMetaclass(DeclarativeVariablesMetaclass): 17 | declared_vars_class = TestDeclaredVars 18 | 19 | 20 | class TestClass(BaseSchema, metaclass=TestDeclarativeVariablesMetaclass): 21 | BUILTIN_DOC_ATTRS = [] 22 | field1 = TestField() 23 | field2 = TestField() 24 | 25 | class DeclarativeTests(unittest.TestCase): 26 | 27 | def test_declared_vars_base_fields(self): 28 | """Test if base fields are correctly identified and collected.""" 29 | declared_vars = TestDeclaredVars() 30 | bases = (TestClass,) 31 | attrs = {'field1': TestField(1), 'field2': TestField(2), 'not_a_field': 3} 32 | base_fields = declared_vars.get_base_fields(bases, attrs) 33 | 34 | self.assertIn('field1', base_fields) 35 | self.assertIn('field2', base_fields) 36 | self.assertNotIn('not_a_field', base_fields) 37 | 38 | def test_metaclass_new_instance(self): 39 | """Test if the metaclass correctly creates a new class instance.""" 40 | instance = TestClass() 41 | self.assertTrue(hasattr(instance, '_base_properties')) 42 | self.assertIn('field1', instance._base_properties) 43 | self.assertIn('field2', instance._base_properties) 44 | 45 | def test_metaclass_prepare_namespace(self): 46 | """Test if __prepare__ method of metaclass returns an OrderedDict.""" 47 | namespace = DeclarativeVariablesMetaclass.__prepare__('Test', (object,)) 48 | self.assertIsInstance(namespace, collections.OrderedDict) 49 | 50 | 51 | if __name__ == '__main__': 52 | unittest.main() 53 | -------------------------------------------------------------------------------- /valley/tests/test_schema.py: -------------------------------------------------------------------------------- 1 | import unittest 2 | 3 | from valley.exceptions import ValidationException 4 | from valley.tests.examples.example_schemas import StudentB, Student, Troop, bruno, blitz, cocker 5 | 6 | 7 | class SchemaTestCase(unittest.TestCase): 8 | 9 | def setUp(self): 10 | self.student = Student(name='Frank White', slug='frank-white', 11 | email='frank@white.com', age=18, 12 | gpa=3.0, date='2017-01-10', 13 | datetime='2017-01-10T12:00:00', active=False 14 | ) 15 | self.troop = Troop(name='Durham', dogs=[bruno, blitz], 16 | primary_breed=cocker) 17 | 18 | def test_valid(self): 19 | self.student.validate() 20 | self.assertEqual(True, self.student._is_valid) 21 | self.assertDictEqual(self.student._errors, {}) 22 | 23 | def test_foreign_valid(self): 24 | self.troop.validate() 25 | self.assertEqual(True, self.troop._is_valid) 26 | self.assertDictEqual(self.troop._errors, {}) 27 | 28 | def test_foreign_property_wrong_type(self): 29 | self.troop.primary_breed = 'Cocker' 30 | self.troop.validate() 31 | self.assertEqual(False, self.troop._is_valid) 32 | self.assertDictEqual({'primary_breed': 'primary_breed must be an instance of Breed.'}, 33 | self.troop._errors) 34 | 35 | def test_foreign_list_property_wrong_type(self): 36 | self.troop.dogs = ['Test', bruno, blitz] 37 | self.troop.validate() 38 | self.assertEqual(False, self.troop._is_valid) 39 | self.assertDictEqual({'dogs': 'All items in dogs must be instances of Dog.'}, 40 | self.troop._errors) 41 | 42 | def test_long_name(self): 43 | self.student.name = 'Frank Lindsay Hightower III' 44 | self.student.validate() 45 | self.assertEqual(False, self.student._is_valid) 46 | ed = {'name': 'name must not be longer than 20 characters.'} 47 | self.assertDictEqual(ed, self.student._errors) 48 | 49 | def test_short_name(self): 50 | self.student.name = 'Ira' 51 | self.student.validate() 52 | self.assertEqual(False, self.student._is_valid) 53 | ed = {'name': 'name must not be shorter than 5 characters.'} 54 | self.assertDictEqual(ed, self.student._errors) 55 | 56 | def test_no_name(self): 57 | self.student.name = None 58 | self.student.validate() 59 | self.assertEqual(False, self.student._is_valid) 60 | ed = {'name': 'name is required and cannot be empty.'} 61 | self.assertDictEqual(ed, self.student._errors) 62 | 63 | def test_slug_space(self): 64 | self.student.slug = 'Some City' 65 | self.student.validate() 66 | ed = {'slug': 'slug must be a valid slug (only letters, numbers, hyphens, and underscores).'} 67 | self.assertDictEqual(ed, self.student._errors) 68 | 69 | def test_email_space(self): 70 | self.student.email = 'Some City' 71 | self.student.validate() 72 | ed = {'email': 'email must be a valid email address.'} 73 | self.assertDictEqual(ed, self.student._errors) 74 | 75 | def test_email_wrong(self): 76 | self.student.email = 'e+ e@g.com' 77 | self.student.validate() 78 | ed = {'email': 'email must be a valid email address.'} 79 | self.assertDictEqual(ed, self.student._errors) 80 | 81 | def test_age_numeric_string(self): 82 | self.student.age = '5' 83 | self.student.validate() 84 | ed = {'age': 'age must be an integer.'} 85 | self.assertDictEqual(ed, self.student._errors) 86 | 87 | def test_age_numeric_float(self): 88 | self.student.age = 5.0 89 | self.student.validate() 90 | ed = {'age': 'age must be an integer.'} 91 | self.assertDictEqual(ed, self.student._errors) 92 | 93 | 94 | class SchemaMethods(unittest.TestCase): 95 | 96 | def setUp(self): 97 | attr_dict = { 98 | 'name': 'Frank White', 99 | 'slug': 'frank-white', 100 | 'email': 'frank@white.com', 101 | 'age': 18, 'gpa': 3.0, 102 | 'date': '2017-01-10', 103 | 'datetime': '2017-01-10T12:00:00' 104 | } 105 | self.student = Student(**attr_dict) 106 | self.studentb = StudentB(**attr_dict) 107 | 108 | def test_setattr(self): 109 | self.student.name = 'Curious George' 110 | self.assertEqual(self.student._data.get('name'), 'Curious George') 111 | 112 | def test_getattr(self): 113 | self.assertEqual(self.student.name, 'Frank White') 114 | 115 | def test_error_dict_false_validate(self): 116 | self.studentb.name = 1 117 | self.assertRaises(ValidationException, self.studentb.validate) 118 | 119 | 120 | if __name__ == '__main__': 121 | unittest.main() 122 | -------------------------------------------------------------------------------- /valley/tests/test_utils.py: -------------------------------------------------------------------------------- 1 | import json 2 | import unittest 3 | 4 | from valley.tests.examples.example_schemas import durham 5 | from valley.utils import import_util 6 | from valley.utils.json_utils import (ValleyEncoder, ValleyDecoder) 7 | 8 | 9 | class UtilTest(unittest.TestCase): 10 | json_string = '{"dogs": [{"breed": {"name": "Cocker Spaniel",' \ 11 | ' "_type": "valley.tests.examples.example_schemas.Breed"},' \ 12 | ' "name": "Bruno", "_type": "valley.tests.examples.' \ 13 | 'example_schemas.Dog"}, {"breed": {"name": "Cockapoo", ' \ 14 | '"_type": "valley.tests.examples.example_schemas.Breed"}' \ 15 | ', "name": "Blitz", "_type": ' \ 16 | '"valley.tests.examples.example_schemas.Dog"}], ' \ 17 | '"primary_breed": {"name": "Cocker Spaniel", ' \ 18 | '"_type": "valley.tests.examples.example_schemas.Breed"}, ' \ 19 | '"name": "Durham", "_type": ' \ 20 | '"valley.tests.examples.example_schemas.Troop"}' 21 | 22 | def test_import_util(self): 23 | klass = import_util('valley.properties.SlugProperty') 24 | self.assertEqual('SlugProperty', klass.__name__) 25 | 26 | def test_json_encoder(self): 27 | self.assertEqual(json.dumps(durham, cls=ValleyEncoder), self.json_string) 28 | 29 | def test_json_decoder(self): 30 | new_troop = json.loads(self.json_string, cls=ValleyDecoder) 31 | self.assertEqual(new_troop.name, durham.name) 32 | self.assertEqual(new_troop.primary_breed.name, durham.primary_breed.name) 33 | self.assertEqual(new_troop.dogs[0].name, durham.dogs[0].name) 34 | self.assertEqual(new_troop.dogs[1].name, durham.dogs[1].name) 35 | -------------------------------------------------------------------------------- /valley/tests/test_validators.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | import unittest 3 | 4 | from valley.validators import (RequiredValidator, DateTimeValidator, 5 | DateValidator, FloatValidator, IntegerValidator, 6 | MaxLengthValidator, MinLengthValidator, 7 | MaxValueValidator, MinValueValidator, 8 | StringValidator, ValidationException, 9 | BooleanValidator, DictValidator, 10 | ListValidator 11 | ) 12 | 13 | 14 | class ValidatorsTestCase(unittest.TestCase): 15 | 16 | def test_required_validator(self): 17 | with self.assertRaises(ValidationException) as vm: 18 | RequiredValidator().validate(None, 'first_name') 19 | self.assertEqual(str(vm.exception), 20 | 'first_name is required and cannot be empty.') 21 | # Test with valid input 22 | RequiredValidator().validate('First Name', 'first_name') 23 | 24 | def test_datetime_validator(self): 25 | with self.assertRaises(ValidationException) as vm: 26 | DateTimeValidator().validate(datetime.date.today(), 'date_created') 27 | self.assertEqual(str(vm.exception), 28 | 'date_created: This value should be a valid datetime object.') 29 | # Test with valid input 30 | DateTimeValidator().validate(datetime.datetime.now(), 'date_created') 31 | 32 | def test_date_validator(self): 33 | with self.assertRaises(ValidationException) as vm: 34 | DateValidator().validate('not a date', 'date_created') 35 | self.assertEqual(str(vm.exception), 36 | 'date_created: This value should be a valid date object.') 37 | # Test with valid input 38 | DateValidator().validate(datetime.date.today(), 'date_created') 39 | 40 | def test_float_validator(self): 41 | with self.assertRaises(ValidationException) as vm: 42 | FloatValidator().validate(1, 'no_packages') 43 | self.assertEqual(str(vm.exception), 44 | 'no_packages must be a float.') 45 | # Test with valid input 46 | FloatValidator().validate(1.3, 'no_packages') 47 | 48 | def test_integer_validator(self): 49 | with self.assertRaises(ValidationException) as vm: 50 | IntegerValidator().validate(1.2, 'no_packages') 51 | self.assertEqual(str(vm.exception), 52 | 'no_packages must be an integer.') 53 | # Test with valid input 54 | IntegerValidator().validate(1, 'no_packages') 55 | 56 | def test_max_length_validator(self): 57 | with self.assertRaises(ValidationException) as vm: 58 | MaxLengthValidator(2).validate('123', 'no_packages') 59 | self.assertEqual(str(vm.exception), 60 | 'no_packages must not be longer than 2 characters.') 61 | # Test with valid input 62 | MaxLengthValidator(2).validate('12', 'no_packages') 63 | 64 | def test_min_length_validator(self): 65 | with self.assertRaises(ValidationException) as vm: 66 | MinLengthValidator(2).validate('1', 'no_packages') 67 | self.assertEqual(str(vm.exception), 68 | 'no_packages must not be shorter than 2 characters.') 69 | # Test with valid input 70 | MinLengthValidator(2).validate('123', 'no_packages') 71 | 72 | def test_max_value_validator(self): 73 | with self.assertRaises(ValidationException) as vm: 74 | MaxValueValidator(2).validate(3, 'no_packages') 75 | self.assertEqual(str(vm.exception), 76 | 'no_packages must not be greater than 2.') 77 | # Test with valid input 78 | MaxValueValidator(2).validate(1, 'no_packages') 79 | 80 | def test_min_value_validator(self): 81 | with self.assertRaises(ValidationException) as vm: 82 | MinValueValidator(2).validate(1, 'no_packages') 83 | self.assertEqual(str(vm.exception), 84 | 'no_packages must not be less than 2.') 85 | # Test with valid input 86 | MinValueValidator(2).validate(3, 'no_packages') 87 | 88 | def test_string_validator(self): 89 | with self.assertRaises(ValidationException) as vm: 90 | StringValidator().validate(1, 'last_name') 91 | self.assertEqual(str(vm.exception), 92 | 'last_name must be a string.') 93 | # Test with valid input 94 | StringValidator().validate('Jones', 'last_name') 95 | 96 | def test_boolean_validator(self): 97 | with self.assertRaises(ValidationException) as vm: 98 | BooleanValidator().validate(1, 'last_name') 99 | self.assertEqual(str(vm.exception), 100 | 'last_name must be a boolean.') 101 | # Test with valid input 102 | BooleanValidator().validate(True, 'last_name') 103 | BooleanValidator().validate(False, 'last_name') 104 | 105 | def test_dict_validator(self): 106 | with self.assertRaises(ValidationException) as vm: 107 | DictValidator().validate(1, 'person') 108 | self.assertEqual(str(vm.exception), 109 | 'person must be a dictionary.') 110 | # Test with valid input 111 | DictValidator().validate({'first': 'Brian', 'last': 'Jones'}, 'person') 112 | 113 | def test_list_validator(self): 114 | with self.assertRaises(ValidationException) as vm: 115 | ListValidator().validate(1, 'schools') 116 | self.assertEqual(str(vm.exception), 117 | 'schools must be a list.') 118 | # Test with valid input 119 | ListValidator().validate(['Ridge Valley High', 'Lewis Cone Elementary'], 'schools') 120 | 121 | 122 | if __name__ == '__main__': 123 | unittest.main() 124 | -------------------------------------------------------------------------------- /valley/utils/__init__.py: -------------------------------------------------------------------------------- 1 | from .imports import import_util 2 | -------------------------------------------------------------------------------- /valley/utils/imports.py: -------------------------------------------------------------------------------- 1 | import importlib 2 | 3 | 4 | def import_util(imp): 5 | ''' 6 | Lazily imports a utils (class, 7 | function,or variable) from a module) from 8 | a string. 9 | @param imp: 10 | ''' 11 | 12 | mod_name, obj_name = imp.rsplit('.', 1) 13 | mod = importlib.import_module(mod_name) 14 | return getattr(mod, obj_name) 15 | 16 | 17 | -------------------------------------------------------------------------------- /valley/utils/json_utils.py: -------------------------------------------------------------------------------- 1 | import json 2 | import inspect 3 | 4 | from .imports import import_util 5 | 6 | 7 | class ValleyEncoder(json.JSONEncoder): 8 | show_type = True 9 | 10 | def default(self, obj): 11 | if not isinstance(obj, (list,dict,int,float,bool)): 12 | obj_dict = obj.to_dict() 13 | if self.show_type: 14 | obj_dict['_type'] = '{}.{}'.format(inspect.getmodule(obj).__name__, obj.__class__.__name__) 15 | return obj_dict 16 | return super(ValleyEncoder, self).default(obj) 17 | 18 | 19 | class ValleyEncoderNoType(ValleyEncoder): 20 | show_type = False 21 | 22 | 23 | class ValleyDecoder(json.JSONDecoder): 24 | def __init__(self, *args, **kwargs): 25 | json.JSONDecoder.__init__(self, object_hook=self.object_hook, *args, **kwargs) 26 | 27 | def object_hook(self, obj): 28 | if '_type' not in obj: 29 | return obj 30 | klass = import_util(obj['_type']) 31 | obj.pop('_type') 32 | 33 | return klass(**obj) 34 | -------------------------------------------------------------------------------- /valley/validators.py: -------------------------------------------------------------------------------- 1 | import re 2 | import datetime 3 | import time 4 | from typing import Any, List, Dict, Type, Optional 5 | 6 | from valley.exceptions import ValidationException 7 | 8 | 9 | class Validator: 10 | """ 11 | Base class for all validators. 12 | 13 | This class provides basic structure and interface for all specific validators. 14 | """ 15 | is_required_regardless: bool = False 16 | def validate(self, value: Any, name: str) -> None: 17 | # Ignore None values if the ignore_none flag is True 18 | if value is None and not self.is_required_regardless: 19 | return 20 | 21 | self.perform_validation(value, name) 22 | 23 | def perform_validation(self, value: Any, name: str) -> None: 24 | """ 25 | Method to perform validation, to be implemented by subclasses. 26 | 27 | Args: 28 | value (Any): The value to validate. 29 | name (str): The name of the property being validated. 30 | """ 31 | raise NotImplementedError 32 | 33 | 34 | class RequiredValidator(Validator): 35 | """ 36 | Validator to ensure a value is not None or empty. 37 | """ 38 | is_required_regardless: bool = True 39 | def perform_validation(self, value: Any, name: str) -> None: 40 | if value is None or value == '': 41 | raise ValidationException(f'{name} is required and cannot be empty.') 42 | 43 | 44 | class StringValidator(Validator): 45 | """ 46 | Validator to ensure a value is a string. 47 | """ 48 | 49 | def perform_validation(self, value: Any, name: str) -> None: 50 | if not isinstance(value, str): 51 | raise ValidationException(f'{name} must be a string.') 52 | 53 | 54 | class IntegerValidator(Validator): 55 | """ 56 | Validator to ensure a value is an integer. 57 | """ 58 | 59 | def perform_validation(self, value: Any, name: str) -> None: 60 | if isinstance(value, float): 61 | raise ValidationException(f'{name} must be an integer.') 62 | try: 63 | int(value) 64 | except ValueError: 65 | raise ValidationException(f'{name} must be an integer.') 66 | 67 | 68 | 69 | class MaxValueValidator(Validator): 70 | """ 71 | Validator to ensure a value does not exceed a maximum. 72 | """ 73 | 74 | def __init__(self, max_value: int) -> None: 75 | self.max_value = max_value 76 | 77 | def perform_validation(self, value: int, name: str) -> None: 78 | if value > self.max_value: 79 | raise ValidationException(f'{name} must not be greater than {self.max_value}.') 80 | 81 | 82 | class MinValueValidator(Validator): 83 | """ 84 | Validator to ensure a value is not below a minimum. 85 | """ 86 | 87 | def __init__(self, min_value: int) -> None: 88 | self.min_value = min_value 89 | 90 | def perform_validation(self, value: int, name: str) -> None: 91 | if value < self.min_value: 92 | raise ValidationException(f'{name} must not be less than {self.min_value}.') 93 | 94 | 95 | class MinLengthValidator(Validator): 96 | """ 97 | Validator to ensure the length of a value is not below a minimum. 98 | """ 99 | 100 | def __init__(self, min_length: int) -> None: 101 | self.min_length = min_length 102 | 103 | def perform_validation(self, value: str, name: str) -> None: 104 | if len(value) < self.min_length: 105 | raise ValidationException(f'{name} must not be shorter than {self.min_length} characters.') 106 | 107 | 108 | class MaxLengthValidator(Validator): 109 | """ 110 | Validator to ensure the length of a value does not exceed a maximum. 111 | """ 112 | 113 | def __init__(self, max_length: int) -> None: 114 | self.max_length = max_length 115 | 116 | def perform_validation(self, value: str, name: str) -> None: 117 | if len(value) > self.max_length: 118 | raise ValidationException(f'{name} must not be longer than {self.max_length} characters.') 119 | 120 | 121 | class DateValidator(Validator): 122 | 123 | def perform_validation(self, value, key=None): 124 | if not value: 125 | return 126 | if value and isinstance(value, str): 127 | try: 128 | value = datetime.date(*time.strptime(value, '%Y-%m-%d')[:3]) 129 | except ValueError: 130 | pass 131 | if value and not isinstance(value, datetime.date): 132 | raise ValidationException( 133 | '{0}: This value should be a valid date object.'.format(key)) 134 | 135 | 136 | class DateTimeValidator(Validator): 137 | 138 | def perform_validation(self, value, key=None): 139 | if not value: 140 | return 141 | if value and isinstance(value, str): 142 | try: 143 | value = value.split('.', 1)[0] # strip out microseconds 144 | value = value[0:19] # remove timezone 145 | value = datetime.datetime.strptime(value, '%Y-%m-%dT%H:%M:%S') 146 | except (IndexError, KeyError, ValueError): 147 | pass 148 | if value and not isinstance(value, datetime.datetime): 149 | raise ValidationException( 150 | '{0}: This value should be a valid datetime object.'.format(key)) 151 | class BooleanValidator(Validator): 152 | """ 153 | Validator to ensure a value is a boolean. 154 | """ 155 | 156 | def perform_validation(self, value: bool, name: str) -> None: 157 | if not isinstance(value, bool): 158 | raise ValidationException(f'{name} must be a boolean.') 159 | 160 | 161 | class ChoiceValidator(Validator): 162 | """ 163 | Validator to ensure a value is within a set of choices. 164 | """ 165 | choices: dict 166 | def __init__(self, choices: Dict[str, Any]) -> None: 167 | self.choices = choices 168 | 169 | def perform_validation(self, value: Any, name: str) -> None: 170 | if value not in self.choices.values(): 171 | raise ValidationException(f'{name} must be one of {self.choices}.') 172 | 173 | 174 | class DictValidator(Validator): 175 | """ 176 | Validator to ensure a value is a dictionary. 177 | """ 178 | 179 | def perform_validation(self, value: Dict[Any, Any], name: str) -> None: 180 | if not isinstance(value, dict): 181 | raise ValidationException(f'{name} must be a dictionary.') 182 | 183 | 184 | class ListValidator(Validator): 185 | """ 186 | Validator to ensure a value is a list. 187 | """ 188 | 189 | def perform_validation(self, value: List[Any], name: str) -> None: 190 | if not isinstance(value, list): 191 | raise ValidationException(f'{name} must be a list.') 192 | 193 | 194 | class ForeignValidator(Validator): 195 | """ 196 | Validator for foreign key relationships. 197 | """ 198 | 199 | def __init__(self, foreign_class: Any) -> None: 200 | self.foreign_class = foreign_class 201 | 202 | def perform_validation(self, value: Any, name: str) -> None: 203 | if not isinstance(value, self.foreign_class): 204 | raise ValidationException(f'{name} must be an instance of {self.foreign_class.__name__}.') 205 | 206 | 207 | class MultiValidator(Validator): 208 | """ 209 | Validator that allows combining multiple validators. 210 | """ 211 | 212 | def __init__(self, validators: List[Validator]) -> None: 213 | self.validators = validators 214 | 215 | def perform_validation(self, value: Any, name: str) -> None: 216 | for validator in self.validators: 217 | validator.validate(value, name) 218 | 219 | 220 | class FloatValidator(Validator): 221 | """ 222 | Validator to ensure a value is a float. 223 | """ 224 | 225 | def perform_validation(self, value: Any, name: str) -> None: 226 | """ 227 | Validates that the given value is a float. 228 | 229 | Args: 230 | value (Any): The value to validate. 231 | name (str): The name of the property being validated. 232 | 233 | Raises: 234 | ValidationException: If the value is not a float. 235 | """ 236 | try: 237 | float(value) 238 | except ValueError: 239 | raise ValidationException(f'{name} must be a float.') 240 | 241 | 242 | class SlugValidator(Validator): 243 | """ 244 | Validator to ensure a value is a valid slug. 245 | """ 246 | 247 | def __init__(self): 248 | self.slug_pattern = re.compile(r'^[-a-zA-Z0-9_]+$') 249 | 250 | def perform_validation(self, value: str, name: str) -> None: 251 | """ 252 | Validates that the given value is a valid slug. 253 | 254 | Args: 255 | value (str): The value to validate. 256 | name (str): The name of the property being validated. 257 | 258 | Raises: 259 | ValidationException: If the value is not a valid slug. 260 | """ 261 | if not isinstance(value, str) or not self.slug_pattern.match(value): 262 | raise ValidationException(f'{name} must be a valid slug (only letters, numbers, hyphens, and underscores).') 263 | 264 | 265 | class EmailValidator(Validator): 266 | """ 267 | Validator to ensure a value is a valid email address. 268 | """ 269 | 270 | def __init__(self): 271 | self.email_pattern = re.compile( 272 | r"(^[-!#$%&'*+/=?^_`{}|~0-9A-Z]+(\.[-!#$%&'*+/=?^_`{}|~0-9A-Z]+)*" # dot-atom 273 | r'|^"([\001-\010\013\014\016-\037!#-\[\]-\177]|\\[\001-011\013\014\016-\177])*"' # quoted-string 274 | r')@(?:[A-Z0-9](?:[A-Z0-9-]{0,61}[A-Z0-9])?\.)+[A-Z]{2,6}\.?$', re.IGNORECASE) # domain 275 | 276 | def perform_validation(self, value: str, name: str) -> None: 277 | """ 278 | Validates that the given value is a valid email address. 279 | 280 | Args: 281 | value (str): The value to validate. 282 | name (str): The name of the property being validated. 283 | 284 | Raises: 285 | ValidationException: If the value is not a valid email address. 286 | """ 287 | if not isinstance(value, str) or not self.email_pattern.match(value): 288 | raise ValidationException(f'{name} must be a valid email address.') 289 | 290 | class ForeignListValidator(Validator): 291 | """ 292 | Validator to ensure all items in a list are instances of a specified class. 293 | """ 294 | 295 | def __init__(self, foreign_class: Type[Any]) -> None: 296 | """ 297 | Initializes the ForeignListValidator with a specified class. 298 | 299 | Args: 300 | foreign_class (Type[Any]): The class that all list items should be instances of. 301 | """ 302 | self.foreign_class = foreign_class 303 | 304 | def perform_validation(self, value: List[Any], name: str) -> None: 305 | """ 306 | Validates that all items in the list are instances of the specified class. 307 | 308 | Args: 309 | value (List[Any]): The list to validate. 310 | name (str): The name of the property being validated. 311 | 312 | Raises: 313 | ValidationException: If any item in the list is not an instance of the specified class. 314 | """ 315 | if not all(isinstance(item, self.foreign_class) for item in value): 316 | raise ValidationException(f'All items in {name} must be instances of {self.foreign_class.__name__}.') 317 | 318 | 319 | 320 | --------------------------------------------------------------------------------