├── .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 | 
2 |
3 | # Valley
4 |
5 | Python extensible schema validations and declarative syntax helpers.
6 |
7 | [](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 |
--------------------------------------------------------------------------------