diff --git a/.bumpversion.cfg b/.bumpversion.cfg index 39294c4..15cdb7e 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -6,4 +6,3 @@ tag = True [bumpversion:file:setup.py] [bumpversion:file:pyetrade/__init__.py] - diff --git a/.flake8 b/.flake8 deleted file mode 100644 index 2bcd70e..0000000 --- a/.flake8 +++ /dev/null @@ -1,2 +0,0 @@ -[flake8] -max-line-length = 88 diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index ac1c360..c7cd4a0 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,19 +1,40 @@ -defualt_stages: [commit, push] -fail_fast: true +default_stages: [commit, push] +fail_fast: false repos: -- repo: https://github.com/ambv/black - rev: 23.1.0 - hooks: - - id: black -- repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.4.0 - hooks: - - id: trailing-whitespace -- repo: https://gitlab.com/pycqa/flake8 - rev: 3.9.2 - hooks: - - id: flake8 -- repo: https://github.com/szebenyib/pre-commit-pytest - rev: 051fea31dbabf063ab38428c34a92c76d1abd5dc - hooks: - - id: pytest + - repo: https://github.com/python-poetry/poetry + rev: 1.8.2 + hooks: + - id: poetry-check + - id: poetry-lock + - id: poetry-export + args: ["-f", "requirements.txt", "-o", "requirements.txt", "--without-hashes"] + - id: poetry-export # Dev dependencies + args: ["-f", "requirements.txt", "-o", "requirements_dev.txt", "--with", "dev", "--without-hashes"] + - id: poetry-install + - repo: https://github.com/asottile/reorder_python_imports + rev: v3.12.0 + hooks: + - id: reorder-python-imports + - repo: https://github.com/ambv/black + rev: 23.1.0 + hooks: + - id: black + - repo: https://github.com/PyCQA/flake8 + rev: 7.0.0 + hooks: + - id: flake8 + exclude: docs/conf.py + - repo: https://github.com/pre-commit/pre-commit-hooks + rev: v4.6.0 + hooks: + - id: check-ast + - id: check-added-large-files + - id: check-merge-conflict + - id: end-of-file-fixer + - id: mixed-line-ending + args: ['--fix=no'] + - id: trailing-whitespace + - repo: https://github.com/szebenyib/pre-commit-pytest + rev: 051fea31dbabf063ab38428c34a92c76d1abd5dc + hooks: + - id: pytest diff --git a/LICENSE b/LICENSE index be6cb4c..5b32568 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -GNU GENERAL PUBLIC LICENSE +GNU GENERAL PUBLIC LICENSE Version 3, 29 June 2007 Copyright © 2007 Free Software Foundation, Inc. @@ -46,7 +46,7 @@ To “convey” a work means any kind of propagation that enables other parties An interactive user interface displays “Appropriate Legal Notices” to the extent that it includes a convenient and prominently visible feature that (1) displays an appropriate copyright notice, and (2) tells the user that there is no warranty for the work (except to the extent that warranties are provided), that licensees may convey the work under this License, and how to view a copy of this License. If the interface presents a list of user commands or options, such as a menu, a prominent item in the list meets this criterion. -1. Source Code. +1. Source Code. The “source code” for a work means the preferred form of the work for making modifications to it. “Object code” means any non-source form of a work. A “Standard Interface” means an interface that either is an official standard defined by a recognized standards body, or, in the case of interfaces specified for a particular programming language, one that is widely used among developers working in that language. @@ -59,24 +59,24 @@ The Corresponding Source need not include anything that users can regenerate aut The Corresponding Source for a work in source code form is that same work. -2. Basic Permissions. +2. Basic Permissions. All rights granted under this License are granted for the term of copyright on the Program, and are irrevocable provided the stated conditions are met. This License explicitly affirms your unlimited permission to run the unmodified Program. The output from running a covered work is covered by this License only if the output, given its content, constitutes a covered work. This License acknowledges your rights of fair use or other equivalent, as provided by copyright law. You may make, run and propagate covered works that you do not convey, without conditions so long as your license otherwise remains in force. You may convey covered works to others for the sole purpose of having them make modifications exclusively for you, or provide you with facilities for running those works, provided that you comply with the terms of this License in conveying all material for which you do not control copyright. Those thus making or running the covered works for you must do so exclusively on your behalf, under your direction and control, on terms that prohibit them from making any copies of your copyrighted material outside their relationship with you. Conveying under any other circumstances is permitted solely under the conditions stated below. Sublicensing is not allowed; section 10 makes it unnecessary. -3. Protecting Users' Legal Rights From Anti-Circumvention Law. +3. Protecting Users' Legal Rights From Anti-Circumvention Law. No covered work shall be deemed part of an effective technological measure under any applicable law fulfilling obligations under article 11 of the WIPO copyright treaty adopted on 20 December 1996, or similar laws prohibiting or restricting circumvention of such measures. When you convey a covered work, you waive any legal power to forbid circumvention of technological measures to the extent such circumvention is effected by exercising rights under this License with respect to the covered work, and you disclaim any intention to limit operation or modification of the work as a means of enforcing, against the work's users, your or third parties' legal rights to forbid circumvention of technological measures. -4. Conveying Verbatim Copies. +4. Conveying Verbatim Copies. You may convey verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice; keep intact all notices stating that this License and any non-permissive terms added in accord with section 7 apply to the code; keep intact all notices of the absence of any warranty; and give all recipients a copy of this License along with the Program. You may charge any price or no price for each copy that you convey, and you may offer support or warranty protection for a fee. -5. Conveying Modified Source Versions. +5. Conveying Modified Source Versions. You may convey a work based on the Program, or the modifications to produce it from the Program, in the form of source code under the terms of section 4, provided that you also meet all of these conditions: a) The work must carry prominent notices stating that you modified it, and giving a relevant date. @@ -89,7 +89,7 @@ d) If the work has interactive user interfaces, each must display Appropriate Le A compilation of a covered work with other separate and independent works, which are not by their nature extensions of the covered work, and which are not combined with it such as to form a larger program, in or on a volume of a storage or distribution medium, is called an “aggregate” if the compilation and its resulting copyright are not used to limit the access or legal rights of the compilation's users beyond what the individual works permit. Inclusion of a covered work in an aggregate does not cause this License to apply to the other parts of the aggregate. -6. Conveying Non-Source Forms. +6. Conveying Non-Source Forms. You may convey a covered work in object code form under the terms of sections 4 and 5, provided that you also convey the machine-readable Corresponding Source under the terms of this License, in one of these ways: a) Convey the object code in, or embodied in, a physical product (including a physical distribution medium), accompanied by the Corresponding Source fixed on a durable physical medium customarily used for software interchange. @@ -114,7 +114,7 @@ The requirement to provide Installation Information does not include a requireme Corresponding Source conveyed, and Installation Information provided, in accord with this section must be in a format that is publicly documented (and with an implementation available to the public in source code form), and must require no special password or key for unpacking, reading or copying. -7. Additional Terms. +7. Additional Terms. “Additional permissions” are terms that supplement the terms of this License by making exceptions from one or more of its conditions. Additional permissions that are applicable to the entire Program shall be treated as though they were included in this License, to the extent that they are valid under applicable law. If additional permissions apply only to part of the Program, that part may be used separately under those permissions, but the entire Program remains governed by this License without regard to the additional permissions. When you convey a copy of a covered work, you may at your option remove any additional permissions from that copy, or from any part of it. (Additional permissions may be written to require their own removal in certain cases when you modify the work.) You may place additional permissions on material, added by you to a covered work, for which you have or can give appropriate copyright permission. @@ -139,7 +139,7 @@ If you add terms to a covered work in accord with this section, you must place, Additional terms, permissive or non-permissive, may be stated in the form of a separately written license, or stated as exceptions; the above requirements apply either way. -8. Termination. +8. Termination. You may not propagate or modify a covered work except as expressly provided under this License. Any attempt otherwise to propagate or modify it is void, and will automatically terminate your rights under this License (including any patent licenses granted under the third paragraph of section 11). However, if you cease all violation of this License, then your license from a particular copyright holder is reinstated (a) provisionally, unless and until the copyright holder explicitly and finally terminates your license, and (b) permanently, if the copyright holder fails to notify you of the violation by some reasonable means prior to 60 days after the cessation. @@ -148,17 +148,17 @@ Moreover, your license from a particular copyright holder is reinstated permanen Termination of your rights under this section does not terminate the licenses of parties who have received copies or rights from you under this License. If your rights have been terminated and not permanently reinstated, you do not qualify to receive new licenses for the same material under section 10. -9. Acceptance Not Required for Having Copies. +9. Acceptance Not Required for Having Copies. You are not required to accept this License in order to receive or run a copy of the Program. Ancillary propagation of a covered work occurring solely as a consequence of using peer-to-peer transmission to receive a copy likewise does not require acceptance. However, nothing other than this License grants you permission to propagate or modify any covered work. These actions infringe copyright if you do not accept this License. Therefore, by modifying or propagating a covered work, you indicate your acceptance of this License to do so. -10. Automatic Licensing of Downstream Recipients. +10. Automatic Licensing of Downstream Recipients. Each time you convey a covered work, the recipient automatically receives a license from the original licensors, to run, modify and propagate that work, subject to this License. You are not responsible for enforcing compliance by third parties with this License. An “entity transaction” is a transaction transferring control of an organization, or substantially all assets of one, or subdividing an organization, or merging organizations. If propagation of a covered work results from an entity transaction, each party to that transaction who receives a copy of the work also receives whatever licenses to the work the party's predecessor in interest had or could give under the previous paragraph, plus a right to possession of the Corresponding Source of the work from the predecessor in interest, if the predecessor has it or can get it with reasonable efforts. You may not impose any further restrictions on the exercise of the rights granted or affirmed under this License. For example, you may not impose a license fee, royalty, or other charge for exercise of rights granted under this License, and you may not initiate litigation (including a cross-claim or counterclaim in a lawsuit) alleging that any patent claim is infringed by making, using, selling, offering for sale, or importing the Program or any portion of it. -11. Patents. +11. Patents. A “contributor” is a copyright holder who authorizes use under this License of the Program or a work on which the Program is based. The work thus licensed is called the contributor's “contributor version”. A contributor's “essential patent claims” are all patent claims owned or controlled by the contributor, whether already acquired or hereafter acquired, that would be infringed by some manner, permitted by this License, of making, using, or selling its contributor version, but do not include claims that would be infringed only as a consequence of further modification of the contributor version. For purposes of this definition, “control” includes the right to grant patent sublicenses in a manner consistent with the requirements of this License. @@ -175,13 +175,13 @@ A patent license is “discriminatory” if it does not include within the scope Nothing in this License shall be construed as excluding or limiting any implied license or other defenses to infringement that may otherwise be available to you under applicable patent law. -12. No Surrender of Others' Freedom. +12. No Surrender of Others' Freedom. If conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot convey a covered work so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not convey it at all. For example, if you agree to terms that obligate you to collect a royalty for further conveying from those to whom you convey the Program, the only way you could satisfy both those terms and this License would be to refrain entirely from conveying the Program. -13. Use with the GNU Affero General Public License. +13. Use with the GNU Affero General Public License. Notwithstanding any other provision of this License, you have permission to link or combine any covered work with a work licensed under version 3 of the GNU Affero General Public License into a single combined work, and to convey the resulting work. The terms of this License will continue to apply to the part which is the covered work, but the special requirements of the GNU Affero General Public License, section 13, concerning interaction through a network will apply to the combination as such. -14. Revised Versions of this License. +14. Revised Versions of this License. The Free Software Foundation may publish revised and/or new versions of the GNU General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies that a certain numbered version of the GNU General Public License “or any later version” applies to it, you have the option of following the terms and conditions either of that numbered version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of the GNU General Public License, you may choose any version ever published by the Free Software Foundation. @@ -190,13 +190,13 @@ If the Program specifies that a proxy can decide which future versions of the GN Later license versions may give you additional or different permissions. However, no additional obligations are imposed on any author or copyright holder as a result of your choosing to follow a later version. -15. Disclaimer of Warranty. +15. Disclaimer of Warranty. THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM “AS IS” WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. -16. Limitation of Liability. +16. Limitation of Liability. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. -17. Interpretation of Sections 15 and 16. +17. Interpretation of Sections 15 and 16. If the disclaimer of warranty and limitation of liability provided above cannot be given local legal effect according to their terms, reviewing courts shall apply local law that most closely approximates an absolute waiver of all civil liability in connection with the Program, unless a warranty or assumption of liability accompanies a copy of the Program in return for a fee. END OF TERMS AND CONDITIONS @@ -207,7 +207,7 @@ If you develop a new program, and you want it to be of the greatest possible use To do so, attach the following notices to the program. It is safest to attach them to the start of each source file to most effectively state the exclusion of warranty; and each file should have at least the “copyright” line and a pointer to where the full notice is found. - + Copyright (C) This program is free software: you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. @@ -220,8 +220,8 @@ Also add information on how to contact you by electronic and paper mail. If the program does terminal interaction, make it output a short notice like this when it starts in an interactive mode: - Copyright (C) -This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + Copyright (C) +This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. This is free software, and you are welcome to redistribute it under certain conditions; type `show c' for details. The hypothetical commands `show w' and `show c' should show the appropriate parts of the General Public License. Of course, your program's commands might be different; for a GUI interface, you would use an “about box”. diff --git a/Makefile b/Makefile index d8f9ba7..4ea2697 100644 --- a/Makefile +++ b/Makefile @@ -2,11 +2,11 @@ init: pip install -r requirements.txt devel: pip install -r requirements_dev.txt - pre-commit install + pre-commit install --hook-type pre-push --install-hooks -t post-checkout -t post-merge test: tox -lint: - flake8 pyetrade tests +analysis: # Lint, format, import optimizer, etc. + poetry run pre-commit run --all-files install: pip install --upgrade . dist: diff --git a/README.md b/README.md index 9c6fa4c..7f2ca8c 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,5 @@ -# pyetrade +# pyetrade (Python E-Trade API Wrapper) -Python E-Trade API Wrapper [![PyPI](https://img.shields.io/pypi/v/pyetrade.svg)](https://pypi.python.org/pypi/pyetrade) [![PyPI](https://img.shields.io/pypi/l/pyetrade.svg)]() [![PyPI](https://img.shields.io/pypi/pyversions/pyetrade.svg)](https://pypi.python.org/pypi/pyetrade) @@ -8,34 +7,65 @@ Python E-Trade API Wrapper [![codecov](https://codecov.io/gh/jessecooper/pyetrade/branch/master/graph/badge.svg)](https://codecov.io/gh/jessecooper/pyetrade) ## Completed -v1 API -Authorization API - ALL -Accounts -* list accounts - -Authorization API - ALL -Order API - -* List Orders -* Place Equity Order -* Cancel Order - -Market API - -* Look Up Product -* optionchain -* Get Quote + +* Authorization API (OAuth) + * get_request_token + * get_access_token + * renew_access_token + * revoke_access_token + + +* Alerts API + * list_alerts + * list_alert_details + * delete_alert + + +* Accounts API + * list_accounts + * get_account_balance + * get_account_portfolio + * get_portfolio_position_lot + * list_transactions + * list_transaction_details + + +* Order API + * list_orders + * list_order_details + * find_option_orders + * preview_equity_order + * change_preview_equity_order + * place_equity_order + * place_changed_equity_order + * place_option_order + * place_changed_option_order + * cancel_order + + +* Market API + * look_up_product + * get_quote + * get_option_chains + * get_option_expire_date ## Install -``` + +```bash pip install pyetrade -- or - +``` +OR +```bash git clone https://github.com/jessecooper/pyetrade.git cd pyetrade sudo make init sudo make install ``` + ## Example Usage To create the OAuth tokens: + ```python import pyetrade @@ -47,6 +77,7 @@ print(oauth.get_request_token()) # Use the printed URL verifier_code = input("Enter verification code: ") tokens = oauth.get_access_token(verifier_code) + print(tokens) ``` @@ -69,33 +100,47 @@ accounts = pyetrade.ETradeAccounts( print(accounts.list_accounts()) ``` + ## Documentation + [PyEtrade Documentation](https://pyetrade.readthedocs.io/en/latest/) + ## Contribute to pyetrade -* [ETrade API Docs](https://apisb.etrade.com/docs/api/account/api-account-v1.html) + +[ETrade API Docs](https://apisb.etrade.com/docs/api/account/api-account-v1.html) + +### Development Setup: + * Fork pyetrade -* Development Setup: -``` - make init - make devel -``` -or -``` - pip install -r requirements.txt - pip install -r requirements_dev.txt - pip install -e . -``` -* Lint +* Setup development environment + +```bash +make init +make devel ``` -# Run Black -black pyetrade/ -# Run Linter -pylint pyetrade/ #Lint score should be >=8 +OR +```bash +pip install -r requirements.txt +pip install -r requirements_dev.txt +pip install -e . +pre-commit install --hook-type pre-commit --hook-type pre-push --install-hooks -t post-checkout -t post-merge ``` -* Test + +* Lint (Run analysis - pre-commit-config) + +```bash +make analysis ``` -make test #Ensure test coverage is >80% + +* Test (Coverage >= 90%) + +```bash +make test ``` -* Push Changes: -Push changes to a branch on your forked repo + +* Push Changes + * Push changes to a branch on your forked repo + + * Create pull request + * Open a pull request on pyetrade and put your fork as the source of your changes diff --git a/docs/conf.py b/docs/conf.py index 92ad997..ebaeaa3 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -12,14 +12,14 @@ # # All configuration values have a default; values that are commented out # serve to show the default. - # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. # import os import sys -sys.path.insert(0, os.path.abspath('..')) + +sys.path.insert(0, os.path.abspath("..")) # Import Module import pyetrade @@ -35,33 +35,31 @@ # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. -extensions = ['sphinx.ext.todo', - 'sphinx.ext.viewcode', - 'sphinx.ext.autodoc'] +extensions = ["sphinx.ext.todo", "sphinx.ext.viewcode", "sphinx.ext.autodoc"] # Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] +templates_path = ["_templates"] # The suffix(es) of source filenames. # You can specify multiple suffix as a list of string: # # source_suffix = ['.rst', '.md'] -source_suffix = '.rst' +source_suffix = ".rst" # The master toctree document. -master_doc = 'index' +master_doc = "index" # General information about the project. -project = 'pyetrade' -copyright = '2017, Jesse Cooper' -author = 'Jesse Cooper' +project = "pyetrade" +copyright = "2024, Jesse Cooper" +author = "Jesse Cooper" # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. -version = '' +version = "" # The full version, including alpha/beta/rc tags. release = __version__ @@ -75,10 +73,10 @@ # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. # This patterns also effect to html_static_path and html_extra_path -exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store', '.spyproject', '__pycache__'] +exclude_patterns = ["_build", "Thumbs.db", ".DS_Store", ".spyproject", "__pycache__"] # The name of the Pygments (syntax highlighting) style to use. -pygments_style = 'sphinx' +pygments_style = "sphinx" # If true, `todo` and `todoList` produce output, else they produce nothing. todo_include_todos = True @@ -89,15 +87,15 @@ # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. # -html_theme = 'alabaster' +html_theme = "alabaster" # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. # html_theme_options = { - 'page_width': 'auto', - 'body_max_width': 'auto', + "page_width": "auto", + "body_max_width": "auto", } # Add any paths that contain custom static files (such as style sheets) here, @@ -111,7 +109,7 @@ # -- Options for HTMLHelp output ------------------------------------------ # Output file base name for HTML help builder. -htmlhelp_basename = 'pyetradedoc' +htmlhelp_basename = "pyetradedoc" # -- Options for LaTeX output --------------------------------------------- @@ -120,15 +118,12 @@ # The paper size ('letterpaper' or 'a4paper'). # # 'papersize': 'letterpaper', - # The font size ('10pt', '11pt' or '12pt'). # # 'pointsize': '10pt', - # Additional stuff for the LaTeX preamble. # # 'preamble': '', - # Latex figure (float) alignment # # 'figure_align': 'htbp', @@ -138,8 +133,7 @@ # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ - (master_doc, 'pyetrade.tex', 'pyetrade Documentation', - 'Jesse Cooper', 'manual'), + (master_doc, "pyetrade.tex", "pyetrade Documentation", "Jesse Cooper", "manual"), ] @@ -147,10 +141,7 @@ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). -man_pages = [ - (master_doc, 'pyetrade', 'pyetrade Documentation', - [author], 1) -] +man_pages = [(master_doc, "pyetrade", "pyetrade Documentation", [author], 1)] # -- Options for Texinfo output ------------------------------------------- @@ -159,10 +150,13 @@ # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ - (master_doc, 'pyetrade', 'pyetrade Documentation', - author, 'pyetrade', 'One line description of project.', - 'Miscellaneous'), + ( + master_doc, + "pyetrade", + "pyetrade Documentation", + author, + "pyetrade", + "One line description of project.", + "Miscellaneous", + ), ] - - - diff --git a/docs/examples.rst b/docs/examples.rst index cc5f077..e9d658d 100644 --- a/docs/examples.rst +++ b/docs/examples.rst @@ -6,17 +6,17 @@ PyEtrade examples for some of the modules are as below Important requirements ----------------------- -Getting access tokens requires the users `Consumer key` and `Consumer Secret` -obtained from E*TRADE. This applies equally to both the sandbox and Live -environments. +Getting access tokens requires the users `Consumer key` and `Consumer Secret` +obtained from E*TRADE. This applies equally to both the sandbox and Live +environments. -For the sandbox key, request a Sandbox consumer key via +For the sandbox key, request a Sandbox consumer key via ``_ and for the Live/Production environment, -request a key through the E*TRADE secure message. Please refer +request a key through the E*TRADE secure message. Please refer `E*TRADE Developer `_ for more information -The following examples assume you were successfully able to obtain the +The following examples assume you were successfully able to obtain the `Consumer key` and `Consumer Secret` from E*TRADE. Primary Authorization @@ -27,8 +27,8 @@ Primary Authorization .. code-block:: python - # Importing the pyetrade module - import pyetrade + # Importing the pyetrade module + import pyetrade # Obtained secrets from Etrade for Sandbox or Live consumer_key = "" @@ -51,7 +51,7 @@ Access Management .. code-block:: python - # Importing the pyetrade module + # Importing the pyetrade module import pyetrade # Obtained secrets from Etrade for Sandbox or Live @@ -100,14 +100,14 @@ Accounts Management consumer_secret, tokens['oauth_token'], tokens['oauth_token_secret'], - dev=True + dev=True ) - # lists all the accounts for + # lists all the accounts for print(accounts.list_accounts(resp_format='json')) - # The above produces a json with all the accounts and their - # respective accountIDKeys + # The above produces a json with all the accounts and their + # respective accountIDKeys accountIDKey = '' diff --git a/poetry.lock b/poetry.lock new file mode 100644 index 0000000..7d69466 --- /dev/null +++ b/poetry.lock @@ -0,0 +1,2064 @@ +# This file is automatically @generated by Poetry 1.8.2 and should not be changed by hand. + +[[package]] +name = "alabaster" +version = "0.7.16" +description = "A light, configurable Sphinx theme" +optional = false +python-versions = ">=3.9" +files = [ + {file = "alabaster-0.7.16-py3-none-any.whl", hash = "sha256:b46733c07dce03ae4e150330b975c75737fa60f0a7c591b6c8bf4928a28e2c92"}, + {file = "alabaster-0.7.16.tar.gz", hash = "sha256:75a8b99c28a5dad50dd7f8ccdd447a121ddb3892da9e53d1ca5cca3106d58d65"}, +] + +[[package]] +name = "astroid" +version = "3.1.0" +description = "An abstract syntax tree for Python with inference support." +optional = false +python-versions = ">=3.8.0" +files = [ + {file = "astroid-3.1.0-py3-none-any.whl", hash = "sha256:951798f922990137ac090c53af473db7ab4e70c770e6d7fae0cec59f74411819"}, + {file = "astroid-3.1.0.tar.gz", hash = "sha256:ac248253bfa4bd924a0de213707e7ebeeb3138abeb48d798784ead1e56d419d4"}, +] + +[package.dependencies] +typing-extensions = {version = ">=4.0.0", markers = "python_version < \"3.11\""} + +[[package]] +name = "babel" +version = "2.14.0" +description = "Internationalization utilities" +optional = false +python-versions = ">=3.7" +files = [ + {file = "Babel-2.14.0-py3-none-any.whl", hash = "sha256:efb1a25b7118e67ce3a259bed20545c29cb68be8ad2c784c83689981b7a57287"}, + {file = "Babel-2.14.0.tar.gz", hash = "sha256:6919867db036398ba21eb5c7a0f6b28ab8cbc3ae7a73a44ebe34ae74a4e7d363"}, +] + +[package.extras] +dev = ["freezegun (>=1.0,<2.0)", "pytest (>=6.0)", "pytest-cov"] + +[[package]] +name = "black" +version = "24.4.0" +description = "The uncompromising code formatter." +optional = false +python-versions = ">=3.8" +files = [ + {file = "black-24.4.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6ad001a9ddd9b8dfd1b434d566be39b1cd502802c8d38bbb1ba612afda2ef436"}, + {file = "black-24.4.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:e3a3a092b8b756c643fe45f4624dbd5a389f770a4ac294cf4d0fce6af86addaf"}, + {file = "black-24.4.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dae79397f367ac8d7adb6c779813328f6d690943f64b32983e896bcccd18cbad"}, + {file = "black-24.4.0-cp310-cp310-win_amd64.whl", hash = "sha256:71d998b73c957444fb7c52096c3843875f4b6b47a54972598741fe9a7f737fcb"}, + {file = "black-24.4.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:8e5537f456a22cf5cfcb2707803431d2feeb82ab3748ade280d6ccd0b40ed2e8"}, + {file = "black-24.4.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:64e60a7edd71fd542a10a9643bf369bfd2644de95ec71e86790b063aa02ff745"}, + {file = "black-24.4.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5cd5b4f76056cecce3e69b0d4c228326d2595f506797f40b9233424e2524c070"}, + {file = "black-24.4.0-cp311-cp311-win_amd64.whl", hash = "sha256:64578cf99b6b46a6301bc28bdb89f9d6f9b592b1c5837818a177c98525dbe397"}, + {file = "black-24.4.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f95cece33329dc4aa3b0e1a771c41075812e46cf3d6e3f1dfe3d91ff09826ed2"}, + {file = "black-24.4.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:4396ca365a4310beef84d446ca5016f671b10f07abdba3e4e4304218d2c71d33"}, + {file = "black-24.4.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:44d99dfdf37a2a00a6f7a8dcbd19edf361d056ee51093b2445de7ca09adac965"}, + {file = "black-24.4.0-cp312-cp312-win_amd64.whl", hash = "sha256:21f9407063ec71c5580b8ad975653c66508d6a9f57bd008bb8691d273705adcd"}, + {file = "black-24.4.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:652e55bb722ca026299eb74e53880ee2315b181dfdd44dca98e43448620ddec1"}, + {file = "black-24.4.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:7f2966b9b2b3b7104fca9d75b2ee856fe3fdd7ed9e47c753a4bb1a675f2caab8"}, + {file = "black-24.4.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1bb9ca06e556a09f7f7177bc7cb604e5ed2d2df1e9119e4f7d2f1f7071c32e5d"}, + {file = "black-24.4.0-cp38-cp38-win_amd64.whl", hash = "sha256:d4e71cdebdc8efeb6deaf5f2deb28325f8614d48426bed118ecc2dcaefb9ebf3"}, + {file = "black-24.4.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:6644f97a7ef6f401a150cca551a1ff97e03c25d8519ee0bbc9b0058772882665"}, + {file = "black-24.4.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:75a2d0b4f5eb81f7eebc31f788f9830a6ce10a68c91fbe0fade34fff7a2836e6"}, + {file = "black-24.4.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eb949f56a63c5e134dfdca12091e98ffb5fd446293ebae123d10fc1abad00b9e"}, + {file = "black-24.4.0-cp39-cp39-win_amd64.whl", hash = "sha256:7852b05d02b5b9a8c893ab95863ef8986e4dda29af80bbbda94d7aee1abf8702"}, + {file = "black-24.4.0-py3-none-any.whl", hash = "sha256:74eb9b5420e26b42c00a3ff470dc0cd144b80a766128b1771d07643165e08d0e"}, + {file = "black-24.4.0.tar.gz", hash = "sha256:f07b69fda20578367eaebbd670ff8fc653ab181e1ff95d84497f9fa20e7d0641"}, +] + +[package.dependencies] +click = ">=8.0.0" +mypy-extensions = ">=0.4.3" +packaging = ">=22.0" +pathspec = ">=0.9.0" +platformdirs = ">=2" +tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} +typing-extensions = {version = ">=4.0.1", markers = "python_version < \"3.11\""} + +[package.extras] +colorama = ["colorama (>=0.4.3)"] +d = ["aiohttp (>=3.7.4)", "aiohttp (>=3.7.4,!=3.9.0)"] +jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] +uvloop = ["uvloop (>=0.15.2)"] + +[[package]] +name = "build" +version = "1.2.1" +description = "A simple, correct Python build frontend" +optional = false +python-versions = ">=3.8" +files = [ + {file = "build-1.2.1-py3-none-any.whl", hash = "sha256:75e10f767a433d9a86e50d83f418e83efc18ede923ee5ff7df93b6cb0306c5d4"}, + {file = "build-1.2.1.tar.gz", hash = "sha256:526263f4870c26f26c433545579475377b2b7588b6f1eac76a001e873ae3e19d"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "os_name == \"nt\""} +importlib-metadata = {version = ">=4.6", markers = "python_full_version < \"3.10.2\""} +packaging = ">=19.1" +pyproject_hooks = "*" +tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} + +[package.extras] +docs = ["furo (>=2023.08.17)", "sphinx (>=7.0,<8.0)", "sphinx-argparse-cli (>=1.5)", "sphinx-autodoc-typehints (>=1.10)", "sphinx-issues (>=3.0.0)"] +test = ["build[uv,virtualenv]", "filelock (>=3)", "pytest (>=6.2.4)", "pytest-cov (>=2.12)", "pytest-mock (>=2)", "pytest-rerunfailures (>=9.1)", "pytest-xdist (>=1.34)", "setuptools (>=42.0.0)", "setuptools (>=56.0.0)", "setuptools (>=56.0.0)", "setuptools (>=67.8.0)", "wheel (>=0.36.0)"] +typing = ["build[uv]", "importlib-metadata (>=5.1)", "mypy (>=1.9.0,<1.10.0)", "tomli", "typing-extensions (>=3.7.4.3)"] +uv = ["uv (>=0.1.18)"] +virtualenv = ["virtualenv (>=20.0.35)"] + +[[package]] +name = "bumpversion" +version = "0.5.3" +description = "Version-bump your software with a single command!" +optional = false +python-versions = "*" +files = [ + {file = "bumpversion-0.5.3-py2.py3-none-any.whl", hash = "sha256:6753d9ff3552013e2130f7bc03c1007e24473b4835952679653fb132367bdd57"}, + {file = "bumpversion-0.5.3.tar.gz", hash = "sha256:6744c873dd7aafc24453d8b6a1a0d6d109faf63cd0cd19cb78fd46e74932c77e"}, +] + +[[package]] +name = "cachecontrol" +version = "0.14.0" +description = "httplib2 caching for requests" +optional = false +python-versions = ">=3.7" +files = [ + {file = "cachecontrol-0.14.0-py3-none-any.whl", hash = "sha256:f5bf3f0620c38db2e5122c0726bdebb0d16869de966ea6a2befe92470b740ea0"}, + {file = "cachecontrol-0.14.0.tar.gz", hash = "sha256:7db1195b41c81f8274a7bbd97c956f44e8348265a1bc7641c37dfebc39f0c938"}, +] + +[package.dependencies] +filelock = {version = ">=3.8.0", optional = true, markers = "extra == \"filecache\""} +msgpack = ">=0.5.2,<2.0.0" +requests = ">=2.16.0" + +[package.extras] +dev = ["CacheControl[filecache,redis]", "black", "build", "cherrypy", "furo", "mypy", "pytest", "pytest-cov", "sphinx", "sphinx-copybutton", "tox", "types-redis", "types-requests"] +filecache = ["filelock (>=3.8.0)"] +redis = ["redis (>=2.10.5)"] + +[[package]] +name = "cachetools" +version = "5.3.3" +description = "Extensible memoizing collections and decorators" +optional = false +python-versions = ">=3.7" +files = [ + {file = "cachetools-5.3.3-py3-none-any.whl", hash = "sha256:0abad1021d3f8325b2fc1d2e9c8b9c9d57b04c3932657a72465447332c24d945"}, + {file = "cachetools-5.3.3.tar.gz", hash = "sha256:ba29e2dfa0b8b556606f097407ed1aa62080ee108ab0dc5ec9d6a723a007d105"}, +] + +[[package]] +name = "certifi" +version = "2024.2.2" +description = "Python package for providing Mozilla's CA Bundle." +optional = false +python-versions = ">=3.6" +files = [ + {file = "certifi-2024.2.2-py3-none-any.whl", hash = "sha256:dc383c07b76109f368f6106eee2b593b04a011ea4d55f652c6ca24a754d1cdd1"}, + {file = "certifi-2024.2.2.tar.gz", hash = "sha256:0569859f95fc761b18b45ef421b1290a0f65f147e92a1e5eb3e635f9a5e4e66f"}, +] + +[[package]] +name = "cffi" +version = "1.16.0" +description = "Foreign Function Interface for Python calling C code." +optional = false +python-versions = ">=3.8" +files = [ + {file = "cffi-1.16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6b3d6606d369fc1da4fd8c357d026317fbb9c9b75d36dc16e90e84c26854b088"}, + {file = "cffi-1.16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ac0f5edd2360eea2f1daa9e26a41db02dd4b0451b48f7c318e217ee092a213e9"}, + {file = "cffi-1.16.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7e61e3e4fa664a8588aa25c883eab612a188c725755afff6289454d6362b9673"}, + {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a72e8961a86d19bdb45851d8f1f08b041ea37d2bd8d4fd19903bc3083d80c896"}, + {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5b50bf3f55561dac5438f8e70bfcdfd74543fd60df5fa5f62d94e5867deca684"}, + {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7651c50c8c5ef7bdb41108b7b8c5a83013bfaa8a935590c5d74627c047a583c7"}, + {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4108df7fe9b707191e55f33efbcb2d81928e10cea45527879a4749cbe472614"}, + {file = "cffi-1.16.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:32c68ef735dbe5857c810328cb2481e24722a59a2003018885514d4c09af9743"}, + {file = "cffi-1.16.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:673739cb539f8cdaa07d92d02efa93c9ccf87e345b9a0b556e3ecc666718468d"}, + {file = "cffi-1.16.0-cp310-cp310-win32.whl", hash = "sha256:9f90389693731ff1f659e55c7d1640e2ec43ff725cc61b04b2f9c6d8d017df6a"}, + {file = "cffi-1.16.0-cp310-cp310-win_amd64.whl", hash = "sha256:e6024675e67af929088fda399b2094574609396b1decb609c55fa58b028a32a1"}, + {file = "cffi-1.16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b84834d0cf97e7d27dd5b7f3aca7b6e9263c56308ab9dc8aae9784abb774d404"}, + {file = "cffi-1.16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1b8ebc27c014c59692bb2664c7d13ce7a6e9a629be20e54e7271fa696ff2b417"}, + {file = "cffi-1.16.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ee07e47c12890ef248766a6e55bd38ebfb2bb8edd4142d56db91b21ea68b7627"}, + {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8a9d3ebe49f084ad71f9269834ceccbf398253c9fac910c4fd7053ff1386936"}, + {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e70f54f1796669ef691ca07d046cd81a29cb4deb1e5f942003f401c0c4a2695d"}, + {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5bf44d66cdf9e893637896c7faa22298baebcd18d1ddb6d2626a6e39793a1d56"}, + {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7b78010e7b97fef4bee1e896df8a4bbb6712b7f05b7ef630f9d1da00f6444d2e"}, + {file = "cffi-1.16.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c6a164aa47843fb1b01e941d385aab7215563bb8816d80ff3a363a9f8448a8dc"}, + {file = "cffi-1.16.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e09f3ff613345df5e8c3667da1d918f9149bd623cd9070c983c013792a9a62eb"}, + {file = "cffi-1.16.0-cp311-cp311-win32.whl", hash = "sha256:2c56b361916f390cd758a57f2e16233eb4f64bcbeee88a4881ea90fca14dc6ab"}, + {file = "cffi-1.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:db8e577c19c0fda0beb7e0d4e09e0ba74b1e4c092e0e40bfa12fe05b6f6d75ba"}, + {file = "cffi-1.16.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:fa3a0128b152627161ce47201262d3140edb5a5c3da88d73a1b790a959126956"}, + {file = "cffi-1.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:68e7c44931cc171c54ccb702482e9fc723192e88d25a0e133edd7aff8fcd1f6e"}, + {file = "cffi-1.16.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:abd808f9c129ba2beda4cfc53bde801e5bcf9d6e0f22f095e45327c038bfe68e"}, + {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88e2b3c14bdb32e440be531ade29d3c50a1a59cd4e51b1dd8b0865c54ea5d2e2"}, + {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fcc8eb6d5902bb1cf6dc4f187ee3ea80a1eba0a89aba40a5cb20a5087d961357"}, + {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b7be2d771cdba2942e13215c4e340bfd76398e9227ad10402a8767ab1865d2e6"}, + {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e715596e683d2ce000574bae5d07bd522c781a822866c20495e52520564f0969"}, + {file = "cffi-1.16.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2d92b25dbf6cae33f65005baf472d2c245c050b1ce709cc4588cdcdd5495b520"}, + {file = "cffi-1.16.0-cp312-cp312-win32.whl", hash = "sha256:b2ca4e77f9f47c55c194982e10f058db063937845bb2b7a86c84a6cfe0aefa8b"}, + {file = "cffi-1.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:68678abf380b42ce21a5f2abde8efee05c114c2fdb2e9eef2efdb0257fba1235"}, + {file = "cffi-1.16.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0c9ef6ff37e974b73c25eecc13952c55bceed9112be2d9d938ded8e856138bcc"}, + {file = "cffi-1.16.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a09582f178759ee8128d9270cd1344154fd473bb77d94ce0aeb2a93ebf0feaf0"}, + {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e760191dd42581e023a68b758769e2da259b5d52e3103c6060ddc02c9edb8d7b"}, + {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:80876338e19c951fdfed6198e70bc88f1c9758b94578d5a7c4c91a87af3cf31c"}, + {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a6a14b17d7e17fa0d207ac08642c8820f84f25ce17a442fd15e27ea18d67c59b"}, + {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6602bc8dc6f3a9e02b6c22c4fc1e47aa50f8f8e6d3f78a5e16ac33ef5fefa324"}, + {file = "cffi-1.16.0-cp38-cp38-win32.whl", hash = "sha256:131fd094d1065b19540c3d72594260f118b231090295d8c34e19a7bbcf2e860a"}, + {file = "cffi-1.16.0-cp38-cp38-win_amd64.whl", hash = "sha256:31d13b0f99e0836b7ff893d37af07366ebc90b678b6664c955b54561fc36ef36"}, + {file = "cffi-1.16.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:582215a0e9adbe0e379761260553ba11c58943e4bbe9c36430c4ca6ac74b15ed"}, + {file = "cffi-1.16.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b29ebffcf550f9da55bec9e02ad430c992a87e5f512cd63388abb76f1036d8d2"}, + {file = "cffi-1.16.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dc9b18bf40cc75f66f40a7379f6a9513244fe33c0e8aa72e2d56b0196a7ef872"}, + {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cb4a35b3642fc5c005a6755a5d17c6c8b6bcb6981baf81cea8bfbc8903e8ba8"}, + {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b86851a328eedc692acf81fb05444bdf1891747c25af7529e39ddafaf68a4f3f"}, + {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c0f31130ebc2d37cdd8e44605fb5fa7ad59049298b3f745c74fa74c62fbfcfc4"}, + {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f8e709127c6c77446a8c0a8c8bf3c8ee706a06cd44b1e827c3e6a2ee6b8c098"}, + {file = "cffi-1.16.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:748dcd1e3d3d7cd5443ef03ce8685043294ad6bd7c02a38d1bd367cfd968e000"}, + {file = "cffi-1.16.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8895613bcc094d4a1b2dbe179d88d7fb4a15cee43c052e8885783fac397d91fe"}, + {file = "cffi-1.16.0-cp39-cp39-win32.whl", hash = "sha256:ed86a35631f7bfbb28e108dd96773b9d5a6ce4811cf6ea468bb6a359b256b1e4"}, + {file = "cffi-1.16.0-cp39-cp39-win_amd64.whl", hash = "sha256:3686dffb02459559c74dd3d81748269ffb0eb027c39a6fc99502de37d501faa8"}, + {file = "cffi-1.16.0.tar.gz", hash = "sha256:bcb3ef43e58665bbda2fb198698fcae6776483e0c4a631aa5647806c25e02cc0"}, +] + +[package.dependencies] +pycparser = "*" + +[[package]] +name = "cfgv" +version = "3.4.0" +description = "Validate configuration and produce human readable error messages." +optional = false +python-versions = ">=3.8" +files = [ + {file = "cfgv-3.4.0-py2.py3-none-any.whl", hash = "sha256:b7265b1f29fd3316bfcd2b330d63d024f2bfd8bcb8b0272f8e19a504856c48f9"}, + {file = "cfgv-3.4.0.tar.gz", hash = "sha256:e52591d4c5f5dead8e0f673fb16db7949d2cfb3f7da4582893288f0ded8fe560"}, +] + +[[package]] +name = "chardet" +version = "5.2.0" +description = "Universal encoding detector for Python 3" +optional = false +python-versions = ">=3.7" +files = [ + {file = "chardet-5.2.0-py3-none-any.whl", hash = "sha256:e1cf59446890a00105fe7b7912492ea04b6e6f06d4b742b2c788469e34c82970"}, + {file = "chardet-5.2.0.tar.gz", hash = "sha256:1b3b6ff479a8c414bc3fa2c0852995695c4a026dcd6d0633b2dd092ca39c1cf7"}, +] + +[[package]] +name = "charset-normalizer" +version = "3.3.2" +description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." +optional = false +python-versions = ">=3.7.0" +files = [ + {file = "charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-win32.whl", hash = "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73"}, + {file = "charset_normalizer-3.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-win32.whl", hash = "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab"}, + {file = "charset_normalizer-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-win32.whl", hash = "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7"}, + {file = "charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-win32.whl", hash = "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4"}, + {file = "charset_normalizer-3.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-win32.whl", hash = "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25"}, + {file = "charset_normalizer-3.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-win32.whl", hash = "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f"}, + {file = "charset_normalizer-3.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d"}, + {file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"}, +] + +[[package]] +name = "cleo" +version = "2.1.0" +description = "Cleo allows you to create beautiful and testable command-line interfaces." +optional = false +python-versions = ">=3.7,<4.0" +files = [ + {file = "cleo-2.1.0-py3-none-any.whl", hash = "sha256:4a31bd4dd45695a64ee3c4758f583f134267c2bc518d8ae9a29cf237d009b07e"}, + {file = "cleo-2.1.0.tar.gz", hash = "sha256:0b2c880b5d13660a7ea651001fb4acb527696c01f15c9ee650f377aa543fd523"}, +] + +[package.dependencies] +crashtest = ">=0.4.1,<0.5.0" +rapidfuzz = ">=3.0.0,<4.0.0" + +[[package]] +name = "click" +version = "8.1.7" +description = "Composable command line interface toolkit" +optional = false +python-versions = ">=3.7" +files = [ + {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, + {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "platform_system == \"Windows\""} + +[[package]] +name = "colorama" +version = "0.4.6" +description = "Cross-platform colored terminal text." +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" +files = [ + {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, + {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, +] + +[[package]] +name = "coverage" +version = "7.4.4" +description = "Code coverage measurement for Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "coverage-7.4.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e0be5efd5127542ef31f165de269f77560d6cdef525fffa446de6f7e9186cfb2"}, + {file = "coverage-7.4.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ccd341521be3d1b3daeb41960ae94a5e87abe2f46f17224ba5d6f2b8398016cf"}, + {file = "coverage-7.4.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:09fa497a8ab37784fbb20ab699c246053ac294d13fc7eb40ec007a5043ec91f8"}, + {file = "coverage-7.4.4-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:b1a93009cb80730c9bca5d6d4665494b725b6e8e157c1cb7f2db5b4b122ea562"}, + {file = "coverage-7.4.4-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:690db6517f09336559dc0b5f55342df62370a48f5469fabf502db2c6d1cffcd2"}, + {file = "coverage-7.4.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:09c3255458533cb76ef55da8cc49ffab9e33f083739c8bd4f58e79fecfe288f7"}, + {file = "coverage-7.4.4-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:8ce1415194b4a6bd0cdcc3a1dfbf58b63f910dcb7330fe15bdff542c56949f87"}, + {file = "coverage-7.4.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:b91cbc4b195444e7e258ba27ac33769c41b94967919f10037e6355e998af255c"}, + {file = "coverage-7.4.4-cp310-cp310-win32.whl", hash = "sha256:598825b51b81c808cb6f078dcb972f96af96b078faa47af7dfcdf282835baa8d"}, + {file = "coverage-7.4.4-cp310-cp310-win_amd64.whl", hash = "sha256:09ef9199ed6653989ebbcaacc9b62b514bb63ea2f90256e71fea3ed74bd8ff6f"}, + {file = "coverage-7.4.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0f9f50e7ef2a71e2fae92774c99170eb8304e3fdf9c8c3c7ae9bab3e7229c5cf"}, + {file = "coverage-7.4.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:623512f8ba53c422fcfb2ce68362c97945095b864cda94a92edbaf5994201083"}, + {file = "coverage-7.4.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0513b9508b93da4e1716744ef6ebc507aff016ba115ffe8ecff744d1322a7b63"}, + {file = "coverage-7.4.4-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:40209e141059b9370a2657c9b15607815359ab3ef9918f0196b6fccce8d3230f"}, + {file = "coverage-7.4.4-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8a2b2b78c78293782fd3767d53e6474582f62443d0504b1554370bde86cc8227"}, + {file = "coverage-7.4.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:73bfb9c09951125d06ee473bed216e2c3742f530fc5acc1383883125de76d9cd"}, + {file = "coverage-7.4.4-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:1f384c3cc76aeedce208643697fb3e8437604b512255de6d18dae3f27655a384"}, + {file = "coverage-7.4.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:54eb8d1bf7cacfbf2a3186019bcf01d11c666bd495ed18717162f7eb1e9dd00b"}, + {file = "coverage-7.4.4-cp311-cp311-win32.whl", hash = "sha256:cac99918c7bba15302a2d81f0312c08054a3359eaa1929c7e4b26ebe41e9b286"}, + {file = "coverage-7.4.4-cp311-cp311-win_amd64.whl", hash = "sha256:b14706df8b2de49869ae03a5ccbc211f4041750cd4a66f698df89d44f4bd30ec"}, + {file = "coverage-7.4.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:201bef2eea65e0e9c56343115ba3814e896afe6d36ffd37bab783261db430f76"}, + {file = "coverage-7.4.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:41c9c5f3de16b903b610d09650e5e27adbfa7f500302718c9ffd1c12cf9d6818"}, + {file = "coverage-7.4.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d898fe162d26929b5960e4e138651f7427048e72c853607f2b200909794ed978"}, + {file = "coverage-7.4.4-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:3ea79bb50e805cd6ac058dfa3b5c8f6c040cb87fe83de10845857f5535d1db70"}, + {file = "coverage-7.4.4-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ce4b94265ca988c3f8e479e741693d143026632672e3ff924f25fab50518dd51"}, + {file = "coverage-7.4.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:00838a35b882694afda09f85e469c96367daa3f3f2b097d846a7216993d37f4c"}, + {file = "coverage-7.4.4-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:fdfafb32984684eb03c2d83e1e51f64f0906b11e64482df3c5db936ce3839d48"}, + {file = "coverage-7.4.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:69eb372f7e2ece89f14751fbcbe470295d73ed41ecd37ca36ed2eb47512a6ab9"}, + {file = "coverage-7.4.4-cp312-cp312-win32.whl", hash = "sha256:137eb07173141545e07403cca94ab625cc1cc6bc4c1e97b6e3846270e7e1fea0"}, + {file = "coverage-7.4.4-cp312-cp312-win_amd64.whl", hash = "sha256:d71eec7d83298f1af3326ce0ff1d0ea83c7cb98f72b577097f9083b20bdaf05e"}, + {file = "coverage-7.4.4-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:d5ae728ff3b5401cc320d792866987e7e7e880e6ebd24433b70a33b643bb0384"}, + {file = "coverage-7.4.4-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:cc4f1358cb0c78edef3ed237ef2c86056206bb8d9140e73b6b89fbcfcbdd40e1"}, + {file = "coverage-7.4.4-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8130a2aa2acb8788e0b56938786c33c7c98562697bf9f4c7d6e8e5e3a0501e4a"}, + {file = "coverage-7.4.4-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:cf271892d13e43bc2b51e6908ec9a6a5094a4df1d8af0bfc360088ee6c684409"}, + {file = "coverage-7.4.4-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a4cdc86d54b5da0df6d3d3a2f0b710949286094c3a6700c21e9015932b81447e"}, + {file = "coverage-7.4.4-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:ae71e7ddb7a413dd60052e90528f2f65270aad4b509563af6d03d53e979feafd"}, + {file = "coverage-7.4.4-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:38dd60d7bf242c4ed5b38e094baf6401faa114fc09e9e6632374388a404f98e7"}, + {file = "coverage-7.4.4-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:aa5b1c1bfc28384f1f53b69a023d789f72b2e0ab1b3787aae16992a7ca21056c"}, + {file = "coverage-7.4.4-cp38-cp38-win32.whl", hash = "sha256:dfa8fe35a0bb90382837b238fff375de15f0dcdb9ae68ff85f7a63649c98527e"}, + {file = "coverage-7.4.4-cp38-cp38-win_amd64.whl", hash = "sha256:b2991665420a803495e0b90a79233c1433d6ed77ef282e8e152a324bbbc5e0c8"}, + {file = "coverage-7.4.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:3b799445b9f7ee8bf299cfaed6f5b226c0037b74886a4e11515e569b36fe310d"}, + {file = "coverage-7.4.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b4d33f418f46362995f1e9d4f3a35a1b6322cb959c31d88ae56b0298e1c22357"}, + {file = "coverage-7.4.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:aadacf9a2f407a4688d700e4ebab33a7e2e408f2ca04dbf4aef17585389eff3e"}, + {file = "coverage-7.4.4-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7c95949560050d04d46b919301826525597f07b33beba6187d04fa64d47ac82e"}, + {file = "coverage-7.4.4-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ff7687ca3d7028d8a5f0ebae95a6e4827c5616b31a4ee1192bdfde697db110d4"}, + {file = "coverage-7.4.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:5fc1de20b2d4a061b3df27ab9b7c7111e9a710f10dc2b84d33a4ab25065994ec"}, + {file = "coverage-7.4.4-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:c74880fc64d4958159fbd537a091d2a585448a8f8508bf248d72112723974cbd"}, + {file = "coverage-7.4.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:742a76a12aa45b44d236815d282b03cfb1de3b4323f3e4ec933acfae08e54ade"}, + {file = "coverage-7.4.4-cp39-cp39-win32.whl", hash = "sha256:d89d7b2974cae412400e88f35d86af72208e1ede1a541954af5d944a8ba46c57"}, + {file = "coverage-7.4.4-cp39-cp39-win_amd64.whl", hash = "sha256:9ca28a302acb19b6af89e90f33ee3e1906961f94b54ea37de6737b7ca9d8827c"}, + {file = "coverage-7.4.4-pp38.pp39.pp310-none-any.whl", hash = "sha256:b2c5edc4ac10a7ef6605a966c58929ec6c1bd0917fb8c15cb3363f65aa40e677"}, + {file = "coverage-7.4.4.tar.gz", hash = "sha256:c901df83d097649e257e803be22592aedfd5182f07b3cc87d640bbb9afd50f49"}, +] + +[package.dependencies] +tomli = {version = "*", optional = true, markers = "python_full_version <= \"3.11.0a6\" and extra == \"toml\""} + +[package.extras] +toml = ["tomli"] + +[[package]] +name = "crashtest" +version = "0.4.1" +description = "Manage Python errors with ease" +optional = false +python-versions = ">=3.7,<4.0" +files = [ + {file = "crashtest-0.4.1-py3-none-any.whl", hash = "sha256:8d23eac5fa660409f57472e3851dab7ac18aba459a8d19cbbba86d3d5aecd2a5"}, + {file = "crashtest-0.4.1.tar.gz", hash = "sha256:80d7b1f316ebfbd429f648076d6275c877ba30ba48979de4191714a75266f0ce"}, +] + +[[package]] +name = "cryptography" +version = "42.0.5" +description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." +optional = false +python-versions = ">=3.7" +files = [ + {file = "cryptography-42.0.5-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:a30596bae9403a342c978fb47d9b0ee277699fa53bbafad14706af51fe543d16"}, + {file = "cryptography-42.0.5-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:b7ffe927ee6531c78f81aa17e684e2ff617daeba7f189f911065b2ea2d526dec"}, + {file = "cryptography-42.0.5-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2424ff4c4ac7f6b8177b53c17ed5d8fa74ae5955656867f5a8affaca36a27abb"}, + {file = "cryptography-42.0.5-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:329906dcc7b20ff3cad13c069a78124ed8247adcac44b10bea1130e36caae0b4"}, + {file = "cryptography-42.0.5-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:b03c2ae5d2f0fc05f9a2c0c997e1bc18c8229f392234e8a0194f202169ccd278"}, + {file = "cryptography-42.0.5-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:f8837fe1d6ac4a8052a9a8ddab256bc006242696f03368a4009be7ee3075cdb7"}, + {file = "cryptography-42.0.5-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:0270572b8bd2c833c3981724b8ee9747b3ec96f699a9665470018594301439ee"}, + {file = "cryptography-42.0.5-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:b8cac287fafc4ad485b8a9b67d0ee80c66bf3574f655d3b97ef2e1082360faf1"}, + {file = "cryptography-42.0.5-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:16a48c23a62a2f4a285699dba2e4ff2d1cff3115b9df052cdd976a18856d8e3d"}, + {file = "cryptography-42.0.5-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:2bce03af1ce5a5567ab89bd90d11e7bbdff56b8af3acbbec1faded8f44cb06da"}, + {file = "cryptography-42.0.5-cp37-abi3-win32.whl", hash = "sha256:b6cd2203306b63e41acdf39aa93b86fb566049aeb6dc489b70e34bcd07adca74"}, + {file = "cryptography-42.0.5-cp37-abi3-win_amd64.whl", hash = "sha256:98d8dc6d012b82287f2c3d26ce1d2dd130ec200c8679b6213b3c73c08b2b7940"}, + {file = "cryptography-42.0.5-cp39-abi3-macosx_10_12_universal2.whl", hash = "sha256:5e6275c09d2badf57aea3afa80d975444f4be8d3bc58f7f80d2a484c6f9485c8"}, + {file = "cryptography-42.0.5-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e4985a790f921508f36f81831817cbc03b102d643b5fcb81cd33df3fa291a1a1"}, + {file = "cryptography-42.0.5-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7cde5f38e614f55e28d831754e8a3bacf9ace5d1566235e39d91b35502d6936e"}, + {file = "cryptography-42.0.5-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:7367d7b2eca6513681127ebad53b2582911d1736dc2ffc19f2c3ae49997496bc"}, + {file = "cryptography-42.0.5-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:cd2030f6650c089aeb304cf093f3244d34745ce0cfcc39f20c6fbfe030102e2a"}, + {file = "cryptography-42.0.5-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:a2913c5375154b6ef2e91c10b5720ea6e21007412f6437504ffea2109b5a33d7"}, + {file = "cryptography-42.0.5-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:c41fb5e6a5fe9ebcd58ca3abfeb51dffb5d83d6775405305bfa8715b76521922"}, + {file = "cryptography-42.0.5-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:3eaafe47ec0d0ffcc9349e1708be2aaea4c6dd4978d76bf6eb0cb2c13636c6fc"}, + {file = "cryptography-42.0.5-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:1b95b98b0d2af784078fa69f637135e3c317091b615cd0905f8b8a087e86fa30"}, + {file = "cryptography-42.0.5-cp39-abi3-win32.whl", hash = "sha256:1f71c10d1e88467126f0efd484bd44bca5e14c664ec2ede64c32f20875c0d413"}, + {file = "cryptography-42.0.5-cp39-abi3-win_amd64.whl", hash = "sha256:a011a644f6d7d03736214d38832e030d8268bcff4a41f728e6030325fea3e400"}, + {file = "cryptography-42.0.5-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:9481ffe3cf013b71b2428b905c4f7a9a4f76ec03065b05ff499bb5682a8d9ad8"}, + {file = "cryptography-42.0.5-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:ba334e6e4b1d92442b75ddacc615c5476d4ad55cc29b15d590cc6b86efa487e2"}, + {file = "cryptography-42.0.5-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:ba3e4a42397c25b7ff88cdec6e2a16c2be18720f317506ee25210f6d31925f9c"}, + {file = "cryptography-42.0.5-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:111a0d8553afcf8eb02a4fea6ca4f59d48ddb34497aa8706a6cf536f1a5ec576"}, + {file = "cryptography-42.0.5-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:cd65d75953847815962c84a4654a84850b2bb4aed3f26fadcc1c13892e1e29f6"}, + {file = "cryptography-42.0.5-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:e807b3188f9eb0eaa7bbb579b462c5ace579f1cedb28107ce8b48a9f7ad3679e"}, + {file = "cryptography-42.0.5-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:f12764b8fffc7a123f641d7d049d382b73f96a34117e0b637b80643169cec8ac"}, + {file = "cryptography-42.0.5-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:37dd623507659e08be98eec89323469e8c7b4c1407c85112634ae3dbdb926fdd"}, + {file = "cryptography-42.0.5.tar.gz", hash = "sha256:6fe07eec95dfd477eb9530aef5bead34fec819b3aaf6c5bd6d20565da607bfe1"}, +] + +[package.dependencies] +cffi = {version = ">=1.12", markers = "platform_python_implementation != \"PyPy\""} + +[package.extras] +docs = ["sphinx (>=5.3.0)", "sphinx-rtd-theme (>=1.1.1)"] +docstest = ["pyenchant (>=1.6.11)", "readme-renderer", "sphinxcontrib-spelling (>=4.0.1)"] +nox = ["nox"] +pep8test = ["check-sdist", "click", "mypy", "ruff"] +sdist = ["build"] +ssh = ["bcrypt (>=3.1.5)"] +test = ["certifi", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-xdist"] +test-randomorder = ["pytest-randomly"] + +[[package]] +name = "dill" +version = "0.3.8" +description = "serialize all of Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "dill-0.3.8-py3-none-any.whl", hash = "sha256:c36ca9ffb54365bdd2f8eb3eff7d2a21237f8452b57ace88b1ac615b7e815bd7"}, + {file = "dill-0.3.8.tar.gz", hash = "sha256:3ebe3c479ad625c4553aca177444d89b486b1d84982eeacded644afc0cf797ca"}, +] + +[package.extras] +graph = ["objgraph (>=1.7.2)"] +profile = ["gprof2dot (>=2022.7.29)"] + +[[package]] +name = "distlib" +version = "0.3.8" +description = "Distribution utilities" +optional = false +python-versions = "*" +files = [ + {file = "distlib-0.3.8-py2.py3-none-any.whl", hash = "sha256:034db59a0b96f8ca18035f36290806a9a6e6bd9d1ff91e45a7f172eb17e51784"}, + {file = "distlib-0.3.8.tar.gz", hash = "sha256:1530ea13e350031b6312d8580ddb6b27a104275a31106523b8f123787f494f64"}, +] + +[[package]] +name = "docutils" +version = "0.21.1" +description = "Docutils -- Python Documentation Utilities" +optional = false +python-versions = ">=3.9" +files = [ + {file = "docutils-0.21.1-py3-none-any.whl", hash = "sha256:14c8d34a55b46c88f9f714adb29cefbdd69fb82f3fef825e59c5faab935390d8"}, + {file = "docutils-0.21.1.tar.gz", hash = "sha256:65249d8a5345bc95e0f40f280ba63c98eb24de35c6c8f5b662e3e8948adea83f"}, +] + +[[package]] +name = "dulwich" +version = "0.21.7" +description = "Python Git Library" +optional = false +python-versions = ">=3.7" +files = [ + {file = "dulwich-0.21.7-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:d4c0110798099bb7d36a110090f2688050703065448895c4f53ade808d889dd3"}, + {file = "dulwich-0.21.7-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2bc12697f0918bee324c18836053644035362bb3983dc1b210318f2fed1d7132"}, + {file = "dulwich-0.21.7-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:471305af74790827fcbafe330fc2e8bdcee4fb56ca1177c8c481b1c8f806c4a4"}, + {file = "dulwich-0.21.7-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d54c9d0e845be26f65f954dff13a1cd3f2b9739820c19064257b8fd7435ab263"}, + {file = "dulwich-0.21.7-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:12d61334a575474e707614f2e93d6ed4cdae9eb47214f9277076d9e5615171d3"}, + {file = "dulwich-0.21.7-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:e274cebaf345f0b1e3b70197f2651de92b652386b68020cfd3bf61bc30f6eaaa"}, + {file = "dulwich-0.21.7-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:817822f970e196e757ae01281ecbf21369383285b9f4a83496312204cf889b8c"}, + {file = "dulwich-0.21.7-cp310-cp310-win32.whl", hash = "sha256:7836da3f4110ce684dcd53489015fb7fa94ed33c5276e3318b8b1cbcb5b71e08"}, + {file = "dulwich-0.21.7-cp310-cp310-win_amd64.whl", hash = "sha256:4a043b90958cec866b4edc6aef5fe3c2c96a664d0b357e1682a46f6c477273c4"}, + {file = "dulwich-0.21.7-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:ce8db196e79c1f381469410d26fb1d8b89c6b87a4e7f00ff418c22a35121405c"}, + {file = "dulwich-0.21.7-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:62bfb26bdce869cd40be443dfd93143caea7089b165d2dcc33de40f6ac9d812a"}, + {file = "dulwich-0.21.7-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:c01a735b9a171dcb634a97a3cec1b174cfbfa8e840156870384b633da0460f18"}, + {file = "dulwich-0.21.7-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fa4d14767cf7a49c9231c2e52cb2a3e90d0c83f843eb6a2ca2b5d81d254cf6b9"}, + {file = "dulwich-0.21.7-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7bca4b86e96d6ef18c5bc39828ea349efb5be2f9b1f6ac9863f90589bac1084d"}, + {file = "dulwich-0.21.7-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:a7b5624b02ef808cdc62dabd47eb10cd4ac15e8ac6df9e2e88b6ac6b40133673"}, + {file = "dulwich-0.21.7-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c3a539b4696a42fbdb7412cb7b66a4d4d332761299d3613d90a642923c7560e1"}, + {file = "dulwich-0.21.7-cp311-cp311-win32.whl", hash = "sha256:675a612ce913081beb0f37b286891e795d905691dfccfb9bf73721dca6757cde"}, + {file = "dulwich-0.21.7-cp311-cp311-win_amd64.whl", hash = "sha256:460ba74bdb19f8d498786ae7776745875059b1178066208c0fd509792d7f7bfc"}, + {file = "dulwich-0.21.7-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:4c51058ec4c0b45dc5189225b9e0c671b96ca9713c1daf71d622c13b0ab07681"}, + {file = "dulwich-0.21.7-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:4bc4c5366eaf26dda3fdffe160a3b515666ed27c2419f1d483da285ac1411de0"}, + {file = "dulwich-0.21.7-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:a0650ec77d89cb947e3e4bbd4841c96f74e52b4650830112c3057a8ca891dc2f"}, + {file = "dulwich-0.21.7-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:4f18f0a311fb7734b033a3101292b932158cade54b74d1c44db519e42825e5a2"}, + {file = "dulwich-0.21.7-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c589468e5c0cd84e97eb7ec209ab005a2cb69399e8c5861c3edfe38989ac3a8"}, + {file = "dulwich-0.21.7-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d62446797163317a397a10080c6397ffaaca51a7804c0120b334f8165736c56a"}, + {file = "dulwich-0.21.7-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:e84cc606b1f581733df4350ca4070e6a8b30be3662bbb81a590b177d0c996c91"}, + {file = "dulwich-0.21.7-cp312-cp312-win32.whl", hash = "sha256:c3d1685f320907a52c40fd5890627945c51f3a5fa4bcfe10edb24fec79caadec"}, + {file = "dulwich-0.21.7-cp312-cp312-win_amd64.whl", hash = "sha256:6bd69921fdd813b7469a3c77bc75c1783cc1d8d72ab15a406598e5a3ba1a1503"}, + {file = "dulwich-0.21.7-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:7d8ab29c660125db52106775caa1f8f7f77a69ed1fe8bc4b42bdf115731a25bf"}, + {file = "dulwich-0.21.7-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:b0d2e4485b98695bf95350ce9d38b1bb0aaac2c34ad00a0df789aa33c934469b"}, + {file = "dulwich-0.21.7-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e138d516baa6b5bafbe8f030eccc544d0d486d6819b82387fc0e285e62ef5261"}, + {file = "dulwich-0.21.7-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:f34bf9b9fa9308376263fd9ac43143c7c09da9bc75037bb75c6c2423a151b92c"}, + {file = "dulwich-0.21.7-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:2e2c66888207b71cd1daa2acb06d3984a6bc13787b837397a64117aa9fc5936a"}, + {file = "dulwich-0.21.7-cp37-cp37m-win32.whl", hash = "sha256:10893105c6566fc95bc2a67b61df7cc1e8f9126d02a1df6a8b2b82eb59db8ab9"}, + {file = "dulwich-0.21.7-cp37-cp37m-win_amd64.whl", hash = "sha256:460b3849d5c3d3818a80743b4f7a0094c893c559f678e56a02fff570b49a644a"}, + {file = "dulwich-0.21.7-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:74700e4c7d532877355743336c36f51b414d01e92ba7d304c4f8d9a5946dbc81"}, + {file = "dulwich-0.21.7-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:c92e72c43c9e9e936b01a57167e0ea77d3fd2d82416edf9489faa87278a1cdf7"}, + {file = "dulwich-0.21.7-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:d097e963eb6b9fa53266146471531ad9c6765bf390849230311514546ed64db2"}, + {file = "dulwich-0.21.7-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:808e8b9cc0aa9ac74870b49db4f9f39a52fb61694573f84b9c0613c928d4caf8"}, + {file = "dulwich-0.21.7-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e1957b65f96e36c301e419d7adaadcff47647c30eb072468901bb683b1000bc5"}, + {file = "dulwich-0.21.7-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:4b09bc3a64fb70132ec14326ecbe6e0555381108caff3496898962c4136a48c6"}, + {file = "dulwich-0.21.7-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:d5882e70b74ac3c736a42d3fdd4f5f2e6570637f59ad5d3e684760290b58f041"}, + {file = "dulwich-0.21.7-cp38-cp38-win32.whl", hash = "sha256:29bb5c1d70eba155ded41ed8a62be2f72edbb3c77b08f65b89c03976292f6d1b"}, + {file = "dulwich-0.21.7-cp38-cp38-win_amd64.whl", hash = "sha256:25c3ab8fb2e201ad2031ddd32e4c68b7c03cb34b24a5ff477b7a7dcef86372f5"}, + {file = "dulwich-0.21.7-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:8929c37986c83deb4eb500c766ee28b6670285b512402647ee02a857320e377c"}, + {file = "dulwich-0.21.7-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:cc1e11be527ac06316539b57a7688bcb1b6a3e53933bc2f844397bc50734e9ae"}, + {file = "dulwich-0.21.7-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:0fc3078a1ba04c588fabb0969d3530efd5cd1ce2cf248eefb6baf7cbc15fc285"}, + {file = "dulwich-0.21.7-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:40dcbd29ba30ba2c5bfbab07a61a5f20095541d5ac66d813056c122244df4ac0"}, + {file = "dulwich-0.21.7-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8869fc8ec3dda743e03d06d698ad489b3705775fe62825e00fa95aa158097fc0"}, + {file = "dulwich-0.21.7-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d96ca5e0dde49376fbcb44f10eddb6c30284a87bd03bb577c59bb0a1f63903fa"}, + {file = "dulwich-0.21.7-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:e0064363bd5e814359657ae32517fa8001e8573d9d040bd997908d488ab886ed"}, + {file = "dulwich-0.21.7-cp39-cp39-win32.whl", hash = "sha256:869eb7be48243e695673b07905d18b73d1054a85e1f6e298fe63ba2843bb2ca1"}, + {file = "dulwich-0.21.7-cp39-cp39-win_amd64.whl", hash = "sha256:404b8edeb3c3a86c47c0a498699fc064c93fa1f8bab2ffe919e8ab03eafaaad3"}, + {file = "dulwich-0.21.7-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:e598d743c6c0548ebcd2baf94aa9c8bfacb787ea671eeeb5828cfbd7d56b552f"}, + {file = "dulwich-0.21.7-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d4a2d76c96426e791556836ef43542b639def81be4f1d6d4322cd886c115eae1"}, + {file = "dulwich-0.21.7-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f6c88acb60a1f4d31bd6d13bfba465853b3df940ee4a0f2a3d6c7a0778c705b7"}, + {file = "dulwich-0.21.7-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:ecd315847dea406a4decfa39d388a2521e4e31acde3bd9c2609c989e817c6d62"}, + {file = "dulwich-0.21.7-pp37-pypy37_pp73-macosx_10_9_x86_64.whl", hash = "sha256:d05d3c781bc74e2c2a2a8f4e4e2ed693540fbe88e6ac36df81deac574a6dad99"}, + {file = "dulwich-0.21.7-pp37-pypy37_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6de6f8de4a453fdbae8062a6faa652255d22a3d8bce0cd6d2d6701305c75f2b3"}, + {file = "dulwich-0.21.7-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e25953c7acbbe4e19650d0225af1c0c0e6882f8bddd2056f75c1cc2b109b88ad"}, + {file = "dulwich-0.21.7-pp37-pypy37_pp73-win_amd64.whl", hash = "sha256:4637cbd8ed1012f67e1068aaed19fcc8b649bcf3e9e26649826a303298c89b9d"}, + {file = "dulwich-0.21.7-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:858842b30ad6486aacaa607d60bab9c9a29e7c59dc2d9cb77ae5a94053878c08"}, + {file = "dulwich-0.21.7-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:739b191f61e1c4ce18ac7d520e7a7cbda00e182c3489552408237200ce8411ad"}, + {file = "dulwich-0.21.7-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:274c18ec3599a92a9b67abaf110e4f181a4f779ee1aaab9e23a72e89d71b2bd9"}, + {file = "dulwich-0.21.7-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:2590e9b431efa94fc356ae33b38f5e64f1834ec3a94a6ac3a64283b206d07aa3"}, + {file = "dulwich-0.21.7-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:ed60d1f610ef6437586f7768254c2a93820ccbd4cfdac7d182cf2d6e615969bb"}, + {file = "dulwich-0.21.7-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8278835e168dd097089f9e53088c7a69c6ca0841aef580d9603eafe9aea8c358"}, + {file = "dulwich-0.21.7-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ffc27fb063f740712e02b4d2f826aee8bbed737ed799962fef625e2ce56e2d29"}, + {file = "dulwich-0.21.7-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:61e3451bd3d3844f2dca53f131982553be4d1b1e1ebd9db701843dd76c4dba31"}, + {file = "dulwich-0.21.7.tar.gz", hash = "sha256:a9e9c66833cea580c3ac12927e4b9711985d76afca98da971405d414de60e968"}, +] + +[package.dependencies] +urllib3 = ">=1.25" + +[package.extras] +fastimport = ["fastimport"] +https = ["urllib3 (>=1.24.1)"] +paramiko = ["paramiko"] +pgp = ["gpg"] + +[[package]] +name = "exceptiongroup" +version = "1.2.1" +description = "Backport of PEP 654 (exception groups)" +optional = false +python-versions = ">=3.7" +files = [ + {file = "exceptiongroup-1.2.1-py3-none-any.whl", hash = "sha256:5258b9ed329c5bbdd31a309f53cbfb0b155341807f6ff7606a1e801a891b29ad"}, + {file = "exceptiongroup-1.2.1.tar.gz", hash = "sha256:a4785e48b045528f5bfe627b6ad554ff32def154f42372786903b7abcfe1aa16"}, +] + +[package.extras] +test = ["pytest (>=6)"] + +[[package]] +name = "fastjsonschema" +version = "2.19.1" +description = "Fastest Python implementation of JSON schema" +optional = false +python-versions = "*" +files = [ + {file = "fastjsonschema-2.19.1-py3-none-any.whl", hash = "sha256:3672b47bc94178c9f23dbb654bf47440155d4db9df5f7bc47643315f9c405cd0"}, + {file = "fastjsonschema-2.19.1.tar.gz", hash = "sha256:e3126a94bdc4623d3de4485f8d468a12f02a67921315ddc87836d6e456dc789d"}, +] + +[package.extras] +devel = ["colorama", "json-spec", "jsonschema", "pylint", "pytest", "pytest-benchmark", "pytest-cache", "validictory"] + +[[package]] +name = "filelock" +version = "3.13.4" +description = "A platform independent file lock." +optional = false +python-versions = ">=3.8" +files = [ + {file = "filelock-3.13.4-py3-none-any.whl", hash = "sha256:404e5e9253aa60ad457cae1be07c0f0ca90a63931200a47d9b6a6af84fd7b45f"}, + {file = "filelock-3.13.4.tar.gz", hash = "sha256:d13f466618bfde72bd2c18255e269f72542c6e70e7bac83a0232d6b1cc5c8cf4"}, +] + +[package.extras] +docs = ["furo (>=2023.9.10)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"] +testing = ["covdefaults (>=2.3)", "coverage (>=7.3.2)", "diff-cover (>=8.0.1)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)", "pytest-timeout (>=2.2)"] +typing = ["typing-extensions (>=4.8)"] + +[[package]] +name = "flake8" +version = "7.0.0" +description = "the modular source code checker: pep8 pyflakes and co" +optional = false +python-versions = ">=3.8.1" +files = [ + {file = "flake8-7.0.0-py2.py3-none-any.whl", hash = "sha256:a6dfbb75e03252917f2473ea9653f7cd799c3064e54d4c8140044c5c065f53c3"}, + {file = "flake8-7.0.0.tar.gz", hash = "sha256:33f96621059e65eec474169085dc92bf26e7b2d47366b70be2f67ab80dc25132"}, +] + +[package.dependencies] +mccabe = ">=0.7.0,<0.8.0" +pycodestyle = ">=2.11.0,<2.12.0" +pyflakes = ">=3.2.0,<3.3.0" + +[[package]] +name = "identify" +version = "2.5.35" +description = "File identification library for Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "identify-2.5.35-py2.py3-none-any.whl", hash = "sha256:c4de0081837b211594f8e877a6b4fad7ca32bbfc1a9307fdd61c28bfe923f13e"}, + {file = "identify-2.5.35.tar.gz", hash = "sha256:10a7ca245cfcd756a554a7288159f72ff105ad233c7c4b9c6f0f4d108f5f6791"}, +] + +[package.extras] +license = ["ukkonen"] + +[[package]] +name = "idna" +version = "3.7" +description = "Internationalized Domain Names in Applications (IDNA)" +optional = false +python-versions = ">=3.5" +files = [ + {file = "idna-3.7-py3-none-any.whl", hash = "sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0"}, + {file = "idna-3.7.tar.gz", hash = "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc"}, +] + +[[package]] +name = "imagesize" +version = "1.4.1" +description = "Getting image size from png/jpeg/jpeg2000/gif file" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "imagesize-1.4.1-py2.py3-none-any.whl", hash = "sha256:0d8d18d08f840c19d0ee7ca1fd82490fdc3729b7ac93f49870406ddde8ef8d8b"}, + {file = "imagesize-1.4.1.tar.gz", hash = "sha256:69150444affb9cb0d5cc5a92b3676f0b2fb7cd9ae39e947a5e11a36b4497cd4a"}, +] + +[[package]] +name = "importlib-metadata" +version = "7.1.0" +description = "Read metadata from Python packages" +optional = false +python-versions = ">=3.8" +files = [ + {file = "importlib_metadata-7.1.0-py3-none-any.whl", hash = "sha256:30962b96c0c223483ed6cc7280e7f0199feb01a0e40cfae4d4450fc6fab1f570"}, + {file = "importlib_metadata-7.1.0.tar.gz", hash = "sha256:b78938b926ee8d5f020fc4772d487045805a55ddbad2ecf21c6d60938dc7fcd2"}, +] + +[package.dependencies] +zipp = ">=0.5" + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +perf = ["ipython"] +testing = ["flufl.flake8", "importlib-resources (>=1.3)", "jaraco.test (>=5.4)", "packaging", "pyfakefs", "pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-perf (>=0.9.2)", "pytest-ruff (>=0.2.1)"] + +[[package]] +name = "iniconfig" +version = "2.0.0" +description = "brain-dead simple config-ini parsing" +optional = false +python-versions = ">=3.7" +files = [ + {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, + {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, +] + +[[package]] +name = "installer" +version = "0.7.0" +description = "A library for installing Python wheels." +optional = false +python-versions = ">=3.7" +files = [ + {file = "installer-0.7.0-py3-none-any.whl", hash = "sha256:05d1933f0a5ba7d8d6296bb6d5018e7c94fa473ceb10cf198a92ccea19c27b53"}, + {file = "installer-0.7.0.tar.gz", hash = "sha256:a26d3e3116289bb08216e0d0f7d925fcef0b0194eedfa0c944bcaaa106c4b631"}, +] + +[[package]] +name = "isort" +version = "5.13.2" +description = "A Python utility / library to sort Python imports." +optional = false +python-versions = ">=3.8.0" +files = [ + {file = "isort-5.13.2-py3-none-any.whl", hash = "sha256:8ca5e72a8d85860d5a3fa69b8745237f2939afe12dbf656afbcb47fe72d947a6"}, + {file = "isort-5.13.2.tar.gz", hash = "sha256:48fdfcb9face5d58a4f6dde2e72a1fb8dcaf8ab26f95ab49fab84c2ddefb0109"}, +] + +[package.extras] +colors = ["colorama (>=0.4.6)"] + +[[package]] +name = "jaraco-classes" +version = "3.4.0" +description = "Utility functions for Python class constructs" +optional = false +python-versions = ">=3.8" +files = [ + {file = "jaraco.classes-3.4.0-py3-none-any.whl", hash = "sha256:f662826b6bed8cace05e7ff873ce0f9283b5c924470fe664fff1c2f00f581790"}, + {file = "jaraco.classes-3.4.0.tar.gz", hash = "sha256:47a024b51d0239c0dd8c8540c6c7f484be3b8fcf0b2d85c13825780d3b3f3acd"}, +] + +[package.dependencies] +more-itertools = "*" + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-ruff (>=0.2.1)"] + +[[package]] +name = "jeepney" +version = "0.8.0" +description = "Low-level, pure Python DBus protocol wrapper." +optional = false +python-versions = ">=3.7" +files = [ + {file = "jeepney-0.8.0-py3-none-any.whl", hash = "sha256:c0a454ad016ca575060802ee4d590dd912e35c122fa04e70306de3d076cce755"}, + {file = "jeepney-0.8.0.tar.gz", hash = "sha256:5efe48d255973902f6badc3ce55e2aa6c5c3b3bc642059ef3a91247bcfcc5806"}, +] + +[package.extras] +test = ["async-timeout", "pytest", "pytest-asyncio (>=0.17)", "pytest-trio", "testpath", "trio"] +trio = ["async_generator", "trio"] + +[[package]] +name = "jinja2" +version = "3.1.3" +description = "A very fast and expressive template engine." +optional = false +python-versions = ">=3.7" +files = [ + {file = "Jinja2-3.1.3-py3-none-any.whl", hash = "sha256:7d6d50dd97d52cbc355597bd845fabfbac3f551e1f99619e39a35ce8c370b5fa"}, + {file = "Jinja2-3.1.3.tar.gz", hash = "sha256:ac8bd6544d4bb2c9792bf3a159e80bba8fda7f07e81bc3aed565432d5925ba90"}, +] + +[package.dependencies] +MarkupSafe = ">=2.0" + +[package.extras] +i18n = ["Babel (>=2.7)"] + +[[package]] +name = "jxmlease" +version = "1.0.3" +description = "jxmlease converts between XML and intelligent Python data structures." +optional = false +python-versions = "*" +files = [ + {file = "jxmlease-1.0.3-py2.py3-none-any.whl", hash = "sha256:6bbfaee0ecf7e287667c9b33fa70c2650265dcbf01518a2531a46b921c17cdaf"}, + {file = "jxmlease-1.0.3.tar.gz", hash = "sha256:612c1575d8a87026dea096bb75acec7302dd69040fa23d9116e71e30d5e0839e"}, +] + +[[package]] +name = "keyring" +version = "24.3.1" +description = "Store and access your passwords safely." +optional = false +python-versions = ">=3.8" +files = [ + {file = "keyring-24.3.1-py3-none-any.whl", hash = "sha256:df38a4d7419a6a60fea5cef1e45a948a3e8430dd12ad88b0f423c5c143906218"}, + {file = "keyring-24.3.1.tar.gz", hash = "sha256:c3327b6ffafc0e8befbdb597cacdb4928ffe5c1212f7645f186e6d9957a898db"}, +] + +[package.dependencies] +importlib-metadata = {version = ">=4.11.4", markers = "python_version < \"3.12\""} +"jaraco.classes" = "*" +jeepney = {version = ">=0.4.2", markers = "sys_platform == \"linux\""} +pywin32-ctypes = {version = ">=0.2.0", markers = "sys_platform == \"win32\""} +SecretStorage = {version = ">=3.2", markers = "sys_platform == \"linux\""} + +[package.extras] +completion = ["shtab (>=1.1.0)"] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-lint"] +testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-mypy", "pytest-ruff (>=0.2.1)"] + +[[package]] +name = "markupsafe" +version = "2.1.5" +description = "Safely add untrusted strings to HTML/XML markup." +optional = false +python-versions = ">=3.7" +files = [ + {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:a17a92de5231666cfbe003f0e4b9b3a7ae3afb1ec2845aadc2bacc93ff85febc"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:72b6be590cc35924b02c78ef34b467da4ba07e4e0f0454a2c5907f473fc50ce5"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e61659ba32cf2cf1481e575d0462554625196a1f2fc06a1c777d3f48e8865d46"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2174c595a0d73a3080ca3257b40096db99799265e1c27cc5a610743acd86d62f"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ae2ad8ae6ebee9d2d94b17fb62763125f3f374c25618198f40cbb8b525411900"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:075202fa5b72c86ad32dc7d0b56024ebdbcf2048c0ba09f1cde31bfdd57bcfff"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:598e3276b64aff0e7b3451b72e94fa3c238d452e7ddcd893c3ab324717456bad"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fce659a462a1be54d2ffcacea5e3ba2d74daa74f30f5f143fe0c58636e355fdd"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-win32.whl", hash = "sha256:d9fad5155d72433c921b782e58892377c44bd6252b5af2f67f16b194987338a4"}, + {file = "MarkupSafe-2.1.5-cp310-cp310-win_amd64.whl", hash = "sha256:bf50cd79a75d181c9181df03572cdce0fbb75cc353bc350712073108cba98de5"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:629ddd2ca402ae6dbedfceeba9c46d5f7b2a61d9749597d4307f943ef198fc1f"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:5b7b716f97b52c5a14bffdf688f971b2d5ef4029127f1ad7a513973cfd818df2"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6ec585f69cec0aa07d945b20805be741395e28ac1627333b1c5b0105962ffced"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b91c037585eba9095565a3556f611e3cbfaa42ca1e865f7b8015fe5c7336d5a5"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7502934a33b54030eaf1194c21c692a534196063db72176b0c4028e140f8f32c"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:0e397ac966fdf721b2c528cf028494e86172b4feba51d65f81ffd65c63798f3f"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c061bb86a71b42465156a3ee7bd58c8c2ceacdbeb95d05a99893e08b8467359a"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:3a57fdd7ce31c7ff06cdfbf31dafa96cc533c21e443d57f5b1ecc6cdc668ec7f"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-win32.whl", hash = "sha256:397081c1a0bfb5124355710fe79478cdbeb39626492b15d399526ae53422b906"}, + {file = "MarkupSafe-2.1.5-cp311-cp311-win_amd64.whl", hash = "sha256:2b7c57a4dfc4f16f7142221afe5ba4e093e09e728ca65c51f5620c9aaeb9a617"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:8dec4936e9c3100156f8a2dc89c4b88d5c435175ff03413b443469c7c8c5f4d1"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:3c6b973f22eb18a789b1460b4b91bf04ae3f0c4234a0a6aa6b0a92f6f7b951d4"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ac07bad82163452a6884fe8fa0963fb98c2346ba78d779ec06bd7a6262132aee"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5dfb42c4604dddc8e4305050aa6deb084540643ed5804d7455b5df8fe16f5e5"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ea3d8a3d18833cf4304cd2fc9cbb1efe188ca9b5efef2bdac7adc20594a0e46b"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:d050b3361367a06d752db6ead6e7edeb0009be66bc3bae0ee9d97fb326badc2a"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:bec0a414d016ac1a18862a519e54b2fd0fc8bbfd6890376898a6c0891dd82e9f"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:58c98fee265677f63a4385256a6d7683ab1832f3ddd1e66fe948d5880c21a169"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-win32.whl", hash = "sha256:8590b4ae07a35970728874632fed7bd57b26b0102df2d2b233b6d9d82f6c62ad"}, + {file = "MarkupSafe-2.1.5-cp312-cp312-win_amd64.whl", hash = "sha256:823b65d8706e32ad2df51ed89496147a42a2a6e01c13cfb6ffb8b1e92bc910bb"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:c8b29db45f8fe46ad280a7294f5c3ec36dbac9491f2d1c17345be8e69cc5928f"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ec6a563cff360b50eed26f13adc43e61bc0c04d94b8be985e6fb24b81f6dcfdf"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a549b9c31bec33820e885335b451286e2969a2d9e24879f83fe904a5ce59d70a"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4f11aa001c540f62c6166c7726f71f7573b52c68c31f014c25cc7901deea0b52"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:7b2e5a267c855eea6b4283940daa6e88a285f5f2a67f2220203786dfa59b37e9"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:2d2d793e36e230fd32babe143b04cec8a8b3eb8a3122d2aceb4a371e6b09b8df"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:ce409136744f6521e39fd8e2a24c53fa18ad67aa5bc7c2cf83645cce5b5c4e50"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-win32.whl", hash = "sha256:4096e9de5c6fdf43fb4f04c26fb114f61ef0bf2e5604b6ee3019d51b69e8c371"}, + {file = "MarkupSafe-2.1.5-cp37-cp37m-win_amd64.whl", hash = "sha256:4275d846e41ecefa46e2015117a9f491e57a71ddd59bbead77e904dc02b1bed2"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:656f7526c69fac7f600bd1f400991cc282b417d17539a1b228617081106feb4a"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:97cafb1f3cbcd3fd2b6fbfb99ae11cdb14deea0736fc2b0952ee177f2b813a46"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1f3fbcb7ef1f16e48246f704ab79d79da8a46891e2da03f8783a5b6fa41a9532"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fa9db3f79de01457b03d4f01b34cf91bc0048eb2c3846ff26f66687c2f6d16ab"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ffee1f21e5ef0d712f9033568f8344d5da8cc2869dbd08d87c84656e6a2d2f68"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:5dedb4db619ba5a2787a94d877bc8ffc0566f92a01c0ef214865e54ecc9ee5e0"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:30b600cf0a7ac9234b2638fbc0fb6158ba5bdcdf46aeb631ead21248b9affbc4"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:8dd717634f5a044f860435c1d8c16a270ddf0ef8588d4887037c5028b859b0c3"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-win32.whl", hash = "sha256:daa4ee5a243f0f20d528d939d06670a298dd39b1ad5f8a72a4275124a7819eff"}, + {file = "MarkupSafe-2.1.5-cp38-cp38-win_amd64.whl", hash = "sha256:619bc166c4f2de5caa5a633b8b7326fbe98e0ccbfacabd87268a2b15ff73a029"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:7a68b554d356a91cce1236aa7682dc01df0edba8d043fd1ce607c49dd3c1edcf"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:db0b55e0f3cc0be60c1f19efdde9a637c32740486004f20d1cff53c3c0ece4d2"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3e53af139f8579a6d5f7b76549125f0d94d7e630761a2111bc431fd820e163b8"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:17b950fccb810b3293638215058e432159d2b71005c74371d784862b7e4683f3"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4c31f53cdae6ecfa91a77820e8b151dba54ab528ba65dfd235c80b086d68a465"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:bff1b4290a66b490a2f4719358c0cdcd9bafb6b8f061e45c7a2460866bf50c2e"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:bc1667f8b83f48511b94671e0e441401371dfd0f0a795c7daa4a3cd1dde55bea"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5049256f536511ee3f7e1b3f87d1d1209d327e818e6ae1365e8653d7e3abb6a6"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-win32.whl", hash = "sha256:00e046b6dd71aa03a41079792f8473dc494d564611a8f89bbbd7cb93295ebdcf"}, + {file = "MarkupSafe-2.1.5-cp39-cp39-win_amd64.whl", hash = "sha256:fa173ec60341d6bb97a89f5ea19c85c5643c1e7dedebc22f5181eb73573142c5"}, + {file = "MarkupSafe-2.1.5.tar.gz", hash = "sha256:d283d37a890ba4c1ae73ffadf8046435c76e7bc2247bbb63c00bd1a709c6544b"}, +] + +[[package]] +name = "mccabe" +version = "0.7.0" +description = "McCabe checker, plugin for flake8" +optional = false +python-versions = ">=3.6" +files = [ + {file = "mccabe-0.7.0-py2.py3-none-any.whl", hash = "sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e"}, + {file = "mccabe-0.7.0.tar.gz", hash = "sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325"}, +] + +[[package]] +name = "more-itertools" +version = "10.2.0" +description = "More routines for operating on iterables, beyond itertools" +optional = false +python-versions = ">=3.8" +files = [ + {file = "more-itertools-10.2.0.tar.gz", hash = "sha256:8fccb480c43d3e99a00087634c06dd02b0d50fbf088b380de5a41a015ec239e1"}, + {file = "more_itertools-10.2.0-py3-none-any.whl", hash = "sha256:686b06abe565edfab151cb8fd385a05651e1fdf8f0a14191e4439283421f8684"}, +] + +[[package]] +name = "msgpack" +version = "1.0.8" +description = "MessagePack serializer" +optional = false +python-versions = ">=3.8" +files = [ + {file = "msgpack-1.0.8-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:505fe3d03856ac7d215dbe005414bc28505d26f0c128906037e66d98c4e95868"}, + {file = "msgpack-1.0.8-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e6b7842518a63a9f17107eb176320960ec095a8ee3b4420b5f688e24bf50c53c"}, + {file = "msgpack-1.0.8-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:376081f471a2ef24828b83a641a02c575d6103a3ad7fd7dade5486cad10ea659"}, + {file = "msgpack-1.0.8-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5e390971d082dba073c05dbd56322427d3280b7cc8b53484c9377adfbae67dc2"}, + {file = "msgpack-1.0.8-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:00e073efcba9ea99db5acef3959efa45b52bc67b61b00823d2a1a6944bf45982"}, + {file = "msgpack-1.0.8-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:82d92c773fbc6942a7a8b520d22c11cfc8fd83bba86116bfcf962c2f5c2ecdaa"}, + {file = "msgpack-1.0.8-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:9ee32dcb8e531adae1f1ca568822e9b3a738369b3b686d1477cbc643c4a9c128"}, + {file = "msgpack-1.0.8-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e3aa7e51d738e0ec0afbed661261513b38b3014754c9459508399baf14ae0c9d"}, + {file = "msgpack-1.0.8-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:69284049d07fce531c17404fcba2bb1df472bc2dcdac642ae71a2d079d950653"}, + {file = "msgpack-1.0.8-cp310-cp310-win32.whl", hash = "sha256:13577ec9e247f8741c84d06b9ece5f654920d8365a4b636ce0e44f15e07ec693"}, + {file = "msgpack-1.0.8-cp310-cp310-win_amd64.whl", hash = "sha256:e532dbd6ddfe13946de050d7474e3f5fb6ec774fbb1a188aaf469b08cf04189a"}, + {file = "msgpack-1.0.8-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:9517004e21664f2b5a5fd6333b0731b9cf0817403a941b393d89a2f1dc2bd836"}, + {file = "msgpack-1.0.8-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:d16a786905034e7e34098634b184a7d81f91d4c3d246edc6bd7aefb2fd8ea6ad"}, + {file = "msgpack-1.0.8-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:e2872993e209f7ed04d963e4b4fbae72d034844ec66bc4ca403329db2074377b"}, + {file = "msgpack-1.0.8-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5c330eace3dd100bdb54b5653b966de7f51c26ec4a7d4e87132d9b4f738220ba"}, + {file = "msgpack-1.0.8-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:83b5c044f3eff2a6534768ccfd50425939e7a8b5cf9a7261c385de1e20dcfc85"}, + {file = "msgpack-1.0.8-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1876b0b653a808fcd50123b953af170c535027bf1d053b59790eebb0aeb38950"}, + {file = "msgpack-1.0.8-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:dfe1f0f0ed5785c187144c46a292b8c34c1295c01da12e10ccddfc16def4448a"}, + {file = "msgpack-1.0.8-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:3528807cbbb7f315bb81959d5961855e7ba52aa60a3097151cb21956fbc7502b"}, + {file = "msgpack-1.0.8-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e2f879ab92ce502a1e65fce390eab619774dda6a6ff719718069ac94084098ce"}, + {file = "msgpack-1.0.8-cp311-cp311-win32.whl", hash = "sha256:26ee97a8261e6e35885c2ecd2fd4a6d38252246f94a2aec23665a4e66d066305"}, + {file = "msgpack-1.0.8-cp311-cp311-win_amd64.whl", hash = "sha256:eadb9f826c138e6cf3c49d6f8de88225a3c0ab181a9b4ba792e006e5292d150e"}, + {file = "msgpack-1.0.8-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:114be227f5213ef8b215c22dde19532f5da9652e56e8ce969bf0a26d7c419fee"}, + {file = "msgpack-1.0.8-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:d661dc4785affa9d0edfdd1e59ec056a58b3dbb9f196fa43587f3ddac654ac7b"}, + {file = "msgpack-1.0.8-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:d56fd9f1f1cdc8227d7b7918f55091349741904d9520c65f0139a9755952c9e8"}, + {file = "msgpack-1.0.8-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:0726c282d188e204281ebd8de31724b7d749adebc086873a59efb8cf7ae27df3"}, + {file = "msgpack-1.0.8-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8db8e423192303ed77cff4dce3a4b88dbfaf43979d280181558af5e2c3c71afc"}, + {file = "msgpack-1.0.8-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:99881222f4a8c2f641f25703963a5cefb076adffd959e0558dc9f803a52d6a58"}, + {file = "msgpack-1.0.8-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:b5505774ea2a73a86ea176e8a9a4a7c8bf5d521050f0f6f8426afe798689243f"}, + {file = "msgpack-1.0.8-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:ef254a06bcea461e65ff0373d8a0dd1ed3aa004af48839f002a0c994a6f72d04"}, + {file = "msgpack-1.0.8-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:e1dd7839443592d00e96db831eddb4111a2a81a46b028f0facd60a09ebbdd543"}, + {file = "msgpack-1.0.8-cp312-cp312-win32.whl", hash = "sha256:64d0fcd436c5683fdd7c907eeae5e2cbb5eb872fafbc03a43609d7941840995c"}, + {file = "msgpack-1.0.8-cp312-cp312-win_amd64.whl", hash = "sha256:74398a4cf19de42e1498368c36eed45d9528f5fd0155241e82c4082b7e16cffd"}, + {file = "msgpack-1.0.8-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:0ceea77719d45c839fd73abcb190b8390412a890df2f83fb8cf49b2a4b5c2f40"}, + {file = "msgpack-1.0.8-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1ab0bbcd4d1f7b6991ee7c753655b481c50084294218de69365f8f1970d4c151"}, + {file = "msgpack-1.0.8-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:1cce488457370ffd1f953846f82323cb6b2ad2190987cd4d70b2713e17268d24"}, + {file = "msgpack-1.0.8-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3923a1778f7e5ef31865893fdca12a8d7dc03a44b33e2a5f3295416314c09f5d"}, + {file = "msgpack-1.0.8-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a22e47578b30a3e199ab067a4d43d790249b3c0587d9a771921f86250c8435db"}, + {file = "msgpack-1.0.8-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:bd739c9251d01e0279ce729e37b39d49a08c0420d3fee7f2a4968c0576678f77"}, + {file = "msgpack-1.0.8-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:d3420522057ebab1728b21ad473aa950026d07cb09da41103f8e597dfbfaeb13"}, + {file = "msgpack-1.0.8-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:5845fdf5e5d5b78a49b826fcdc0eb2e2aa7191980e3d2cfd2a30303a74f212e2"}, + {file = "msgpack-1.0.8-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6a0e76621f6e1f908ae52860bdcb58e1ca85231a9b0545e64509c931dd34275a"}, + {file = "msgpack-1.0.8-cp38-cp38-win32.whl", hash = "sha256:374a8e88ddab84b9ada695d255679fb99c53513c0a51778796fcf0944d6c789c"}, + {file = "msgpack-1.0.8-cp38-cp38-win_amd64.whl", hash = "sha256:f3709997b228685fe53e8c433e2df9f0cdb5f4542bd5114ed17ac3c0129b0480"}, + {file = "msgpack-1.0.8-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:f51bab98d52739c50c56658cc303f190785f9a2cd97b823357e7aeae54c8f68a"}, + {file = "msgpack-1.0.8-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:73ee792784d48aa338bba28063e19a27e8d989344f34aad14ea6e1b9bd83f596"}, + {file = "msgpack-1.0.8-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:f9904e24646570539a8950400602d66d2b2c492b9010ea7e965025cb71d0c86d"}, + {file = "msgpack-1.0.8-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e75753aeda0ddc4c28dce4c32ba2f6ec30b1b02f6c0b14e547841ba5b24f753f"}, + {file = "msgpack-1.0.8-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5dbf059fb4b7c240c873c1245ee112505be27497e90f7c6591261c7d3c3a8228"}, + {file = "msgpack-1.0.8-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4916727e31c28be8beaf11cf117d6f6f188dcc36daae4e851fee88646f5b6b18"}, + {file = "msgpack-1.0.8-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:7938111ed1358f536daf311be244f34df7bf3cdedb3ed883787aca97778b28d8"}, + {file = "msgpack-1.0.8-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:493c5c5e44b06d6c9268ce21b302c9ca055c1fd3484c25ba41d34476c76ee746"}, + {file = "msgpack-1.0.8-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:5fbb160554e319f7b22ecf530a80a3ff496d38e8e07ae763b9e82fadfe96f273"}, + {file = "msgpack-1.0.8-cp39-cp39-win32.whl", hash = "sha256:f9af38a89b6a5c04b7d18c492c8ccf2aee7048aff1ce8437c4683bb5a1df893d"}, + {file = "msgpack-1.0.8-cp39-cp39-win_amd64.whl", hash = "sha256:ed59dd52075f8fc91da6053b12e8c89e37aa043f8986efd89e61fae69dc1b011"}, + {file = "msgpack-1.0.8.tar.gz", hash = "sha256:95c02b0e27e706e48d0e5426d1710ca78e0f0628d6e89d5b5a5b91a5f12274f3"}, +] + +[[package]] +name = "mypy-extensions" +version = "1.0.0" +description = "Type system extensions for programs checked with the mypy type checker." +optional = false +python-versions = ">=3.5" +files = [ + {file = "mypy_extensions-1.0.0-py3-none-any.whl", hash = "sha256:4392f6c0eb8a5668a69e23d168ffa70f0be9ccfd32b5cc2d26a34ae5b844552d"}, + {file = "mypy_extensions-1.0.0.tar.gz", hash = "sha256:75dbf8955dc00442a438fc4d0666508a9a97b6bd41aa2f0ffe9d2f2725af0782"}, +] + +[[package]] +name = "nodeenv" +version = "1.8.0" +description = "Node.js virtual environment builder" +optional = false +python-versions = ">=2.7,!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*" +files = [ + {file = "nodeenv-1.8.0-py2.py3-none-any.whl", hash = "sha256:df865724bb3c3adc86b3876fa209771517b0cfe596beff01a92700e0e8be4cec"}, + {file = "nodeenv-1.8.0.tar.gz", hash = "sha256:d51e0c37e64fbf47d017feac3145cdbb58836d7eee8c6f6d3b6880c5456227d2"}, +] + +[package.dependencies] +setuptools = "*" + +[[package]] +name = "oauthlib" +version = "3.2.2" +description = "A generic, spec-compliant, thorough implementation of the OAuth request-signing logic" +optional = false +python-versions = ">=3.6" +files = [ + {file = "oauthlib-3.2.2-py3-none-any.whl", hash = "sha256:8139f29aac13e25d502680e9e19963e83f16838d48a0d71c287fe40e7067fbca"}, + {file = "oauthlib-3.2.2.tar.gz", hash = "sha256:9859c40929662bec5d64f34d01c99e093149682a3f38915dc0655d5a633dd918"}, +] + +[package.extras] +rsa = ["cryptography (>=3.0.0)"] +signals = ["blinker (>=1.4.0)"] +signedtoken = ["cryptography (>=3.0.0)", "pyjwt (>=2.0.0,<3)"] + +[[package]] +name = "packaging" +version = "24.0" +description = "Core utilities for Python packages" +optional = false +python-versions = ">=3.7" +files = [ + {file = "packaging-24.0-py3-none-any.whl", hash = "sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5"}, + {file = "packaging-24.0.tar.gz", hash = "sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9"}, +] + +[[package]] +name = "pathspec" +version = "0.12.1" +description = "Utility library for gitignore style pattern matching of file paths." +optional = false +python-versions = ">=3.8" +files = [ + {file = "pathspec-0.12.1-py3-none-any.whl", hash = "sha256:a0d503e138a4c123b27490a4f7beda6a01c6f288df0e4a8b79c7eb0dc7b4cc08"}, + {file = "pathspec-0.12.1.tar.gz", hash = "sha256:a482d51503a1ab33b1c67a6c3813a26953dbdc71c31dacaef9a838c4e29f5712"}, +] + +[[package]] +name = "pexpect" +version = "4.9.0" +description = "Pexpect allows easy control of interactive console applications." +optional = false +python-versions = "*" +files = [ + {file = "pexpect-4.9.0-py2.py3-none-any.whl", hash = "sha256:7236d1e080e4936be2dc3e326cec0af72acf9212a7e1d060210e70a47e253523"}, + {file = "pexpect-4.9.0.tar.gz", hash = "sha256:ee7d41123f3c9911050ea2c2dac107568dc43b2d3b0c7557a33212c398ead30f"}, +] + +[package.dependencies] +ptyprocess = ">=0.5" + +[[package]] +name = "pkginfo" +version = "1.10.0" +description = "Query metadata from sdists / bdists / installed packages." +optional = false +python-versions = ">=3.6" +files = [ + {file = "pkginfo-1.10.0-py3-none-any.whl", hash = "sha256:889a6da2ed7ffc58ab5b900d888ddce90bce912f2d2de1dc1c26f4cb9fe65097"}, + {file = "pkginfo-1.10.0.tar.gz", hash = "sha256:5df73835398d10db79f8eecd5cd86b1f6d29317589ea70796994d49399af6297"}, +] + +[package.extras] +testing = ["pytest", "pytest-cov", "wheel"] + +[[package]] +name = "platformdirs" +version = "4.2.0" +description = "A small Python package for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." +optional = false +python-versions = ">=3.8" +files = [ + {file = "platformdirs-4.2.0-py3-none-any.whl", hash = "sha256:0614df2a2f37e1a662acbd8e2b25b92ccf8632929bc6d43467e17fe89c75e068"}, + {file = "platformdirs-4.2.0.tar.gz", hash = "sha256:ef0cc731df711022c174543cb70a9b5bd22e5a9337c8624ef2c2ceb8ddad8768"}, +] + +[package.extras] +docs = ["furo (>=2023.9.10)", "proselint (>=0.13)", "sphinx (>=7.2.6)", "sphinx-autodoc-typehints (>=1.25.2)"] +test = ["appdirs (==1.4.4)", "covdefaults (>=2.3)", "pytest (>=7.4.3)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)"] + +[[package]] +name = "pluggy" +version = "1.4.0" +description = "plugin and hook calling mechanisms for python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pluggy-1.4.0-py3-none-any.whl", hash = "sha256:7db9f7b503d67d1c5b95f59773ebb58a8c1c288129a88665838012cfb07b8981"}, + {file = "pluggy-1.4.0.tar.gz", hash = "sha256:8c85c2876142a764e5b7548e7d9a0e0ddb46f5185161049a79b7e974454223be"}, +] + +[package.extras] +dev = ["pre-commit", "tox"] +testing = ["pytest", "pytest-benchmark"] + +[[package]] +name = "poetry" +version = "1.8.2" +description = "Python dependency management and packaging made easy." +optional = false +python-versions = ">=3.8,<4.0" +files = [ + {file = "poetry-1.8.2-py3-none-any.whl", hash = "sha256:b42b400d9a803af6e788a30a6f3e9998020b77860e28df20647eb10b6f414910"}, + {file = "poetry-1.8.2.tar.gz", hash = "sha256:49cceb3838104647c3e1021f3a4f13c6053704cc18d33f849a90fe687a29cb73"}, +] + +[package.dependencies] +build = ">=1.0.3,<2.0.0" +cachecontrol = {version = ">=0.14.0,<0.15.0", extras = ["filecache"]} +cleo = ">=2.1.0,<3.0.0" +crashtest = ">=0.4.1,<0.5.0" +dulwich = ">=0.21.2,<0.22.0" +fastjsonschema = ">=2.18.0,<3.0.0" +importlib-metadata = {version = ">=4.4", markers = "python_version < \"3.10\""} +installer = ">=0.7.0,<0.8.0" +keyring = ">=24.0.0,<25.0.0" +packaging = ">=23.1" +pexpect = ">=4.7.0,<5.0.0" +pkginfo = ">=1.9.4,<2.0.0" +platformdirs = ">=3.0.0,<5" +poetry-core = "1.9.0" +poetry-plugin-export = ">=1.6.0,<2.0.0" +pyproject-hooks = ">=1.0.0,<2.0.0" +requests = ">=2.26,<3.0" +requests-toolbelt = ">=1.0.0,<2.0.0" +shellingham = ">=1.5,<2.0" +tomli = {version = ">=2.0.1,<3.0.0", markers = "python_version < \"3.11\""} +tomlkit = ">=0.11.4,<1.0.0" +trove-classifiers = ">=2022.5.19" +virtualenv = ">=20.23.0,<21.0.0" +xattr = {version = ">=1.0.0,<2.0.0", markers = "sys_platform == \"darwin\""} + +[[package]] +name = "poetry-core" +version = "1.9.0" +description = "Poetry PEP 517 Build Backend" +optional = false +python-versions = ">=3.8,<4.0" +files = [ + {file = "poetry_core-1.9.0-py3-none-any.whl", hash = "sha256:4e0c9c6ad8cf89956f03b308736d84ea6ddb44089d16f2adc94050108ec1f5a1"}, + {file = "poetry_core-1.9.0.tar.gz", hash = "sha256:fa7a4001eae8aa572ee84f35feb510b321bd652e5cf9293249d62853e1f935a2"}, +] + +[[package]] +name = "poetry-plugin-export" +version = "1.7.1" +description = "Poetry plugin to export the dependencies to various formats" +optional = false +python-versions = ">=3.8,<4.0" +files = [ + {file = "poetry_plugin_export-1.7.1-py3-none-any.whl", hash = "sha256:b2258e53ae0d369a73806f957ed0e726eb95c571a0ce8b1f273da686528cc1da"}, + {file = "poetry_plugin_export-1.7.1.tar.gz", hash = "sha256:cf62cfb6218a904290ba6db3bc1a24aa076d10f81c48c6e48b2ded430131e22e"}, +] + +[package.dependencies] +poetry = ">=1.8.0,<2.0.0" +poetry-core = ">=1.7.0,<2.0.0" + +[[package]] +name = "pre-commit" +version = "3.7.0" +description = "A framework for managing and maintaining multi-language pre-commit hooks." +optional = false +python-versions = ">=3.9" +files = [ + {file = "pre_commit-3.7.0-py2.py3-none-any.whl", hash = "sha256:5eae9e10c2b5ac51577c3452ec0a490455c45a0533f7960f993a0d01e59decab"}, + {file = "pre_commit-3.7.0.tar.gz", hash = "sha256:e209d61b8acdcf742404408531f0c37d49d2c734fd7cff2d6076083d191cb060"}, +] + +[package.dependencies] +cfgv = ">=2.0.0" +identify = ">=1.0.0" +nodeenv = ">=0.11.1" +pyyaml = ">=5.1" +virtualenv = ">=20.10.0" + +[[package]] +name = "ptyprocess" +version = "0.7.0" +description = "Run a subprocess in a pseudo terminal" +optional = false +python-versions = "*" +files = [ + {file = "ptyprocess-0.7.0-py2.py3-none-any.whl", hash = "sha256:4b41f3967fce3af57cc7e94b888626c18bf37a083e3651ca8feeb66d492fef35"}, + {file = "ptyprocess-0.7.0.tar.gz", hash = "sha256:5c5d0a3b48ceee0b48485e0c26037c0acd7d29765ca3fbb5cb3831d347423220"}, +] + +[[package]] +name = "pycodestyle" +version = "2.11.1" +description = "Python style guide checker" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pycodestyle-2.11.1-py2.py3-none-any.whl", hash = "sha256:44fe31000b2d866f2e41841b18528a505fbd7fef9017b04eff4e2648a0fadc67"}, + {file = "pycodestyle-2.11.1.tar.gz", hash = "sha256:41ba0e7afc9752dfb53ced5489e89f8186be00e599e712660695b7a75ff2663f"}, +] + +[[package]] +name = "pycparser" +version = "2.22" +description = "C parser in Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pycparser-2.22-py3-none-any.whl", hash = "sha256:c3702b6d3dd8c7abc1afa565d7e63d53a1d0bd86cdc24edd75470f4de499cfcc"}, + {file = "pycparser-2.22.tar.gz", hash = "sha256:491c8be9c040f5390f5bf44a5b07752bd07f56edf992381b05c701439eec10f6"}, +] + +[[package]] +name = "pyflakes" +version = "3.2.0" +description = "passive checker of Python programs" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyflakes-3.2.0-py2.py3-none-any.whl", hash = "sha256:84b5be138a2dfbb40689ca07e2152deb896a65c3a3e24c251c5c62489568074a"}, + {file = "pyflakes-3.2.0.tar.gz", hash = "sha256:1c61603ff154621fb2a9172037d84dca3500def8c8b630657d1701f026f8af3f"}, +] + +[[package]] +name = "pygments" +version = "2.17.2" +description = "Pygments is a syntax highlighting package written in Python." +optional = false +python-versions = ">=3.7" +files = [ + {file = "pygments-2.17.2-py3-none-any.whl", hash = "sha256:b27c2826c47d0f3219f29554824c30c5e8945175d888647acd804ddd04af846c"}, + {file = "pygments-2.17.2.tar.gz", hash = "sha256:da46cec9fd2de5be3a8a784f434e4c4ab670b4ff54d605c4c2717e9d49c4c367"}, +] + +[package.extras] +plugins = ["importlib-metadata"] +windows-terminal = ["colorama (>=0.4.6)"] + +[[package]] +name = "pylint" +version = "3.1.0" +description = "python code static checker" +optional = false +python-versions = ">=3.8.0" +files = [ + {file = "pylint-3.1.0-py3-none-any.whl", hash = "sha256:507a5b60953874766d8a366e8e8c7af63e058b26345cfcb5f91f89d987fd6b74"}, + {file = "pylint-3.1.0.tar.gz", hash = "sha256:6a69beb4a6f63debebaab0a3477ecd0f559aa726af4954fc948c51f7a2549e23"}, +] + +[package.dependencies] +astroid = ">=3.1.0,<=3.2.0-dev0" +colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""} +dill = [ + {version = ">=0.2", markers = "python_version < \"3.11\""}, + {version = ">=0.3.7", markers = "python_version >= \"3.12\""}, + {version = ">=0.3.6", markers = "python_version >= \"3.11\" and python_version < \"3.12\""}, +] +isort = ">=4.2.5,<5.13.0 || >5.13.0,<6" +mccabe = ">=0.6,<0.8" +platformdirs = ">=2.2.0" +tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} +tomlkit = ">=0.10.1" +typing-extensions = {version = ">=3.10.0", markers = "python_version < \"3.10\""} + +[package.extras] +spelling = ["pyenchant (>=3.2,<4.0)"] +testutils = ["gitpython (>3)"] + +[[package]] +name = "pyproject-api" +version = "1.6.1" +description = "API to interact with the python pyproject.toml based projects" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pyproject_api-1.6.1-py3-none-any.whl", hash = "sha256:4c0116d60476b0786c88692cf4e325a9814965e2469c5998b830bba16b183675"}, + {file = "pyproject_api-1.6.1.tar.gz", hash = "sha256:1817dc018adc0d1ff9ca1ed8c60e1623d5aaca40814b953af14a9cf9a5cae538"}, +] + +[package.dependencies] +packaging = ">=23.1" +tomli = {version = ">=2.0.1", markers = "python_version < \"3.11\""} + +[package.extras] +docs = ["furo (>=2023.8.19)", "sphinx (<7.2)", "sphinx-autodoc-typehints (>=1.24)"] +testing = ["covdefaults (>=2.3)", "pytest (>=7.4)", "pytest-cov (>=4.1)", "pytest-mock (>=3.11.1)", "setuptools (>=68.1.2)", "wheel (>=0.41.2)"] + +[[package]] +name = "pyproject-hooks" +version = "1.0.0" +description = "Wrappers to call pyproject.toml-based build backend hooks." +optional = false +python-versions = ">=3.7" +files = [ + {file = "pyproject_hooks-1.0.0-py3-none-any.whl", hash = "sha256:283c11acd6b928d2f6a7c73fa0d01cb2bdc5f07c57a2eeb6e83d5e56b97976f8"}, + {file = "pyproject_hooks-1.0.0.tar.gz", hash = "sha256:f271b298b97f5955d53fb12b72c1fb1948c22c1a6b70b315c54cedaca0264ef5"}, +] + +[package.dependencies] +tomli = {version = ">=1.1.0", markers = "python_version < \"3.11\""} + +[[package]] +name = "pytest" +version = "8.1.1" +description = "pytest: simple powerful testing with Python" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pytest-8.1.1-py3-none-any.whl", hash = "sha256:2a8386cfc11fa9d2c50ee7b2a57e7d898ef90470a7a34c4b949ff59662bb78b7"}, + {file = "pytest-8.1.1.tar.gz", hash = "sha256:ac978141a75948948817d360297b7aae0fcb9d6ff6bc9ec6d514b85d5a65c044"}, +] + +[package.dependencies] +colorama = {version = "*", markers = "sys_platform == \"win32\""} +exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} +iniconfig = "*" +packaging = "*" +pluggy = ">=1.4,<2.0" +tomli = {version = ">=1", markers = "python_version < \"3.11\""} + +[package.extras] +testing = ["argcomplete", "attrs (>=19.2)", "hypothesis (>=3.56)", "mock", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] + +[[package]] +name = "pytest-cov" +version = "5.0.0" +description = "Pytest plugin for measuring coverage." +optional = false +python-versions = ">=3.8" +files = [ + {file = "pytest-cov-5.0.0.tar.gz", hash = "sha256:5837b58e9f6ebd335b0f8060eecce69b662415b16dc503883a02f45dfeb14857"}, + {file = "pytest_cov-5.0.0-py3-none-any.whl", hash = "sha256:4f0764a1219df53214206bf1feea4633c3b558a2925c8b59f144f682861ce652"}, +] + +[package.dependencies] +coverage = {version = ">=5.2.1", extras = ["toml"]} +pytest = ">=4.6" + +[package.extras] +testing = ["fields", "hunter", "process-tests", "pytest-xdist", "virtualenv"] + +[[package]] +name = "pytest-mock" +version = "3.14.0" +description = "Thin-wrapper around the mock package for easier use with pytest" +optional = false +python-versions = ">=3.8" +files = [ + {file = "pytest-mock-3.14.0.tar.gz", hash = "sha256:2719255a1efeceadbc056d6bf3df3d1c5015530fb40cf347c0f9afac88410bd0"}, + {file = "pytest_mock-3.14.0-py3-none-any.whl", hash = "sha256:0b72c38033392a5f4621342fe11e9219ac11ec9d375f8e2a0c164539e0d70f6f"}, +] + +[package.dependencies] +pytest = ">=6.2.5" + +[package.extras] +dev = ["pre-commit", "pytest-asyncio", "tox"] + +[[package]] +name = "python-dateutil" +version = "2.9.0.post0" +description = "Extensions to the standard Python datetime module" +optional = false +python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" +files = [ + {file = "python-dateutil-2.9.0.post0.tar.gz", hash = "sha256:37dd54208da7e1cd875388217d5e00ebd4179249f90fb72437e91a35459a0ad3"}, + {file = "python_dateutil-2.9.0.post0-py2.py3-none-any.whl", hash = "sha256:a8b2bc7bffae282281c8140a97d3aa9c14da0b136dfe83f850eea9a5f7470427"}, +] + +[package.dependencies] +six = ">=1.5" + +[[package]] +name = "pywin32-ctypes" +version = "0.2.2" +description = "A (partial) reimplementation of pywin32 using ctypes/cffi" +optional = false +python-versions = ">=3.6" +files = [ + {file = "pywin32-ctypes-0.2.2.tar.gz", hash = "sha256:3426e063bdd5fd4df74a14fa3cf80a0b42845a87e1d1e81f6549f9daec593a60"}, + {file = "pywin32_ctypes-0.2.2-py3-none-any.whl", hash = "sha256:bf490a1a709baf35d688fe0ecf980ed4de11d2b3e37b51e5442587a75d9957e7"}, +] + +[[package]] +name = "pyyaml" +version = "6.0.1" +description = "YAML parser and emitter for Python" +optional = false +python-versions = ">=3.6" +files = [ + {file = "PyYAML-6.0.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:d858aa552c999bc8a8d57426ed01e40bef403cd8ccdd0fc5f6f04a00414cac2a"}, + {file = "PyYAML-6.0.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:fd66fc5d0da6d9815ba2cebeb4205f95818ff4b79c3ebe268e75d961704af52f"}, + {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:69b023b2b4daa7548bcfbd4aa3da05b3a74b772db9e23b982788168117739938"}, + {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:81e0b275a9ecc9c0c0c07b4b90ba548307583c125f54d5b6946cfee6360c733d"}, + {file = "PyYAML-6.0.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ba336e390cd8e4d1739f42dfe9bb83a3cc2e80f567d8805e11b46f4a943f5515"}, + {file = "PyYAML-6.0.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:326c013efe8048858a6d312ddd31d56e468118ad4cdeda36c719bf5bb6192290"}, + {file = "PyYAML-6.0.1-cp310-cp310-win32.whl", hash = "sha256:bd4af7373a854424dabd882decdc5579653d7868b8fb26dc7d0e99f823aa5924"}, + {file = "PyYAML-6.0.1-cp310-cp310-win_amd64.whl", hash = "sha256:fd1592b3fdf65fff2ad0004b5e363300ef59ced41c2e6b3a99d4089fa8c5435d"}, + {file = "PyYAML-6.0.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6965a7bc3cf88e5a1c3bd2e0b5c22f8d677dc88a455344035f03399034eb3007"}, + {file = "PyYAML-6.0.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:f003ed9ad21d6a4713f0a9b5a7a0a79e08dd0f221aff4525a2be4c346ee60aab"}, + {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42f8152b8dbc4fe7d96729ec2b99c7097d656dc1213a3229ca5383f973a5ed6d"}, + {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:062582fca9fabdd2c8b54a3ef1c978d786e0f6b3a1510e0ac93ef59e0ddae2bc"}, + {file = "PyYAML-6.0.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b04aac4d386b172d5b9692e2d2da8de7bfb6c387fa4f801fbf6fb2e6ba4673"}, + {file = "PyYAML-6.0.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e7d73685e87afe9f3b36c799222440d6cf362062f78be1013661b00c5c6f678b"}, + {file = "PyYAML-6.0.1-cp311-cp311-win32.whl", hash = "sha256:1635fd110e8d85d55237ab316b5b011de701ea0f29d07611174a1b42f1444741"}, + {file = "PyYAML-6.0.1-cp311-cp311-win_amd64.whl", hash = "sha256:bf07ee2fef7014951eeb99f56f39c9bb4af143d8aa3c21b1677805985307da34"}, + {file = "PyYAML-6.0.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:855fb52b0dc35af121542a76b9a84f8d1cd886ea97c84703eaa6d88e37a2ad28"}, + {file = "PyYAML-6.0.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:40df9b996c2b73138957fe23a16a4f0ba614f4c0efce1e9406a184b6d07fa3a9"}, + {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a08c6f0fe150303c1c6b71ebcd7213c2858041a7e01975da3a99aed1e7a378ef"}, + {file = "PyYAML-6.0.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6c22bec3fbe2524cde73d7ada88f6566758a8f7227bfbf93a408a9d86bcc12a0"}, + {file = "PyYAML-6.0.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:8d4e9c88387b0f5c7d5f281e55304de64cf7f9c0021a3525bd3b1c542da3b0e4"}, + {file = "PyYAML-6.0.1-cp312-cp312-win32.whl", hash = "sha256:d483d2cdf104e7c9fa60c544d92981f12ad66a457afae824d146093b8c294c54"}, + {file = "PyYAML-6.0.1-cp312-cp312-win_amd64.whl", hash = "sha256:0d3304d8c0adc42be59c5f8a4d9e3d7379e6955ad754aa9d6ab7a398b59dd1df"}, + {file = "PyYAML-6.0.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:50550eb667afee136e9a77d6dc71ae76a44df8b3e51e41b77f6de2932bfe0f47"}, + {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1fe35611261b29bd1de0070f0b2f47cb6ff71fa6595c077e42bd0c419fa27b98"}, + {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:704219a11b772aea0d8ecd7058d0082713c3562b4e271b849ad7dc4a5c90c13c"}, + {file = "PyYAML-6.0.1-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:afd7e57eddb1a54f0f1a974bc4391af8bcce0b444685d936840f125cf046d5bd"}, + {file = "PyYAML-6.0.1-cp36-cp36m-win32.whl", hash = "sha256:fca0e3a251908a499833aa292323f32437106001d436eca0e6e7833256674585"}, + {file = "PyYAML-6.0.1-cp36-cp36m-win_amd64.whl", hash = "sha256:f22ac1c3cac4dbc50079e965eba2c1058622631e526bd9afd45fedd49ba781fa"}, + {file = "PyYAML-6.0.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:b1275ad35a5d18c62a7220633c913e1b42d44b46ee12554e5fd39c70a243d6a3"}, + {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18aeb1bf9a78867dc38b259769503436b7c72f7a1f1f4c93ff9a17de54319b27"}, + {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:596106435fa6ad000c2991a98fa58eeb8656ef2325d7e158344fb33864ed87e3"}, + {file = "PyYAML-6.0.1-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:baa90d3f661d43131ca170712d903e6295d1f7a0f595074f151c0aed377c9b9c"}, + {file = "PyYAML-6.0.1-cp37-cp37m-win32.whl", hash = "sha256:9046c58c4395dff28dd494285c82ba00b546adfc7ef001486fbf0324bc174fba"}, + {file = "PyYAML-6.0.1-cp37-cp37m-win_amd64.whl", hash = "sha256:4fb147e7a67ef577a588a0e2c17b6db51dda102c71de36f8549b6816a96e1867"}, + {file = "PyYAML-6.0.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:1d4c7e777c441b20e32f52bd377e0c409713e8bb1386e1099c2415f26e479595"}, + {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a0cd17c15d3bb3fa06978b4e8958dcdc6e0174ccea823003a106c7d4d7899ac5"}, + {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:28c119d996beec18c05208a8bd78cbe4007878c6dd15091efb73a30e90539696"}, + {file = "PyYAML-6.0.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7e07cbde391ba96ab58e532ff4803f79c4129397514e1413a7dc761ccd755735"}, + {file = "PyYAML-6.0.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:49a183be227561de579b4a36efbb21b3eab9651dd81b1858589f796549873dd6"}, + {file = "PyYAML-6.0.1-cp38-cp38-win32.whl", hash = "sha256:184c5108a2aca3c5b3d3bf9395d50893a7ab82a38004c8f61c258d4428e80206"}, + {file = "PyYAML-6.0.1-cp38-cp38-win_amd64.whl", hash = "sha256:1e2722cc9fbb45d9b87631ac70924c11d3a401b2d7f410cc0e3bbf249f2dca62"}, + {file = "PyYAML-6.0.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9eb6caa9a297fc2c2fb8862bc5370d0303ddba53ba97e71f08023b6cd73d16a8"}, + {file = "PyYAML-6.0.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:c8098ddcc2a85b61647b2590f825f3db38891662cfc2fc776415143f599bb859"}, + {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5773183b6446b2c99bb77e77595dd486303b4faab2b086e7b17bc6bef28865f6"}, + {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b786eecbdf8499b9ca1d697215862083bd6d2a99965554781d0d8d1ad31e13a0"}, + {file = "PyYAML-6.0.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:bc1bf2925a1ecd43da378f4db9e4f799775d6367bdb94671027b73b393a7c42c"}, + {file = "PyYAML-6.0.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:04ac92ad1925b2cff1db0cfebffb6ffc43457495c9b3c39d3fcae417d7125dc5"}, + {file = "PyYAML-6.0.1-cp39-cp39-win32.whl", hash = "sha256:faca3bdcf85b2fc05d06ff3fbc1f83e1391b3e724afa3feba7d13eeab355484c"}, + {file = "PyYAML-6.0.1-cp39-cp39-win_amd64.whl", hash = "sha256:510c9deebc5c0225e8c96813043e62b680ba2f9c50a08d3724c7f28a747d1486"}, + {file = "PyYAML-6.0.1.tar.gz", hash = "sha256:bfdf460b1736c775f2ba9f6a92bca30bc2095067b8a9d77876d1fad6cc3b4a43"}, +] + +[[package]] +name = "rapidfuzz" +version = "3.8.1" +description = "rapid fuzzy string matching" +optional = false +python-versions = ">=3.8" +files = [ + {file = "rapidfuzz-3.8.1-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:1b176f01490b48337183da5b4223005bc0c2354a4faee5118917d2fba0bedc1c"}, + {file = "rapidfuzz-3.8.1-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0798e32304b8009d215026bf7e1c448f1831da0a03987b7de30059a41bee92f3"}, + {file = "rapidfuzz-3.8.1-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ad4dbd06c1f579eb043b2dcfc635bc6c9fb858240a70f0abd3bed84d8ac79994"}, + {file = "rapidfuzz-3.8.1-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e6ec696a268e8d730b42711537e500f7397afc06125c0e8fa9c8211386d315a5"}, + {file = "rapidfuzz-3.8.1-cp310-cp310-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2a8a007fdc5cf646e48e361a39eabe725b93af7673c5ab90294e551cae72ff58"}, + {file = "rapidfuzz-3.8.1-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:68b185a0397aebe78bcc5d0e1efd96509d4e2f3c4a05996e5c843732f547e9ef"}, + {file = "rapidfuzz-3.8.1-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:267ff42370e031195e3020fff075420c136b69dc918ecb5542ec75c1e36af81f"}, + {file = "rapidfuzz-3.8.1-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:987cd277d27d14301019fdf61c17524f6127f5d364be5482228726049d8e0d10"}, + {file = "rapidfuzz-3.8.1-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:bc5a1ec3bd05b55d3070d557c0cdd4412272d51b4966c79aa3e9da207bd33d65"}, + {file = "rapidfuzz-3.8.1-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:aa223c73c59cc45c12eaa9c439318084003beced0447ff92b578a890288e19eb"}, + {file = "rapidfuzz-3.8.1-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:d4276c7ee061db0bac54846933b40339f60085523675f917f37de24a4b3ce0ee"}, + {file = "rapidfuzz-3.8.1-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:2ba0e43e9a94d256a704a674c7010e6f8ef9225edf7287cf3e7f66c9894b06cd"}, + {file = "rapidfuzz-3.8.1-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:c22b32a57ab47afb207e8fe4bd7bb58c90f9291a63723cafd4e704742166e368"}, + {file = "rapidfuzz-3.8.1-cp310-cp310-win32.whl", hash = "sha256:50db3867864422bf6a6435ea65b9ac9de71ef52ed1e05d62f498cd430189eece"}, + {file = "rapidfuzz-3.8.1-cp310-cp310-win_amd64.whl", hash = "sha256:bca5acf77508d1822023a85118c2dd8d3c16abdd56d2762359a46deb14daa5e0"}, + {file = "rapidfuzz-3.8.1-cp310-cp310-win_arm64.whl", hash = "sha256:c763d99cf087e7b2c5be0cf34ae9a0e1b031f5057d2341a0a0ed782458645b7e"}, + {file = "rapidfuzz-3.8.1-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:30c282612b7ebf2d7646ebebfd98dd308c582246a94d576734e4b0162f57baf4"}, + {file = "rapidfuzz-3.8.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:2c6a43446f0cd8ff347b1fbb918dc0d657bebf484ddfa960ee069e422a477428"}, + {file = "rapidfuzz-3.8.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:4969fe0eb179aedacee53ca8f8f1be3c655964a6d62db30f247fee444b9c52b4"}, + {file = "rapidfuzz-3.8.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:799f5f221d639d1c2ed8a2348d1edf5e22aa489b58b2cc99f5bf0c1917e2d0f2"}, + {file = "rapidfuzz-3.8.1-cp311-cp311-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:e62bde7d5df3312acc528786ee801c472cae5078b1f1e42761c853ba7fe1072a"}, + {file = "rapidfuzz-3.8.1-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9ea3d2e41d8fac71cb63ee72f75bee0ed1e9c50709d4c58587f15437761c1858"}, + {file = "rapidfuzz-3.8.1-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:6f34a541895627c2bc9ef7757f16f02428a08d960d33208adfb96b33338d0945"}, + {file = "rapidfuzz-3.8.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a0643a25937fafe8d117f2907606e9940cd1cc905c66f16ece9ab93128299994"}, + {file = "rapidfuzz-3.8.1-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:63044a7b6791a2e945dce9d812a6886e93159deb0464984eb403617ded257f08"}, + {file = "rapidfuzz-3.8.1-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:bbc15985c5658691f637a6b97651771147744edfad2a4be56b8a06755e3932fa"}, + {file = "rapidfuzz-3.8.1-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:48b6e5a337a814aec7c6dda5d6460f947c9330860615301f35b519e16dde3c77"}, + {file = "rapidfuzz-3.8.1-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:8c40da44ca20235cda05751d6e828b6b348e7a7c5de2922fa0f9c63f564fd675"}, + {file = "rapidfuzz-3.8.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:c21d5c7cfa6078c79897e5e482a7e84ff927143d2f3fb020dd6edd27f5469574"}, + {file = "rapidfuzz-3.8.1-cp311-cp311-win32.whl", hash = "sha256:209bb712c448cdec4def6260b9f059bd4681ec61a01568f5e70e37bfe9efe830"}, + {file = "rapidfuzz-3.8.1-cp311-cp311-win_amd64.whl", hash = "sha256:6f7641992de44ec2ca54102422be44a8e3fb75b9690ccd74fff72b9ac7fc00ee"}, + {file = "rapidfuzz-3.8.1-cp311-cp311-win_arm64.whl", hash = "sha256:c458085e067c766112f089f78ce39eab2b69ba027d7bbb11d067a0b085774367"}, + {file = "rapidfuzz-3.8.1-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:1905d9319a97bed29f21584ca641190dbc9218a556202b77876f1e37618d2e03"}, + {file = "rapidfuzz-3.8.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f176867f438ff2a43e6a837930153ca78fddb3ca94e378603a1e7b860d7869bf"}, + {file = "rapidfuzz-3.8.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:25498650e30122f4a5ad6b27c7614b4af8628c1d32b19d406410d33f77a86c80"}, + {file = "rapidfuzz-3.8.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:16153a97efacadbd693ccc612a3285df2f072fd07c121f30c2c135a709537075"}, + {file = "rapidfuzz-3.8.1-cp312-cp312-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1c0264d03dcee1bb975975b77c2fe041820fb4d4a25a99e3cb74ddd083d671ca"}, + {file = "rapidfuzz-3.8.1-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:17d79398849c1244f646425cf31d856eab9ebd67b7d6571273e53df724ca817e"}, + {file = "rapidfuzz-3.8.1-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8e08b01dc9369941a24d7e512b0d81bf514e7d6add1b93d8aeec3c8fa08a824e"}, + {file = "rapidfuzz-3.8.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:97c13f156f14f10667e1cfc4257069b775440ce005e896c09ce3aff21c9ae665"}, + {file = "rapidfuzz-3.8.1-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:8b76abfec195bf1ee6f9ec56c33ba5e9615ff2d0a9530a54001ed87e5a6ced3b"}, + {file = "rapidfuzz-3.8.1-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:b0ba20be465566264fa5580d874ccf5eabba6975dba45857e2c76e2df3359c6d"}, + {file = "rapidfuzz-3.8.1-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:4d5cd86aca3f12e73bfc70015db7e8fc44122da03aa3761138b95112e83f66e4"}, + {file = "rapidfuzz-3.8.1-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:9a16ef3702cecf16056c5fd66398b7ea8622ff4e3afeb00a8db3e74427e850af"}, + {file = "rapidfuzz-3.8.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:392582aa784737d95255ca122ebe7dca3c774da900d100c07b53d32cd221a60e"}, + {file = "rapidfuzz-3.8.1-cp312-cp312-win32.whl", hash = "sha256:ceb10039e7346927cec47eaa490b34abb602b537e738ee9914bb41b8de029fbc"}, + {file = "rapidfuzz-3.8.1-cp312-cp312-win_amd64.whl", hash = "sha256:cc4af7090a626c902c48db9b5d786c1faa0d8e141571e8a63a5350419ea575bd"}, + {file = "rapidfuzz-3.8.1-cp312-cp312-win_arm64.whl", hash = "sha256:3aff3b829b0b04bdf78bd780ec9faf5f26eac3591df98c35a0ae216c925ae436"}, + {file = "rapidfuzz-3.8.1-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:78a0d2a11bb3936463609777c6d6d4984a27ebb2360b58339c699899d85db036"}, + {file = "rapidfuzz-3.8.1-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:f8af980695b866255447703bf634551e67e1a4e1c2d2d26501858d9233d886d7"}, + {file = "rapidfuzz-3.8.1-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:d1a15fef1938b43468002f2d81012dbc9e7b50eb8533af202b0559c2dc7865d9"}, + {file = "rapidfuzz-3.8.1-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c4dbb1ebc9a811f38da33f32ed2bb5f58b149289b89eb11e384519e9ba7ca881"}, + {file = "rapidfuzz-3.8.1-cp38-cp38-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:41219536634bd6f85419f38450ef080cfb519638125d805cf8626443e677dc61"}, + {file = "rapidfuzz-3.8.1-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e3f882110f2f4894942e314451773c47e8b1b4920b5ea2b6dd2e2d4079dd3135"}, + {file = "rapidfuzz-3.8.1-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c754ce1fab41b731259f100d5d46529a38aa2c9b683c92aeb7e96ef5b2898cd8"}, + {file = "rapidfuzz-3.8.1-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:718ea99f84b16c4bdbf6a93e53552cdccefa18e12ff9a02c5041e621460e2e61"}, + {file = "rapidfuzz-3.8.1-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:9441aca94b21f7349cdb231cd0ce9ca251b2355836e8a02bf6ccbea5b442d7a9"}, + {file = "rapidfuzz-3.8.1-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:90167a48de3ed7f062058826608a80242b8561d0fb0cce2c610d741624811a61"}, + {file = "rapidfuzz-3.8.1-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:8e02425bfc7ebed617323a674974b70eaecd8f07b64a7d16e0bf3e766b93e3c9"}, + {file = "rapidfuzz-3.8.1-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:d48657a404fab82b2754faa813a10c5ad6aa594cb1829dca168a49438b61b4ec"}, + {file = "rapidfuzz-3.8.1-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:6f8b62fdccc429e6643cefffd5df9c7bca65588d06e8925b78014ad9ad983bf5"}, + {file = "rapidfuzz-3.8.1-cp38-cp38-win32.whl", hash = "sha256:63db612bb6da1bb9f6aa7412739f0e714b1910ec07bc675943044fe683ef192c"}, + {file = "rapidfuzz-3.8.1-cp38-cp38-win_amd64.whl", hash = "sha256:bb571dbd4cc93342be0ba632f0b8d7de4cbd9d959d76371d33716d2216090d41"}, + {file = "rapidfuzz-3.8.1-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:b27cea618601ca5032ea98ee116ca6e0fe67be7b286bcb0b9f956d64db697472"}, + {file = "rapidfuzz-3.8.1-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:1d5592b08e3cadc9e06ef3af6a9d66b6ef1bf871ed5acd7f9b1e162d78806a65"}, + {file = "rapidfuzz-3.8.1-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:58999b21d01dd353f49511a61937eac20c7a5b22eab87612063947081855d85f"}, + {file = "rapidfuzz-3.8.1-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a2ee3909f611cc5860cc8d9f92d039fd84241ce7360b49ea88e657181d2b45f6"}, + {file = "rapidfuzz-3.8.1-cp39-cp39-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:00b5ee47b387fa3805f4038362a085ec58149135dc5bc640ca315a9893a16f9e"}, + {file = "rapidfuzz-3.8.1-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e4c647795c5b901091a68e210c76b769af70a33a8624ac496ac3e34d33366c0d"}, + {file = "rapidfuzz-3.8.1-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:77ea62879932b32aba77ab23a9296390a67d024bf2f048dee99143be80a4ce26"}, + {file = "rapidfuzz-3.8.1-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3fee62ae76e3b8b9fff8aa2ca4061575ee358927ffbdb2919a8c84a98da59f78"}, + {file = "rapidfuzz-3.8.1-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:231dc1cb63b1c8dd78c0597aa3ad3749a86a2b7e76af295dd81609522699a558"}, + {file = "rapidfuzz-3.8.1-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:827ddf2d5d157ac3d1001b52e84c9e20366237a742946599ffc435af7fdd26d0"}, + {file = "rapidfuzz-3.8.1-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:c04ef83c9ca3162d200df36e933b3ea0327a2626cee2e01bbe55acbc004ce261"}, + {file = "rapidfuzz-3.8.1-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:747265f39978bbaad356f5c6b6c808f0e8f5e8994875af0119b82b4700c55387"}, + {file = "rapidfuzz-3.8.1-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:14791324f0c753f5a0918df1249b91515f5ddc16281fbaa5ec48bff8fa659229"}, + {file = "rapidfuzz-3.8.1-cp39-cp39-win32.whl", hash = "sha256:b7b9cbc60e3eb08da6d18636c62c6eb6206cd9d0c7ad73996f7a1df3fc415b27"}, + {file = "rapidfuzz-3.8.1-cp39-cp39-win_amd64.whl", hash = "sha256:2084193fd8fd346db496a2220363437eb9370a06d1d5a7a9dba00a64390c6a28"}, + {file = "rapidfuzz-3.8.1-cp39-cp39-win_arm64.whl", hash = "sha256:c9597a05d08e8103ad59ebdf29e3fbffb0d0dbf3b641f102cfbeadc3a77bde51"}, + {file = "rapidfuzz-3.8.1-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:5f4174079dfe8ed1f13ece9bde7660f19f98ab17e0c0d002d90cc845c3a7e238"}, + {file = "rapidfuzz-3.8.1-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:07d7d4a3c49a15146d65f06e44d7545628ca0437c929684e32ef122852f44d95"}, + {file = "rapidfuzz-3.8.1-pp38-pypy38_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:1ef119fc127c982053fb9ec638dcc3277f83b034b5972eb05941984b9ec4a290"}, + {file = "rapidfuzz-3.8.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f8e57f9c2367706a320b78e91f8bf9a3b03bf9069464eb7b54455fa340d03e4c"}, + {file = "rapidfuzz-3.8.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:6d4f1956fe1fc618e34ac79a6ed84fff5a6f23e41a8a476dd3e8570f0b12f02b"}, + {file = "rapidfuzz-3.8.1-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:313bdcd16e9cd5e5568b4a31d18a631f0b04cc10a3fd916e4ef75b713e6f177e"}, + {file = "rapidfuzz-3.8.1-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a02def2eb526cc934d2125533cf2f15aa71c72ed4397afca38427ab047901e88"}, + {file = "rapidfuzz-3.8.1-pp39-pypy39_pp73-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f9d5d924970b07128c61c08eebee718686f4bd9838ef712a50468169520c953f"}, + {file = "rapidfuzz-3.8.1-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1edafc0a2737df277d3ddf401f3a73f76e246b7502762c94a3916453ae67e9b1"}, + {file = "rapidfuzz-3.8.1-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:81fd28389bedab28251f0535b3c034b0e63a618efc3ff1d338c81a3da723adb3"}, + {file = "rapidfuzz-3.8.1.tar.gz", hash = "sha256:a357aae6791118011ad3ab4f2a4aa7bd7a487e5f9981b390e9f3c2c5137ecadf"}, +] + +[package.extras] +full = ["numpy"] + +[[package]] +name = "requests" +version = "2.31.0" +description = "Python HTTP for Humans." +optional = false +python-versions = ">=3.7" +files = [ + {file = "requests-2.31.0-py3-none-any.whl", hash = "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f"}, + {file = "requests-2.31.0.tar.gz", hash = "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1"}, +] + +[package.dependencies] +certifi = ">=2017.4.17" +charset-normalizer = ">=2,<4" +idna = ">=2.5,<4" +urllib3 = ">=1.21.1,<3" + +[package.extras] +socks = ["PySocks (>=1.5.6,!=1.5.7)"] +use-chardet-on-py3 = ["chardet (>=3.0.2,<6)"] + +[[package]] +name = "requests-oauthlib" +version = "2.0.0" +description = "OAuthlib authentication support for Requests." +optional = false +python-versions = ">=3.4" +files = [ + {file = "requests-oauthlib-2.0.0.tar.gz", hash = "sha256:b3dffaebd884d8cd778494369603a9e7b58d29111bf6b41bdc2dcd87203af4e9"}, + {file = "requests_oauthlib-2.0.0-py2.py3-none-any.whl", hash = "sha256:7dd8a5c40426b779b0868c404bdef9768deccf22749cde15852df527e6269b36"}, +] + +[package.dependencies] +oauthlib = ">=3.0.0" +requests = ">=2.0.0" + +[package.extras] +rsa = ["oauthlib[signedtoken] (>=3.0.0)"] + +[[package]] +name = "requests-toolbelt" +version = "1.0.0" +description = "A utility belt for advanced users of python-requests" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "requests-toolbelt-1.0.0.tar.gz", hash = "sha256:7681a0a3d047012b5bdc0ee37d7f8f07ebe76ab08caeccfc3921ce23c88d5bc6"}, + {file = "requests_toolbelt-1.0.0-py2.py3-none-any.whl", hash = "sha256:cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06"}, +] + +[package.dependencies] +requests = ">=2.0.1,<3.0.0" + +[[package]] +name = "secretstorage" +version = "3.3.3" +description = "Python bindings to FreeDesktop.org Secret Service API" +optional = false +python-versions = ">=3.6" +files = [ + {file = "SecretStorage-3.3.3-py3-none-any.whl", hash = "sha256:f356e6628222568e3af06f2eba8df495efa13b3b63081dafd4f7d9a7b7bc9f99"}, + {file = "SecretStorage-3.3.3.tar.gz", hash = "sha256:2403533ef369eca6d2ba81718576c5e0f564d5cca1b58f73a8b23e7d4eeebd77"}, +] + +[package.dependencies] +cryptography = ">=2.0" +jeepney = ">=0.6" + +[[package]] +name = "setuptools" +version = "69.5.1" +description = "Easily download, build, install, upgrade, and uninstall Python packages" +optional = false +python-versions = ">=3.8" +files = [ + {file = "setuptools-69.5.1-py3-none-any.whl", hash = "sha256:c636ac361bc47580504644275c9ad802c50415c7522212252c033bd15f301f32"}, + {file = "setuptools-69.5.1.tar.gz", hash = "sha256:6c1fccdac05a97e598fb0ae3bbed5904ccb317337a51139dcd51453611bbb987"}, +] + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] +testing = ["build[virtualenv]", "filelock (>=3.4.0)", "importlib-metadata", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "mypy (==1.9)", "packaging (>=23.2)", "pip (>=19.1)", "pytest (>=6,!=8.1.1)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-home (>=0.5)", "pytest-mypy", "pytest-perf", "pytest-ruff (>=0.2.1)", "pytest-timeout", "pytest-xdist (>=3)", "tomli", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] +testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.2)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] + +[[package]] +name = "shellingham" +version = "1.5.4" +description = "Tool to Detect Surrounding Shell" +optional = false +python-versions = ">=3.7" +files = [ + {file = "shellingham-1.5.4-py2.py3-none-any.whl", hash = "sha256:7ecfff8f2fd72616f7481040475a65b2bf8af90a56c89140852d1120324e8686"}, + {file = "shellingham-1.5.4.tar.gz", hash = "sha256:8dbca0739d487e5bd35ab3ca4b36e11c4078f3a234bfce294b0a0291363404de"}, +] + +[[package]] +name = "six" +version = "1.16.0" +description = "Python 2 and 3 compatibility utilities" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" +files = [ + {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, + {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, +] + +[[package]] +name = "snowballstemmer" +version = "2.2.0" +description = "This package provides 29 stemmers for 28 languages generated from Snowball algorithms." +optional = false +python-versions = "*" +files = [ + {file = "snowballstemmer-2.2.0-py2.py3-none-any.whl", hash = "sha256:c8e1716e83cc398ae16824e5572ae04e0d9fc2c6b985fb0f900f5f0c96ecba1a"}, + {file = "snowballstemmer-2.2.0.tar.gz", hash = "sha256:09b16deb8547d3412ad7b590689584cd0fe25ec8db3be37788be3810cbf19cb1"}, +] + +[[package]] +name = "sphinx" +version = "7.3.6" +description = "Python documentation generator" +optional = false +python-versions = ">=3.9" +files = [ + {file = "sphinx-7.3.6-py3-none-any.whl", hash = "sha256:d6c09acd42094fcd96a9299c1b32b2dafe82d667fdd6e532e5978443ad074c2a"}, + {file = "sphinx-7.3.6.tar.gz", hash = "sha256:fc9f3d13fed5c9a0e677d368090e209899ce5d0081eb552b657e2923e57517f0"}, +] + +[package.dependencies] +alabaster = ">=0.7.14,<0.8.0" +babel = ">=2.9" +colorama = {version = ">=0.4.5", markers = "sys_platform == \"win32\""} +docutils = ">=0.18.1,<0.22" +imagesize = ">=1.3" +importlib-metadata = {version = ">=4.8", markers = "python_version < \"3.10\""} +Jinja2 = ">=3.0" +packaging = ">=21.0" +Pygments = ">=2.14" +requests = ">=2.25.0" +snowballstemmer = ">=2.0" +sphinxcontrib-applehelp = "*" +sphinxcontrib-devhelp = "*" +sphinxcontrib-htmlhelp = ">=2.0.0" +sphinxcontrib-jsmath = "*" +sphinxcontrib-qthelp = "*" +sphinxcontrib-serializinghtml = ">=1.1.9" +tomli = {version = ">=2", markers = "python_version < \"3.11\""} + +[package.extras] +docs = ["sphinxcontrib-websupport"] +lint = ["flake8 (>=3.5.0)", "importlib_metadata", "mypy (==1.9.0)", "pytest (>=6.0)", "ruff (==0.3.7)", "sphinx-lint", "tomli", "types-docutils", "types-requests"] +test = ["cython (>=3.0)", "defusedxml (>=0.7.1)", "pytest (>=6.0)", "setuptools (>=67.0)"] + +[[package]] +name = "sphinxcontrib-applehelp" +version = "1.0.8" +description = "sphinxcontrib-applehelp is a Sphinx extension which outputs Apple help books" +optional = false +python-versions = ">=3.9" +files = [ + {file = "sphinxcontrib_applehelp-1.0.8-py3-none-any.whl", hash = "sha256:cb61eb0ec1b61f349e5cc36b2028e9e7ca765be05e49641c97241274753067b4"}, + {file = "sphinxcontrib_applehelp-1.0.8.tar.gz", hash = "sha256:c40a4f96f3776c4393d933412053962fac2b84f4c99a7982ba42e09576a70619"}, +] + +[package.extras] +lint = ["docutils-stubs", "flake8", "mypy"] +standalone = ["Sphinx (>=5)"] +test = ["pytest"] + +[[package]] +name = "sphinxcontrib-devhelp" +version = "1.0.6" +description = "sphinxcontrib-devhelp is a sphinx extension which outputs Devhelp documents" +optional = false +python-versions = ">=3.9" +files = [ + {file = "sphinxcontrib_devhelp-1.0.6-py3-none-any.whl", hash = "sha256:6485d09629944511c893fa11355bda18b742b83a2b181f9a009f7e500595c90f"}, + {file = "sphinxcontrib_devhelp-1.0.6.tar.gz", hash = "sha256:9893fd3f90506bc4b97bdb977ceb8fbd823989f4316b28c3841ec128544372d3"}, +] + +[package.extras] +lint = ["docutils-stubs", "flake8", "mypy"] +standalone = ["Sphinx (>=5)"] +test = ["pytest"] + +[[package]] +name = "sphinxcontrib-htmlhelp" +version = "2.0.5" +description = "sphinxcontrib-htmlhelp is a sphinx extension which renders HTML help files" +optional = false +python-versions = ">=3.9" +files = [ + {file = "sphinxcontrib_htmlhelp-2.0.5-py3-none-any.whl", hash = "sha256:393f04f112b4d2f53d93448d4bce35842f62b307ccdc549ec1585e950bc35e04"}, + {file = "sphinxcontrib_htmlhelp-2.0.5.tar.gz", hash = "sha256:0dc87637d5de53dd5eec3a6a01753b1ccf99494bd756aafecd74b4fa9e729015"}, +] + +[package.extras] +lint = ["docutils-stubs", "flake8", "mypy"] +standalone = ["Sphinx (>=5)"] +test = ["html5lib", "pytest"] + +[[package]] +name = "sphinxcontrib-jsmath" +version = "1.0.1" +description = "A sphinx extension which renders display math in HTML via JavaScript" +optional = false +python-versions = ">=3.5" +files = [ + {file = "sphinxcontrib-jsmath-1.0.1.tar.gz", hash = "sha256:a9925e4a4587247ed2191a22df5f6970656cb8ca2bd6284309578f2153e0c4b8"}, + {file = "sphinxcontrib_jsmath-1.0.1-py2.py3-none-any.whl", hash = "sha256:2ec2eaebfb78f3f2078e73666b1415417a116cc848b72e5172e596c871103178"}, +] + +[package.extras] +test = ["flake8", "mypy", "pytest"] + +[[package]] +name = "sphinxcontrib-qthelp" +version = "1.0.7" +description = "sphinxcontrib-qthelp is a sphinx extension which outputs QtHelp documents" +optional = false +python-versions = ">=3.9" +files = [ + {file = "sphinxcontrib_qthelp-1.0.7-py3-none-any.whl", hash = "sha256:e2ae3b5c492d58fcbd73281fbd27e34b8393ec34a073c792642cd8e529288182"}, + {file = "sphinxcontrib_qthelp-1.0.7.tar.gz", hash = "sha256:053dedc38823a80a7209a80860b16b722e9e0209e32fea98c90e4e6624588ed6"}, +] + +[package.extras] +lint = ["docutils-stubs", "flake8", "mypy"] +standalone = ["Sphinx (>=5)"] +test = ["pytest"] + +[[package]] +name = "sphinxcontrib-serializinghtml" +version = "1.1.10" +description = "sphinxcontrib-serializinghtml is a sphinx extension which outputs \"serialized\" HTML files (json and pickle)" +optional = false +python-versions = ">=3.9" +files = [ + {file = "sphinxcontrib_serializinghtml-1.1.10-py3-none-any.whl", hash = "sha256:326369b8df80a7d2d8d7f99aa5ac577f51ea51556ed974e7716cfd4fca3f6cb7"}, + {file = "sphinxcontrib_serializinghtml-1.1.10.tar.gz", hash = "sha256:93f3f5dc458b91b192fe10c397e324f262cf163d79f3282c158e8436a2c4511f"}, +] + +[package.extras] +lint = ["docutils-stubs", "flake8", "mypy"] +standalone = ["Sphinx (>=5)"] +test = ["pytest"] + +[[package]] +name = "tomli" +version = "2.0.1" +description = "A lil' TOML parser" +optional = false +python-versions = ">=3.7" +files = [ + {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, + {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, +] + +[[package]] +name = "tomlkit" +version = "0.12.4" +description = "Style preserving TOML library" +optional = false +python-versions = ">=3.7" +files = [ + {file = "tomlkit-0.12.4-py3-none-any.whl", hash = "sha256:5cd82d48a3dd89dee1f9d64420aa20ae65cfbd00668d6f094d7578a78efbb77b"}, + {file = "tomlkit-0.12.4.tar.gz", hash = "sha256:7ca1cfc12232806517a8515047ba66a19369e71edf2439d0f5824f91032b6cc3"}, +] + +[[package]] +name = "tox" +version = "4.14.2" +description = "tox is a generic virtualenv management and test command line tool" +optional = false +python-versions = ">=3.8" +files = [ + {file = "tox-4.14.2-py3-none-any.whl", hash = "sha256:2900c4eb7b716af4a928a7fdc2ed248ad6575294ed7cfae2ea41203937422847"}, + {file = "tox-4.14.2.tar.gz", hash = "sha256:0defb44f6dafd911b61788325741cc6b2e12ea71f987ac025ad4d649f1f1a104"}, +] + +[package.dependencies] +cachetools = ">=5.3.2" +chardet = ">=5.2" +colorama = ">=0.4.6" +filelock = ">=3.13.1" +packaging = ">=23.2" +platformdirs = ">=4.1" +pluggy = ">=1.3" +pyproject-api = ">=1.6.1" +tomli = {version = ">=2.0.1", markers = "python_version < \"3.11\""} +virtualenv = ">=20.25" + +[package.extras] +docs = ["furo (>=2023.9.10)", "sphinx (>=7.2.6)", "sphinx-argparse-cli (>=1.11.1)", "sphinx-autodoc-typehints (>=1.25.2)", "sphinx-copybutton (>=0.5.2)", "sphinx-inline-tabs (>=2023.4.21)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.11)"] +testing = ["build[virtualenv] (>=1.0.3)", "covdefaults (>=2.3)", "detect-test-pollution (>=1.2)", "devpi-process (>=1)", "diff-cover (>=8.0.2)", "distlib (>=0.3.8)", "flaky (>=3.7)", "hatch-vcs (>=0.4)", "hatchling (>=1.21)", "psutil (>=5.9.7)", "pytest (>=7.4.4)", "pytest-cov (>=4.1)", "pytest-mock (>=3.12)", "pytest-xdist (>=3.5)", "re-assert (>=1.1)", "time-machine (>=2.13)", "wheel (>=0.42)"] + +[[package]] +name = "trove-classifiers" +version = "2024.4.10" +description = "Canonical source for classifiers on PyPI (pypi.org)." +optional = false +python-versions = "*" +files = [ + {file = "trove-classifiers-2024.4.10.tar.gz", hash = "sha256:49f40bb6a746b72a1cba4f8d55ee8252169cda0f70802e3fd24f04b7fb25a492"}, + {file = "trove_classifiers-2024.4.10-py3-none-any.whl", hash = "sha256:678bd6fcc5218d72e3304e27a608acc9b91e17bd00c3f3d8c968497c843ad98b"}, +] + +[[package]] +name = "typing-extensions" +version = "4.11.0" +description = "Backported and Experimental Type Hints for Python 3.8+" +optional = false +python-versions = ">=3.8" +files = [ + {file = "typing_extensions-4.11.0-py3-none-any.whl", hash = "sha256:c1f94d72897edaf4ce775bb7558d5b79d8126906a14ea5ed1635921406c0387a"}, + {file = "typing_extensions-4.11.0.tar.gz", hash = "sha256:83f085bd5ca59c80295fc2a82ab5dac679cbe02b9f33f7d83af68e241bea51b0"}, +] + +[[package]] +name = "urllib3" +version = "2.2.1" +description = "HTTP library with thread-safe connection pooling, file post, and more." +optional = false +python-versions = ">=3.8" +files = [ + {file = "urllib3-2.2.1-py3-none-any.whl", hash = "sha256:450b20ec296a467077128bff42b73080516e71b56ff59a60a02bef2232c4fa9d"}, + {file = "urllib3-2.2.1.tar.gz", hash = "sha256:d0570876c61ab9e520d776c38acbbb5b05a776d3f9ff98a5c8fd5162a444cf19"}, +] + +[package.extras] +brotli = ["brotli (>=1.0.9)", "brotlicffi (>=0.8.0)"] +h2 = ["h2 (>=4,<5)"] +socks = ["pysocks (>=1.5.6,!=1.5.7,<2.0)"] +zstd = ["zstandard (>=0.18.0)"] + +[[package]] +name = "virtualenv" +version = "20.25.3" +description = "Virtual Python Environment builder" +optional = false +python-versions = ">=3.7" +files = [ + {file = "virtualenv-20.25.3-py3-none-any.whl", hash = "sha256:8aac4332f2ea6ef519c648d0bc48a5b1d324994753519919bddbb1aff25a104e"}, + {file = "virtualenv-20.25.3.tar.gz", hash = "sha256:7bb554bbdfeaacc3349fa614ea5bff6ac300fc7c335e9facf3a3bcfc703f45be"}, +] + +[package.dependencies] +distlib = ">=0.3.7,<1" +filelock = ">=3.12.2,<4" +platformdirs = ">=3.9.1,<5" + +[package.extras] +docs = ["furo (>=2023.7.26)", "proselint (>=0.13)", "sphinx (>=7.1.2,!=7.3)", "sphinx-argparse (>=0.4)", "sphinxcontrib-towncrier (>=0.2.1a0)", "towncrier (>=23.6)"] +test = ["covdefaults (>=2.3)", "coverage (>=7.2.7)", "coverage-enable-subprocess (>=1)", "flaky (>=3.7)", "packaging (>=23.1)", "pytest (>=7.4)", "pytest-env (>=0.8.2)", "pytest-freezer (>=0.4.8)", "pytest-mock (>=3.11.1)", "pytest-randomly (>=3.12)", "pytest-timeout (>=2.1)", "setuptools (>=68)", "time-machine (>=2.10)"] + +[[package]] +name = "xattr" +version = "1.1.0" +description = "Python wrapper for extended filesystem attributes" +optional = false +python-versions = ">=3.8" +files = [ + {file = "xattr-1.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:ef2fa0f85458736178fd3dcfeb09c3cf423f0843313e25391db2cfd1acec8888"}, + {file = "xattr-1.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:ccab735d0632fe71f7d72e72adf886f45c18b7787430467ce0070207882cfe25"}, + {file = "xattr-1.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9013f290387f1ac90bccbb1926555ca9aef75651271098d99217284d9e010f7c"}, + {file = "xattr-1.1.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9dcd5dfbcee73c7be057676ecb900cabb46c691aff4397bf48c579ffb30bb963"}, + {file = "xattr-1.1.0-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6480589c1dac7785d1f851347a32c4a97305937bf7b488b857fe8b28a25de9e9"}, + {file = "xattr-1.1.0-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:08f61cbed52dc6f7c181455826a9ff1e375ad86f67dd9d5eb7663574abb32451"}, + {file = "xattr-1.1.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:918e1f83f2e8a072da2671eac710871ee5af337e9bf8554b5ce7f20cdb113186"}, + {file = "xattr-1.1.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:0f06e0c1e4d06b4e0e49aaa1184b6f0e81c3758c2e8365597918054890763b53"}, + {file = "xattr-1.1.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:46a641ac038a9f53d2f696716147ca4dbd6a01998dc9cd4bc628801bc0df7f4d"}, + {file = "xattr-1.1.0-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:7e4ca0956fd11679bb2e0c0d6b9cdc0f25470cc00d8da173bb7656cc9a9cf104"}, + {file = "xattr-1.1.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:6881b120f9a4b36ccd8a28d933bc0f6e1de67218b6ce6e66874e0280fc006844"}, + {file = "xattr-1.1.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:dab29d9288aa28e68a6f355ddfc3f0a7342b40c9012798829f3e7bd765e85c2c"}, + {file = "xattr-1.1.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e0c80bbf55339c93770fc294b4b6586b5bf8e85ec00a4c2d585c33dbd84b5006"}, + {file = "xattr-1.1.0-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:d1418705f253b6b6a7224b69773842cac83fcbcd12870354b6e11dd1cd54630f"}, + {file = "xattr-1.1.0-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:687e7d18611ef8d84a6ecd8f4d1ab6757500c1302f4c2046ce0aa3585e13da3f"}, + {file = "xattr-1.1.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:b6ceb9efe0657a982ccb8b8a2efe96b690891779584c901d2f920784e5d20ae3"}, + {file = "xattr-1.1.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:b489b7916f239100956ea0b39c504f3c3a00258ba65677e4c8ba1bd0b5513446"}, + {file = "xattr-1.1.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:0a9c431b0e66516a078125e9a273251d4b8e5ba84fe644b619f2725050d688a0"}, + {file = "xattr-1.1.0-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:1a5921ea3313cc1c57f2f53b63ea8ca9a91e48f4cc7ebec057d2447ec82c7efe"}, + {file = "xattr-1.1.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:f6ad2a7bd5e6cf71d4a862413234a067cf158ca0ae94a40d4b87b98b62808498"}, + {file = "xattr-1.1.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:0683dae7609f7280b0c89774d00b5957e6ffcb181c6019c46632b389706b77e6"}, + {file = "xattr-1.1.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:54cb15cd94e5ef8a0ef02309f1bf973ba0e13c11e87686e983f371948cfee6af"}, + {file = "xattr-1.1.0-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ff6223a854229055e803c2ad0c0ea9a6da50c6be30d92c198cf5f9f28819a921"}, + {file = "xattr-1.1.0-cp312-cp312-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d44e8f955218638c9ab222eed21e9bd9ab430d296caf2176fb37abe69a714e5c"}, + {file = "xattr-1.1.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:caab2c2986c30f92301f12e9c50415d324412e8e6a739a52a603c3e6a54b3610"}, + {file = "xattr-1.1.0-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:d6eb7d5f281014cd44e2d847a9107491af1bf3087f5afeded75ed3e37ec87239"}, + {file = "xattr-1.1.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:47a3bdfe034b4fdb70e5941d97037405e3904accc28e10dbef6d1c9061fb6fd7"}, + {file = "xattr-1.1.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:00d2b415cf9d6a24112d019e721aa2a85652f7bbc9f3b9574b2d1cd8668eb491"}, + {file = "xattr-1.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:78b377832dd0ee408f9f121a354082c6346960f7b6b1480483ed0618b1912120"}, + {file = "xattr-1.1.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:6461a43b585e5f2e049b39bcbfcb6391bfef3c5118231f1b15d10bdb89ef17fe"}, + {file = "xattr-1.1.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:24d97f0d28f63695e3344ffdabca9fcc30c33e5c8ccc198c7524361a98d526f2"}, + {file = "xattr-1.1.0-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ad47d89968c9097900607457a0c89160b4771601d813e769f68263755516065"}, + {file = "xattr-1.1.0-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dc53cab265f6e8449bd683d5ee3bc5a191e6dd940736f3de1a188e6da66b0653"}, + {file = "xattr-1.1.0-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:cd11e917f5b89f2a0ad639d9875943806c6c9309a3dd02da5a3e8ef92db7bed9"}, + {file = "xattr-1.1.0-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:9c5a78c7558989492c4cb7242e490ffb03482437bf782967dfff114e44242343"}, + {file = "xattr-1.1.0-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:cebcf8a303a44fbc439b68321408af7267507c0d8643229dbb107f6c132d389c"}, + {file = "xattr-1.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:b0d73150f2f9655b4da01c2369eb33a294b7f9d56eccb089819eafdbeb99f896"}, + {file = "xattr-1.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:793c01deaadac50926c0e1481702133260c7cb5e62116762f6fe1543d07b826f"}, + {file = "xattr-1.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:e189e440bcd04ccaad0474720abee6ee64890823ec0db361fb0a4fb5e843a1bf"}, + {file = "xattr-1.1.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:afacebbc1fa519f41728f8746a92da891c7755e6745164bd0d5739face318e86"}, + {file = "xattr-1.1.0-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:9b1664edf003153ac8d1911e83a0fc60db1b1b374ee8ac943f215f93754a1102"}, + {file = "xattr-1.1.0-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dda2684228798e937a7c29b0e1c7ef3d70e2b85390a69b42a1c61b2039ba81de"}, + {file = "xattr-1.1.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:b735ac2625a4fc2c9343b19f806793db6494336338537d2911c8ee4c390dda46"}, + {file = "xattr-1.1.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:fa6a7af7a4ada43f15ccc58b6f9adcdbff4c36ba040013d2681e589e07ae280a"}, + {file = "xattr-1.1.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:d1059b2f726e2702c8bbf9bbf369acfc042202a4cc576c2dec6791234ad5e948"}, + {file = "xattr-1.1.0-pp310-pypy310_pp73-macosx_10_9_x86_64.whl", hash = "sha256:e2255f36ebf2cb2dbf772a7437ad870836b7396e60517211834cf66ce678b595"}, + {file = "xattr-1.1.0-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dba4f80b9855cc98513ddf22b7ad8551bc448c70d3147799ea4f6c0b758fb466"}, + {file = "xattr-1.1.0-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4cb70c16e7c3ae6ba0ab6c6835c8448c61d8caf43ea63b813af1f4dbe83dd156"}, + {file = "xattr-1.1.0-pp310-pypy310_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:83652910ef6a368b77b00825ad67815e5c92bfab551a848ca66e9981d14a7519"}, + {file = "xattr-1.1.0-pp38-pypy38_pp73-macosx_10_9_x86_64.whl", hash = "sha256:7a92aff66c43fa3e44cbeab7cbeee66266c91178a0f595e044bf3ce51485743b"}, + {file = "xattr-1.1.0-pp38-pypy38_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9d4f71b673339aeaae1f6ea9ef8ea6c9643c8cd0df5003b9a0eaa75403e2e06c"}, + {file = "xattr-1.1.0-pp38-pypy38_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a20de1c47b5cd7b47da61799a3b34e11e5815d716299351f82a88627a43f9a96"}, + {file = "xattr-1.1.0-pp38-pypy38_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:23705c7079b05761ff2fa778ad17396e7599c8759401abc05b312dfb3bc99f69"}, + {file = "xattr-1.1.0-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:27272afeba8422f2a9d27e1080a9a7b807394e88cce73db9ed8d2dde3afcfb87"}, + {file = "xattr-1.1.0-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dd43978966de3baf4aea367c99ffa102b289d6c2ea5f3d9ce34a203dc2f2ab73"}, + {file = "xattr-1.1.0-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ded771eaf27bb4eb3c64c0d09866460ee8801d81dc21097269cf495b3cac8657"}, + {file = "xattr-1.1.0-pp39-pypy39_pp73-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:96ca300c0acca4f0cddd2332bb860ef58e1465d376364f0e72a1823fdd58e90d"}, + {file = "xattr-1.1.0.tar.gz", hash = "sha256:fecbf3b05043ed3487a28190dec3e4c4d879b2fcec0e30bafd8ec5d4b6043630"}, +] + +[package.dependencies] +cffi = ">=1.16.0" + +[package.extras] +test = ["pytest"] + +[[package]] +name = "xmltodict" +version = "0.13.0" +description = "Makes working with XML feel like you are working with JSON" +optional = false +python-versions = ">=3.4" +files = [ + {file = "xmltodict-0.13.0-py2.py3-none-any.whl", hash = "sha256:aa89e8fd76320154a40d19a0df04a4695fb9dc5ba977cbb68ab3e4eb225e7852"}, + {file = "xmltodict-0.13.0.tar.gz", hash = "sha256:341595a488e3e01a85a9d8911d8912fd922ede5fecc4dce437eb4b6c8d037e56"}, +] + +[[package]] +name = "zipp" +version = "3.18.1" +description = "Backport of pathlib-compatible object wrapper for zip files" +optional = false +python-versions = ">=3.8" +files = [ + {file = "zipp-3.18.1-py3-none-any.whl", hash = "sha256:206f5a15f2af3dbaee80769fb7dc6f249695e940acca08dfb2a4769fe61e538b"}, + {file = "zipp-3.18.1.tar.gz", hash = "sha256:2884ed22e7d8961de1c9a05142eb69a247f120291bc0206a00a7642f09b5b715"}, +] + +[package.extras] +docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "rst.linker (>=1.9)", "sphinx (>=3.5)", "sphinx-lint"] +testing = ["big-O", "jaraco.functools", "jaraco.itertools", "more-itertools", "pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-ignore-flaky", "pytest-mypy", "pytest-ruff (>=0.2.1)"] + +[metadata] +lock-version = "2.0" +python-versions = "^3.9" +content-hash = "adb6c864e0226d714ab9223a96921887e4f749a9a5d16435bc227fe2ecadc2f7" diff --git a/pyetrade/accounts.py b/pyetrade/accounts.py index 48f8892..28cb413 100644 --- a/pyetrade/accounts.py +++ b/pyetrade/accounts.py @@ -1,34 +1,28 @@ -"""Accounts - ETrade Accounts API Calls - - TODO: - * Check request response for error - - """ - import logging +from datetime import datetime + import xmltodict from requests_oauthlib import OAuth1Session -# Set up logging LOGGER = logging.getLogger(__name__) class ETradeAccounts(object): """:description: Accounts object to access account information - :param client_key: Client key provided by Etrade - :type client_key: str, required - :param client_secret: Client secret provided by Etrade - :type client_secret: str, required - :param resource_owner_key: Resource key from :class:`pyetrade.authorization.ETradeOAuth` - :type resource_owner_key: str, required - :param resource_owner_secret: Resource secret from - :class:`pyetrade.authorization.ETradeOAuth` - :type resource_owner_secret: str, required - :param dev: Defines Sandbox (True) or Live (False) ETrade, defaults to True - :type dev: bool, optional - :EtradeRef: https://apisb.etrade.com/docs/api/account/api-account-v1.html - """ + :param client_key: Client key provided by Etrade + :type client_key: str, required + :param client_secret: Client secret provided by Etrade + :type client_secret: str, required + :param resource_owner_key: Resource key from :class:`pyetrade.authorization.ETradeOAuth` + :type resource_owner_key: str, required + :param resource_owner_secret: Resource secret from + :class:`pyetrade.authorization.ETradeOAuth` + :type resource_owner_secret: str, required + :param dev: Defines Sandbox (True) or Live (False) ETrade, defaults to True + :type dev: bool, optional + :EtradeRef: https://apisb.etrade.com/docs/api/account/api-account-v1.html + """ def __init__( self, @@ -38,16 +32,11 @@ def __init__( resource_owner_secret: str, dev: bool = True, ): - """__init_() - """ self.client_key = client_key self.client_secret = client_secret self.resource_owner_key = resource_owner_key self.resource_owner_secret = resource_owner_secret - suffix = "apisb" if dev else "api" - self.base_url = r"https://%s.etrade.com/v1/accounts" % suffix - # self.base_url_prod = r"https://api.etrade.com/v1/accounts" - # self.base_url_dev = r"https://apisb.etrade.com/v1/accounts" + self.base_url = f'https://{"apisb" if dev else "api"}.etrade.com/v1/accounts' self.session = OAuth1Session( self.client_key, self.client_secret, @@ -56,14 +45,14 @@ def __init__( signature_type="AUTH_HEADER", ) - def list_accounts(self, resp_format="xml") -> dict: + def list_accounts(self, resp_format: str = "xml") -> dict: """:description: Lists accounts in Etrade - :param resp_format: Desired Response format, defaults to xml - :type resp_format: str, optional - :return: List of accounts - :rtype: xml or json based on ``resp_format`` - :EtradeRef: https://apisb.etrade.com/docs/api/account/api-account-v1.html + :param resp_format: Desired Response format, defaults to xml + :type resp_format: str, optional + :return: List of accounts + :rtype: xml or json based on ``resp_format`` + :EtradeRef: https://apisb.etrade.com/docs/api/account/api-account-v1.html """ api_url = "%s/list%s" % ( @@ -72,26 +61,34 @@ def list_accounts(self, resp_format="xml") -> dict: ) LOGGER.debug(api_url) + req = self.session.get(api_url) req.raise_for_status() + LOGGER.debug(req.text) return xmltodict.parse(req.text) if resp_format.lower() == "xml" else req.json() - def get_account_balance(self, account_id_key: str, account_type: str = None, real_time: bool = True, resp_format="xml") -> dict: # noqa: E501 + def get_account_balance( + self, + account_id_key: str, + account_type: str = None, + real_time: bool = True, + resp_format: str = "xml", + ) -> dict: """:description: Retrieves account balance for an account - :param account_id_key: AccountIDkey retrieved from :class:`list_accounts` - :type account_id_key: str, required - :param account_type: The registered account type, defaults to None - :type account_type: str, optional - :param real_time: Use real time balance or not, defaults to True - :type real_time: bool, optional - :param resp_format: Desired Response format, defaults to xml - :type resp_format: str, optional - :return: Balance of account with key ``account_id_key`` - :rtype: xml or json based on ``resp_format`` - :EtradeRef: https://apisb.etrade.com/docs/api/account/api-balance-v1.html + :param account_id_key: AccountIDkey retrieved from :class:`list_accounts` + :type account_id_key: str, required + :param account_type: The registered account type, defaults to None + :type account_type: str, optional + :param real_time: Use real time balance or not, defaults to True + :type real_time: bool, optional + :param resp_format: Desired Response format, defaults to xml + :type resp_format: str, optional + :return: Balance of account with key ``account_id_key`` + :rtype: xml or json based on ``resp_format`` + :EtradeRef: https://apisb.etrade.com/docs/api/account/api-balance-v1.html """ api_url = "%s/%s/balance%s" % ( @@ -106,52 +103,63 @@ def get_account_balance(self, account_id_key: str, account_type: str = None, rea payload["accountType"] = account_type LOGGER.debug(api_url) + req = self.session.get(api_url, params=payload) req.raise_for_status() + LOGGER.debug(req.text) return xmltodict.parse(req.text) if resp_format.lower() == "xml" else req.json() def get_account_portfolio( - self, - account_id_key: str, - count: int = 50, - sort_by: str = None, - sort_order: str = "DESC", - page_number: int = None, - market_session: str = "REGULAR", - totals_required: bool = False, - lots_required: bool = False, - view: str = "QUICK", - resp_format="xml" + self, + account_id_key: str, + count: int = 50, + sort_by: str = None, + sort_order: str = "DESC", + page_number: int = None, + market_session: str = "REGULAR", + totals_required: bool = False, + lots_required: bool = False, + view: str = "QUICK", + resp_format: str = "xml", ) -> dict: """:description: Retrieves account portfolio for an account - :param account_id_key: AccountIDkey retrieved from :class:`list_accounts` - :type account_id_key: str, required - :param count: The number of positions to return in the response, defaults to 50 - :type count: int, optional - :param sort_by: Sorting done based on the column specified in the query parameter. - :type sort_by: str, optional - :param sort_order: Sort orders (ASC or DESC), defaults to DESC - :type sort_order: str, optional - :param page_number: The specific page that in the list that is to be returned. Each page has a default count of 50 positions. # noqa: E501 - :type page_number: int, optional - :param market_session: The market session (Regular or Extended), defaults to REGULAR - :type market_session: str, optional - :param totals_required: It gives the total values of the portfolio, defaults to False - :type totals_required: bool, optional - :param lots_required: It gives position lots for positions, defaults to False - :type lots_required: bool, optional - :param view: The view query: PERFORMANCE, FUNDAMENTAL, OPTIONSWATCH, QUICK, COMPLETE. Defaults to QUICK. - :type view: str, optional - :param resp_format: Desired Response format, defaults to xml - :type resp_format: str, optional - :return: Account portfolio of account with key ``account_id_key`` - :rtype: xml or json based on ``resp_format`` - :EtradeRef: https://apisb.etrade.com/docs/api/account/api-portfolio-v1.html - - """ + :param account_id_key: AccountIDkey retrieved from :class:`list_accounts` + :type account_id_key: str, required + :param count: The number of positions to return in the response, defaults to 50 + :type count: int, optional + :param sort_by: Sorting done based on the column specified in the query parameter. + :type sort_by: str, optional + :param sort_order: Sort orders (ASC or DESC), defaults to DESC + :type sort_order: str, optional + :param page_number: The specific page that in the list that is to be returned. + Each page has a default count of 50 positions. + :type page_number: int, optional + :param market_session: The market session, defaults to REGULAR + :type market_session: str, optional + :market_session values: + * REGULAR + * EXTENDED + :param totals_required: It gives the total values of the portfolio, defaults to False + :type totals_required: bool, optional + :param lots_required: It gives position lots for positions, defaults to False + :type lots_required: bool, optional + :param view: The view query, defaults to QUICK. + :type view: str, optional + :view values: + * PERFORMANCE + * FUNDAMENTAL + * OPTIONSWATCH + * QUICK + * COMPLETE + :param resp_format: Desired Response format, defaults to xml + :type resp_format: str, optional + :return: Account portfolio of account with key ``account_id_key`` + :rtype: xml or json based on ``resp_format`` + :EtradeRef: https://apisb.etrade.com/docs/api/account/api-portfolio-v1.html + """ api_url = "%s/%s/portfolio%s" % ( self.base_url, @@ -167,45 +175,99 @@ def get_account_portfolio( "marketSession": market_session, "totalsRequired": totals_required, "lotsRequired": lots_required, - "view": view + "view": view, } LOGGER.debug(api_url) + req = self.session.get(api_url, params=payload) req.raise_for_status() + + LOGGER.debug(req.text) + + return xmltodict.parse(req.text) if resp_format.lower() == "xml" else req.json() + + def get_portfolio_position_lot( + self, symbol: str, account_id_key: str, resp_format: str = "xml" + ) -> dict: + """:description: Retrieves account portfolio position lot based on provided symbol + + :param symbol: Desired equity symbol to search for position lots in desired account portfolio + :type symbol: str, required + :param account_id_key: AccountIDkey retrieved from :class:`list_accounts` + :type account_id_key: str, required + :param resp_format: Desired Response format, defaults to xml + :type resp_format: str, optional + :return: PositionLot of ``symbol`` in account portfolio of account with key ``account_id_key`` + :rtype: xml or json based on ``resp_format`` + :EtradeRef: https://apisb.etrade.com/docs/api/account/api-portfolio-v1.html + """ + + account_portfolio = self.get_account_portfolio( + account_id_key, lots_required=True, resp_format="json" + )["PortfolioResponse"]["AccountPortfolio"][0]["Position"] + + lot_position_id = [ + position["positionId"] + for position in account_portfolio + if symbol.upper() == position["Product"]["symbol"].upper() + ] + + # If the symbol exists then there should only be one ID filtered from the portfolio response + if len(lot_position_id) != 1: + raise KeyError( + f'Symbol "{symbol}" could not be found in the current portfolio. ' + f"Please check your portfolio and symbol before trying again." + ) + + LOGGER.debug(lot_position_id[0]) + + api_url = "%s/%s/portfolio/%s%s" % ( + self.base_url, + account_id_key, + lot_position_id[0], + ".json" if resp_format == "json" else "", + ) + + req = self.session.get(api_url) + req.raise_for_status() + LOGGER.debug(req.text) return xmltodict.parse(req.text) if resp_format.lower() == "xml" else req.json() def list_transactions( - self, - account_id_key: str, - start_date: str = None, - end_date: str = None, - sort_order: str = "DESC", - marker=None, - count: int = 50, - resp_format="xml" + self, + account_id_key: str, + start_date: datetime = None, + end_date: datetime = None, + sort_order: str = "DESC", + marker: str = None, + count: int = 50, + resp_format: str = "xml", ) -> dict: """:description: Retrieves transactions for an account - :param account_id_key: AccountIDKey retrieved from :class:`list_accounts` - :type account_id_key: str, required - :param start_date: The earliest date to include in the date range, formatted as MMDDYYYY (history is available for two years), default is None # noqa: E501 - :type start_date: str, optional - :param end_date: The latest date to include in the date range, formatted as MMDDYYYY, default is None - :type end_date: `str, optional - :param sort_order: The sort order request (ASC or DESC), default is DESC - :type sort_order: str, optional - :param marker: Specifies the desired starting point of the set of items to return (used for paging), default is None # noqa: E501 - :type marker: ??, optional - :param count: Number of transactions to return in the response, default is 50 - :type count: int, optional - :param resp_format: Desired Response format, defaults to xml - :type resp_format: str, optional - :return: Transactions list for account with key ``account_id_key`` - :rtype: xml or json based on ``resp_format`` - :EtradeRef: https://apisb.etrade.com/docs/api/account/api-transaction-v1.html + :param account_id_key: AccountIDKey retrieved from :class:`list_accounts` + :type account_id_key: str, required + :param start_date: The earliest date to include in the date range (history is available for two years), + defaults to None + :type start_date: datetime obj, optional + :param end_date: The latest date to include in the date range (history is available for two years), + defaults to None + :type end_date: datetime obj, optional + :param sort_order: The sort order request (ASC or DESC), default is DESC + :type sort_order: str, optional + :param marker: Specifies the desired starting point of the set of items to return (used for paging), + default is None + :type marker: str, optional + :param count: Number of transactions to return in the response, default is 50 + :type count: int, optional + :param resp_format: Desired Response format, defaults to xml + :type resp_format: str, optional + :return: Transactions list for account with key ``account_id_key`` + :rtype: xml or json based on ``resp_format`` + :EtradeRef: https://apisb.etrade.com/docs/api/account/api-transaction-v1.html """ api_url = "%s/%s/transactions%s" % ( @@ -215,16 +277,18 @@ def list_transactions( ) payload = { - "startDate": start_date, - "endDate": end_date, + "startDate": start_date.date().strftime("%m%d%Y") if start_date else None, + "endDate": end_date.date().strftime("%m%d%Y") if end_date else None, "sortOrder": sort_order, "marker": marker, - "count": count + "count": count, } LOGGER.debug(api_url) + req = self.session.get(api_url, params=payload) req.raise_for_status() + LOGGER.debug(req.text) # Depending on when transactions are completed and start/end date @@ -236,33 +300,41 @@ def list_transactions( else: return req.json() - def list_transaction_details(self, account_id_key: str, transaction_id: int, resp_format="xml", **kwargs) -> dict: + def list_transaction_details( + self, + account_id_key: str, + transaction_id: int, + store_id: any = None, + resp_format: str = "xml", + ) -> dict: """:description: Retrieves transaction details for an account - :param account_id_key: AccountIDKey retrieved from :class:`list_accounts` - :type account_id_key: str, required - :param transaction_id: Numeric transaction ID obtained from :class:`list_transactions` - :type transaction_id: int, required - :param resp_format: Desired Response format, defaults to xml - :type resp_format: str, optional - :param kwargs: Parameters for api - :type kwargs: ``**kwargs``, optional - :return: Transaction Details for ``transaction_id`` for account key ``account_id_key`` - :rtype: xml or json based on ``resp_format`` - :EtradeRef: https://apisb.etrade.com/docs/api/account/api-transaction-v1.html - """ + :param account_id_key: AccountIDKey retrieved from :class:`list_accounts` + :type account_id_key: str, required + :param transaction_id: Numeric transaction ID obtained from :class:`list_transactions` + :type transaction_id: int, required + :param store_id: storage location for older transactions + :type store_id: Unknown, optional + :param resp_format: Desired Response format, defaults to xml + :type resp_format: str, optional + :return: Transaction Details for ``transaction_id`` for account key ``account_id_key`` + :rtype: xml or json based on ``resp_format`` + :EtradeRef: https://apisb.etrade.com/docs/api/account/api-transaction-v1.html + """ # Set Env - api_url = "%s/%s/transactions%s/%s" % ( + api_url = "%s/%s/transactions/%s%s" % ( self.base_url, account_id_key, - ".json" if resp_format == "json" else "", transaction_id, + ".json" if resp_format == "json" else "", ) LOGGER.debug(api_url) - req = self.session.get(api_url, params=kwargs) + + req = self.session.get(api_url, params={"storeId": store_id}) req.raise_for_status() + LOGGER.debug(req.text) return xmltodict.parse(req.text) if resp_format.lower() == "xml" else req.json() diff --git a/pyetrade/alerts.py b/pyetrade/alerts.py index 1eb11b0..0abfc6f 100644 --- a/pyetrade/alerts.py +++ b/pyetrade/alerts.py @@ -1,7 +1,7 @@ """Alerts - ETrade Alerts API """ - import logging + import xmltodict from requests_oauthlib import OAuth1Session @@ -12,19 +12,18 @@ class ETradeAlerts(object): """:description: Object to retrieve alerts - :param client_key: Client key provided by Etrade - :type client_key: str, required - :param client_secret: Client secret provided by Etrade - :type client_secret: str, required - :param resource_owner_key: Resource key from :class:`pyetrade.authorization.ETradeOAuth` - :type resource_owner_key: str, required - :param resource_owner_secret: Resource secret from - :class:`pyetrade.authorization.ETradeOAuth` - :type resource_owner_secret: str, required - :param dev: Defines Sandbox (True) or Live (False) ETrade, defaults to True - :type dev: bool, optional - :EtradeRef: https://apisb.etrade.com/docs/api/user/api-alert-v1.html - + :param client_key: Client key provided by Etrade + :type client_key: str, required + :param client_secret: Client secret provided by Etrade + :type client_secret: str, required + :param resource_owner_key: Resource key from :class:`pyetrade.authorization.ETradeOAuth` + :type resource_owner_key: str, required + :param resource_owner_secret: Resource secret from + :class:`pyetrade.authorization.ETradeOAuth` + :type resource_owner_secret: str, required + :param dev: Defines Sandbox (True) or Live (False) ETrade, defaults to True + :type dev: bool, optional + :EtradeRef: https://apisb.etrade.com/docs/api/user/api-alert-v1.html """ def __init__( @@ -39,8 +38,7 @@ def __init__( self.client_secret = client_secret self.resource_owner_key = resource_owner_key self.resource_owner_secret = resource_owner_secret - suffix = "apisb" if dev else "api" - self.base_url = r"https://%s.etrade.com/v1/user/alerts" % suffix + self.base_url = f'https://{"apisb" if dev else "api"}.etrade.com/v1/user/alerts' self.session = OAuth1Session( self.client_key, self.client_secret, @@ -49,43 +47,59 @@ def __init__( signature_type="AUTH_HEADER", ) - def list_alerts(self, count: int = 25 <= 300, sort_order: str = "DESC", resp_format="xml") -> dict: + def list_alerts( + self, count: int = 25, sort_order: str = "DESC", resp_format: str = "xml" + ) -> dict: """:description: Lists alerts in Etrade - :param count: The alert count, defaults to 25 (max 300) - :type count: int, optional - :param sort_order: Sorting is done based on the createDate (ASC or DESC), defaults to DESC - :type sort_order: str, optional - :param resp_format: Desired Response format, defaults to xml - :type resp_format: str, optional - :return: List of alerts - :rtype: ``xml`` or ``json`` based on ``resp_format`` - :EtradeRef: https://apisb.etrade.com/docs/api/user/api-alert-v1.html - - """ - api_url = "%s%s" % (self.base_url, ".json" if resp_format == "json" else "") + :param count: The alert count, defaults to 25 (max 300) + :type count: int, optional + :param sort_order: Sorting is done based on the createDate (ASC or DESC), defaults to DESC + :type sort_order: str, optional + :param resp_format: Desired Response format, defaults to xml + :type resp_format: str, optional + :return: List of alerts + :rtype: ``xml`` or ``json`` based on ``resp_format`` + :EtradeRef: https://apisb.etrade.com/docs/api/user/api-alert-v1.html + """ + api_url = "%s%s" % ( + self.base_url, + ".json" if resp_format == "json" else "", + ) LOGGER.debug(api_url) - req = self.session.get(api_url, params={"count": count, "direction": sort_order}) + + if count >= 301: + LOGGER.debug( + f"Count {count} is greater than the max allowable value (300), using 300" + ) + count = 300 + + req = self.session.get( + api_url, params={"count": count, "direction": sort_order} + ) + req.raise_for_status() LOGGER.debug(req.text) return xmltodict.parse(req.text) if resp_format.lower() == "xml" else req.json() - def list_alert_details(self, alert_id: int, html_tags: bool = False, resp_format="xml") -> dict: + def list_alert_details( + self, alert_id: int, html_tags: bool = False, resp_format: str = "xml" + ) -> dict: """:description: Provides details for an alert - :param alert_id: Alert ID obtained from :class:`list_alerts` - :type alert_id: int, required - :param html_tags: The HTML tags on the alert, defaults to false. If set to true, it returns the alert details msgText with html tags. # noqa: E501 - :type html_tags: bool, optional - :param resp_format: Desired Response format, defaults to xml - :type resp_format: str, optional - :return: List of alert details - :rtype: xml or json based on ``resp_format`` - :EtradeRef: https://apisb.etrade.com/docs/api/user/api-alert-v1.html - - """ + :param alert_id: Alert ID obtained from :class:`list_alerts` + :type alert_id: int, required + :param html_tags: The HTML tags on the alert, defaults to false. If set to true, + it returns the alert details msgText with html tags. + :type html_tags: bool, optional + :param resp_format: Desired Response format, defaults to xml + :type resp_format: str, optional + :return: List of alert details + :rtype: xml or json based on ``resp_format`` + :EtradeRef: https://apisb.etrade.com/docs/api/user/api-alert-v1.html + """ api_url = "%s%s/%s" % ( self.base_url, @@ -94,23 +108,24 @@ def list_alert_details(self, alert_id: int, html_tags: bool = False, resp_format ) LOGGER.debug(api_url) + req = self.session.get(api_url, params={"htmlTags": html_tags}) req.raise_for_status() + LOGGER.debug(req.text) return xmltodict.parse(req.text) if resp_format.lower() == "xml" else req.json() - def delete_alert(self, alert_id: int, resp_format="xml") -> dict: + def delete_alert(self, alert_id: int, resp_format: str = "xml") -> dict: """:description: Deletes specified alert - :param alert_id: Alert ID obtained from :class:`list_alerts` - :type alert_id: int, required - :param resp_format: Desired Response format, defaults to xml - :type resp_format: str, optional - :return: List of alert details - :rtype: xml or json based on ``resp_format`` - :EtradeRef: https://apisb.etrade.com/docs/api/user/api-alert-v1.html - + :param alert_id: Alert ID obtained from :class:`list_alerts` + :type alert_id: int, required + :param resp_format: Desired Response format, defaults to xml + :type resp_format: str, optional + :return: List of alert details + :rtype: xml or json based on ``resp_format`` + :EtradeRef: https://apisb.etrade.com/docs/api/user/api-alert-v1.html """ api_url = "%s%s/%s" % ( @@ -120,8 +135,10 @@ def delete_alert(self, alert_id: int, resp_format="xml") -> dict: ) LOGGER.debug(api_url) + req = self.session.delete(api_url) req.raise_for_status() + LOGGER.debug(req.text) return xmltodict.parse(req.text) if resp_format.lower() == "xml" else req.json() diff --git a/pyetrade/authorization.py b/pyetrade/authorization.py index 3321982..e2e556c 100644 --- a/pyetrade/authorization.py +++ b/pyetrade/authorization.py @@ -4,8 +4,8 @@ * Catch events """ - import logging + from requests_oauthlib import OAuth1Session # Set up logging @@ -15,17 +15,18 @@ class ETradeOAuth(object): """:description: Performs authorization for OAuth 1.0a - :param consumer_key: Client key provided by Etrade - :type consumer_key: str, required - :param consumer_secret: Client secret provided by Etrade - :type consumer_secret: str, required - :param callback_url: Callback URL passed to OAuth mod, defaults to "oob" - :type callback_url: str, optional - :EtradeRef: https://apisb.etrade.com/docs/api/authorization/request_token.html - + :param consumer_key: Client key provided by Etrade + :type consumer_key: str, required + :param consumer_secret: Client secret provided by Etrade + :type consumer_secret: str, required + :param callback_url: Callback URL passed to OAuth mod, defaults to "oob" + :type callback_url: str, optional + :EtradeRef: https://apisb.etrade.com/docs/api/authorization/request_token.html """ - def __init__(self, consumer_key: str, consumer_secret: str, callback_url: str = "oob"): + def __init__( + self, consumer_key: str, consumer_secret: str, callback_url: str = "oob" + ): self.consumer_key = consumer_key self.consumer_secret = consumer_secret self.base_url_prod = r"https://api.etrade.com" @@ -40,11 +41,10 @@ def __init__(self, consumer_key: str, consumer_secret: str, callback_url: str = def get_request_token(self) -> str: """:description: Obtains the token URL from Etrade. - :param None: Takes no parameters - :return: Formatted Authorization URL (Access this to obtain taken) - :rtype: str - :EtradeRef: https://apisb.etrade.com/docs/api/authorization/request_token.html - + :param None: Takes no parameters + :return: Formatted Authorization URL (Access this to obtain taken) + :rtype: str + :EtradeRef: https://apisb.etrade.com/docs/api/authorization/request_token.html """ # Set up session @@ -74,12 +74,11 @@ def get_request_token(self) -> str: def get_access_token(self, verifier: str) -> dict: """:description: Obtains access token. Requires token URL from :class:`get_request_token` - :param verifier: OAuth Verification Code from Etrade - :type verifier: str, required - :return: OAuth access tokens - :rtype: dict - :EtradeRef: https://apisb.etrade.com/docs/api/authorization/get_access_token.html - + :param verifier: OAuth Verification Code from Etrade + :type verifier: str, required + :return: OAuth access tokens + :rtype: dict + :EtradeRef: https://apisb.etrade.com/docs/api/authorization/get_access_token.html """ # Set verifier @@ -94,25 +93,32 @@ def get_access_token(self, verifier: str) -> dict: class ETradeAccessManager(object): """:description: Renews and revokes ETrade OAuth access tokens - :param client_key: Client key provided by Etrade - :type client_key: str, required - :param client_secret: Client secret provided by Etrade - :type client_secret: str, required - :param resource_owner_key: Resource key from :class:`ETradeOAuth` - :type resource_owner_key: str, required - :param resource_owner_secret: Resource secret from :class:`ETradeOAuth` - :type resource_owner_secret: str, required - :EtradeRef: https://apisb.etrade.com/docs/api/authorization/renew_access_token.html - + :param client_key: Client key provided by Etrade + :type client_key: str, required + :param client_secret: Client secret provided by Etrade + :type client_secret: str, required + :param resource_owner_key: Resource key from :class:`ETradeOAuth` + :type resource_owner_key: str, required + :param resource_owner_secret: Resource secret from :class:`ETradeOAuth` + :type resource_owner_secret: str, required + :EtradeRef: https://apisb.etrade.com/docs/api/authorization/renew_access_token.html """ - def __init__(self, client_key: str, client_secret: str, resource_owner_key: str, resource_owner_secret: str): + def __init__( + self, + client_key: str, + client_secret: str, + resource_owner_key: str, + resource_owner_secret: str, + ): self.client_key = client_key self.client_secret = client_secret self.resource_owner_key = resource_owner_key self.resource_owner_secret = resource_owner_secret self.renew_access_token_url = r"https://api.etrade.com/oauth/renew_access_token" - self.revoke_access_token_url = r"https://api.etrade.com/oauth/revoke_access_token" + self.revoke_access_token_url = ( + r"https://api.etrade.com/oauth/revoke_access_token" + ) self.session = OAuth1Session( self.client_key, self.client_secret, @@ -124,29 +130,31 @@ def __init__(self, client_key: str, client_secret: str, resource_owner_key: str, def renew_access_token(self) -> bool: """:description: Renews access tokens obtained from :class:`ETradeOAuth` - :param None: Takes no parameters - :return: Success or failure - :rtype: bool (True or False) - :EtradeRef: https://apisb.etrade.com/docs/api/authorization/renew_access_token.html - + :param None: Takes no parameters + :return: Success or failure + :rtype: bool (True or False) + :EtradeRef: https://apisb.etrade.com/docs/api/authorization/renew_access_token.html """ + resp = self.session.get(self.renew_access_token_url) - LOGGER.debug(resp.text) resp.raise_for_status() + LOGGER.debug(resp.text) + return True def revoke_access_token(self) -> bool: """:description: Revokes access tokens obtained from :class:`ETradeOAuth` - :param None: Takes no parameters - :return: Success or failure - :rtype: bool (True or False) - :EtradeRef: https://apisb.etrade.com/docs/api/authorization/revoke_access_token.html - + :param None: Takes no parameters + :return: Success or failure + :rtype: bool (True or False) + :EtradeRef: https://apisb.etrade.com/docs/api/authorization/revoke_access_token.html """ + resp = self.session.get(self.revoke_access_token_url) - LOGGER.debug(resp.text) resp.raise_for_status() + LOGGER.debug(resp.text) + return True diff --git a/pyetrade/market.py b/pyetrade/market.py index 3c4882d..f908443 100644 --- a/pyetrade/market.py +++ b/pyetrade/market.py @@ -1,13 +1,13 @@ """Market - ETrade Market API V1 TODO: - * move logger into object under self.logger + * move logger into object under self.logger """ - import logging -import xmltodict from datetime import datetime + +import xmltodict from requests_oauthlib import OAuth1Session LOGGER = logging.getLogger(__name__) @@ -16,18 +16,18 @@ class ETradeMarket(object): """:description: Performs Market functions - :param client_key: Client key provided by Etrade - :type client_key: str, required - :param client_secret: Client secret provided by Etrade - :type client_secret: str, required - :param resource_owner_key: Resource key from :class:`pyetrade.authorization.ETradeOAuth` - :type resource_owner_key: str, required - :param resource_owner_secret: Resource secret from - :class:`pyetrade.authorization.ETradeOAuth` - :type resource_owner_secret: str, required - :param dev: Defines Sandboxi (True) or Live (False) ETrade, defaults to True - :type dev: bool, optional - :EtradeRef: https://apisb.etrade.com/docs/api/market/api-quote-v1.html + :param client_key: Client key provided by Etrade + :type client_key: str, required + :param client_secret: Client secret provided by Etrade + :type client_secret: str, required + :param resource_owner_key: Resource key from :class:`pyetrade.authorization.ETradeOAuth` + :type resource_owner_key: str, required + :param resource_owner_secret: Resource secret from + :class:`pyetrade.authorization.ETradeOAuth` + :type resource_owner_secret: str, required + :param dev: Defines Sandboxi (True) or Live (False) ETrade, defaults to True + :type dev: bool, optional + :EtradeRef: https://apisb.etrade.com/docs/api/market/api-quote-v1.html """ @@ -44,8 +44,7 @@ def __init__( self.resource_owner_key = resource_owner_key self.resource_owner_secret = resource_owner_secret self.dev_environment = dev - suffix = "apisb" if dev else "api" - self.base_url = r"https://%s.etrade.com/v1/market/" % suffix + self.base_url = f'https://{"apisb" if dev else "api"}.etrade.com/v1/market/' self.session = OAuth1Session( self.client_key, self.client_secret, @@ -57,78 +56,78 @@ def __init__( def __str__(self): ret = [ "Use development environment: %s" % self.dev_environment, - "base URL: %s" % self.base_url, + "Base URL: %s" % self.base_url, ] return "\n".join(ret) - def look_up_product(self, search_str: str, resp_format="xml") -> dict: + def look_up_product(self, search_str: str, resp_format: str = "xml") -> dict: """:description: Performs a look-up product - :param search_str: Full or partial name of the company. - :type search_str: str, required - :param resp_format: Desired Response format, defaults to xml - :type resp_format: str, optional - :return: Product lookup - :rtype: ``xml`` or ``json`` as defined by ``resp_format`` - :Note: Etrade abbreviates common words such as company, industry and systems - and generally skips punctuation. - :EtradeRef: - https://apisb.etrade.com/docs/api/market/api-market-v1.html#/definition/Lookup - + :param search_str: Full or partial name of the company. + :type search_str: str, required + :param resp_format: Desired Response format, defaults to xml + :type resp_format: str, optional + :return: Product lookup + :rtype: ``xml`` or ``json`` as defined by ``resp_format`` + :Note: Etrade abbreviates common words such as company, industry and systems + and generally skips punctuation. + :EtradeRef: https://apisb.etrade.com/docs/api/market/api-market-v1.html#/definition/Lookup """ # api_url = self.base_url + "lookup/%s" % search_str api_url = "%slookup/%s" % ( self.base_url, - search_str if resp_format.lower() == "xml" else search_str + ".json", + search_str if resp_format.lower() == "xml" else f"{search_str}.json", ) LOGGER.debug(api_url) + req = self.session.get(api_url) req.raise_for_status() + LOGGER.debug(req.text) + return xmltodict.parse(req.text) if resp_format.lower() == "xml" else req.json() def get_quote( self, - symbols: list, + symbols: list[str], detail_flag: str = None, require_earnings_date: str = None, skip_mini_options_check: str = None, - resp_format="xml", + resp_format: str = "xml", ) -> dict: """:description: Get quote data on symbols provided in the list args. - :param symbols: Symbols in list args format. Limit 25. - :type symbols: list[], required - :param detail_flag: Market fields returned from a quote request, defaults to None - :type detail_flag: str, optional - :param require_earnings_date: Provides Earnings date if True, defaults to None - :type require_earnings_date: str, optional - :param skip_mini_options_check: Skips mini options check if True, defaults to None - :type skip_mini_options_check: str, optional - :param resp_format: Desired Response format, defaults to xml - :type resp_format: str, optional - :return: Returns quote data on symbols provided - :rtype: xml or json based on ``resp_format`` - :symbols values: - * Limited to 25. If exceeded, first 25 will be processed with warnings - * Equities format - ``symbol`` name sufficient, e.g. GOOGL. - * Options format - ``underlier:year:month:day:optionType:strikePrice`` - :detailflag values: - * fundamental - Instrument fundamentals and latest price - * intraday - Performance for the current of most recent trading day - * options - Information on a given option offering - * week_52 - 52-week high and low (highest high and lowest low) - * mf_detail - MutualFund structure gets displayed - * all (default) - All of the above information and more - * None - Defaults to all. - :skipMiniOptionsCheck values: - * True - Call is NOT made to check whether the symbol has mini options - * False - Call is made to check whether the symbol has mini options - * None - Call is made to check whether the symbol has mini options (default) - :EtradeRef: https://apisb.etrade.com/docs/api/market/api-quote-v1.html - - """ + :param symbols: Symbols in list args format. Limit 25. + :type symbols: list[str], required + :param detail_flag: Market fields returned from a quote request, defaults to None + :type detail_flag: str, optional + :param require_earnings_date: Provides Earnings date if True, defaults to None + :type require_earnings_date: str, optional + :param skip_mini_options_check: Skips mini options check if True, defaults to None + :type skip_mini_options_check: str, optional + :param resp_format: Desired Response format, defaults to xml + :type resp_format: str, optional + :return: Returns quote data on symbols provided + :rtype: xml or json based on ``resp_format`` + :symbols values: + * Limited to 25. If exceeded, first 25 will be processed with warnings + * Equities format - ``symbol`` name sufficient, e.g. GOOGL. + * Options format - ``underlier:year:month:day:optionType:strikePrice`` + :detailflag values: + * fundamental - Instrument fundamentals and latest price + * intraday - Performance for the current of most recent trading day + * options - Information on a given option offering + * week_52 - 52-week high and low (highest high and lowest low) + * mf_detail - MutualFund structure gets displayed + * all (default) - All of the above information and more + * None - Defaults to all. + :skipMiniOptionsCheck values: + * True - Call is NOT made to check whether the symbol has mini options + * False - Call is made to check whether the symbol has mini options + * None - Call is made to check whether the symbol has mini options (default) + :EtradeRef: https://apisb.etrade.com/docs/api/market/api-quote-v1.html + """ if detail_flag is not None: detail_flag = detail_flag.lower() @@ -147,10 +146,13 @@ def get_quote( assert skip_mini_options_check in (True, False, None) assert isinstance(symbols, list or tuple) - if len(symbols) > 25: - LOGGER.warning("get_quote asked for %d requests; only first 25 returned" % len(symbols)) + if len(symbols) >= 26: + LOGGER.warning( + "get_quote asked for %d requests; only first 25 returned" % len(symbols) + ) args = list() + if detail_flag is not None: args.append("detailflag=%s" % detail_flag.upper()) if require_earnings_date: @@ -182,49 +184,48 @@ def get_option_chains( no_of_strikes: int = None, option_category: str = None, price_type: str = None, - resp_format="xml", + resp_format: str = "xml", ) -> dict: """:description: Returns the option chain information for the - requested expiry_date and chaintype in the desired format. - This should be a list of dictionaries, - one for each option chain. - - :param underlier: Market Symbol - :type underlier: str, required - :param expiry_date: Contract expiration date, None produces closest to today - :type expiry_date: datetime.date(year, month, day), optional - :param skip_adjusted: Specifies whether to show (True) or not show (False) adjusted - options, defaults to True - :type skip_adjusted: str, optional - :param chain_type: Type of option chain, defaults to callput - :type chain_type: str, optional - :param strike_price_near: Optionchains fetched will have strike price close to this value - :type strike_price_near: int, optional - :param no_of_strikes: Indicates number of strikes for which the optionchain - needs to be fetched, defaults to None - :type no_of_strikes: int, optional - :param option_category: The option category, defaults to ``standard`` - :type option_category: str, optional - :param price_type: The price type, defaults to ``atnm`` - :type price_type: str, optional - :param resp_format: Desired Response format, defaults to ``xml`` - :type resp_format: str, optional - :return: Returns list of option chains for a specific underlying instrument - :rtype: xml or json based on ``resp_format`` - :chain_type values: - * put - * call - * callput (default) - :option_category values: - * standard (default) - * all - * mini - :price_type values: - * atnm - * all - :sampleURL: https://api.etrade.com/v1/market/optionchains?expiryDay=03&expiryMonth=04&expiryYear=2011&chainType=PUT&skipAdjusted=true&symbol=GOOGL # noqa: E501 - :EtradeRef: https://apisb.etrade.com/docs/api/market/api-market-v1.html - + requested expiry_date and chain-type in the desired format. + This should be a list of dictionaries, + one for each option chain. + + :param underlier: Market Symbol + :type underlier: str, required + :param expiry_date: Contract expiration date, None produces closest to today + :type expiry_date: datetime.date(year, month, day), optional + :param skip_adjusted: Specifies whether to show (True) or not show (False) adjusted + options, defaults to True + :type skip_adjusted: str, optional + :param chain_type: Type of option chain, defaults to call/put + :type chain_type: str, optional + :param strike_price_near: Option chains fetched will have strike price close to this value + :type strike_price_near: int, optional + :param no_of_strikes: Indicates number of strikes for which the option chain + needs to be fetched, defaults to None + :type no_of_strikes: int, optional + :param option_category: The option category, defaults to ``standard`` + :type option_category: str, optional + :param price_type: The price type, defaults to ``atnm`` + :type price_type: str, optional + :param resp_format: Desired Response format, defaults to ``xml`` + :type resp_format: str, optional + :return: Returns list of option chains for a specific underlying instrument + :rtype: xml or json based on ``resp_format`` + :chain_type values: + * put + * call + * call/put (default) + :option_category values: + * standard (default) + * all + * mini + :price_type values: + * atnm + * all + :sampleURL: https://api.etrade.com/v1/market/optionchains?expiryDay=03&expiryMonth=04&expiryYear=2011&chainType=PUT&skipAdjusted=true&symbol=GOOGL # noqa: E501 + :EtradeRef: https://apisb.etrade.com/docs/api/market/api-market-v1.html """ if chain_type is not None: @@ -243,6 +244,7 @@ def get_option_chains( assert isinstance(resp_format, str) args = ["symbol=%s" % underlier] + if expiry_date is not None: args.append( "expiryDay=%02d&expiryMonth=%02d&expiryYear=%04d" @@ -262,31 +264,32 @@ def get_option_chains( args.append("noOfStrikes=%d" % no_of_strikes) api_url = "%s%s%s" % ( - self.base_url, "optionchains?" if resp_format.lower() == "xml" else "optionchains.json?", "&".join(args), + self.base_url, + "optionchains?" if resp_format.lower() == "xml" else "optionchains.json?", + "&".join(args), ) + LOGGER.debug(api_url) req = self.session.get(api_url) req.raise_for_status() - LOGGER.debug(api_url) + LOGGER.debug(req.text) return xmltodict.parse(req.text) if resp_format.lower() == "xml" else req.json() - def get_option_expire_date(self, symbol: str, resp_format="xml") -> dict: + def get_option_expire_date(self, symbol: str, resp_format: str = "xml") -> dict: """:description: Returns a list of dates suitable for structuring an option table display - :param symbol: Market Symbol - :type symbol: str, required - :param resp_format: Desired Response format, defaults to xml - :type resp_format: str, optional - :return: Returns expiry of options for symbol - :rtype: xml or json based on ``resp_format`` - :sampleURL: https://api.etrade.com/v1/market/optionexpiredate?symbol=GOOG&expiryType=ALL - :EtradeRef: https://apisb.etrade.com/docs/api/market/api-market-v1.html - + :param symbol: Market Symbol + :type symbol: str, required + :param resp_format: Desired Response format, defaults to xml + :type resp_format: str, optional + :return: Returns expiry of options for symbol + :rtype: xml or json based on ``resp_format`` + :sampleURL: https://api.etrade.com/v1/market/optionexpiredate?symbol=GOOG&expiryType=ALL + :EtradeRef: https://apisb.etrade.com/docs/api/market/api-market-v1.html """ - assert isinstance(resp_format, str) assert resp_format in ["xml", "json"] api_url = "%s%s" % ( @@ -296,11 +299,11 @@ def get_option_expire_date(self, symbol: str, resp_format="xml") -> dict: else "optionexpiredate.json", ) - payload = {"symbol": symbol, "expiryType": "ALL"} LOGGER.debug(api_url) - req = self.session.get(api_url, params=payload) + req = self.session.get(api_url, params={"symbol": symbol, "expiryType": "ALL"}) req.raise_for_status() + LOGGER.debug(req.text) return xmltodict.parse(req.text) if resp_format.lower() == "xml" else req.json() diff --git a/pyetrade/order.py b/pyetrade/order.py index 6506aba..5eb3c2f 100644 --- a/pyetrade/order.py +++ b/pyetrade/order.py @@ -1,20 +1,9 @@ -"""Order - ETrade Order API - - TODO: - * Preview equity order change - * Place equity order change - * Preview option order - * Place option order - * Preview option order change - * Place option order change - -""" - import logging -import xmltodict -import dateutil.parser - +from datetime import datetime from typing import Union + +import dateutil.parser +import xmltodict from jxmlease import emit_xml from requests_oauthlib import OAuth1Session @@ -25,16 +14,13 @@ PUT = "Put" -# price: number -# round_down: bool -# return string def to_decimal_str(price: float, round_down: bool) -> str: spstr = "%.2f" % price # round to 2-place decimal spstrf = float(spstr) # convert back to float again diff = price - spstrf if diff != 0: # have to work hard to round to decimal - HALF_CENT = 0.005 # e.g. BUY stop: round up to decimal + HALF_CENT = 0.005 # e.g. BUY stop: round up to decimal if round_down: HALF_CENT *= -1 # e.g. SELL stop: round down to decimal @@ -46,49 +32,50 @@ def to_decimal_str(price: float, round_down: bool) -> str: return spstr -# resp_format: xml (default) -# empty_json: either [] or {}, depends on the caller's semantics -def get_request_result(req: OAuth1Session.request, empty_json: dict, resp_format: str = "xml") -> dict: +def get_request_result(req: OAuth1Session.request, resp_format: str = "xml") -> dict: LOGGER.debug(req.text) - if resp_format == "json": - if req.text.strip() == "": - # otherwise, when ETrade server return empty string, we got this error: - # simplejson.errors.JSONDecodeError: Expecting value: line 1 column 1 (char 0) - req_output = empty_json # empty json object - else: - req_output = req.json() - else: + # Initialize as empty dict, otherwise, when ETrade server returns an empty string, you get this error: + # "simplejson.errors.JSONDecodeError: Expecting value: line 1 column 1 (char 0)" + req_output = {} + + assert resp_format in ["xml", "json"] + + if resp_format == "json" and req.text.strip() != "": + req_output = req.json() + elif resp_format == "xml": req_output = xmltodict.parse(req.text) - if 'Error' in req_output.keys(): - raise Exception(f'Etrade API Error - Code: {req_output["Error"]["code"]}, Msg: {req_output["Error"]["message"]}') # noqa: E501 - else: - return req_output + if "Error" in req_output.keys(): + raise Exception( + f'Etrade API Error - Code: {req_output["Error"]["code"]}, Msg: {req_output["Error"]["message"]}' + ) + + return req_output # return Etrade internal option symbol: e.g. "PLTR--220218P00023000" ref:_test_option_symbol() -def option_symbol(symbol: str, call_put: str, expiry_date: str, strike_price: float) -> str: +def option_symbol( + symbol: str, call_put: str, expiry_date: str, strike_price: float +) -> str: sym = symbol.strip().upper() symstr = sym + ("-" * (6 - len(sym))) ed = dateutil.parser.parse(expiry_date) # dateutil can handle most date formats edstr = ed.strftime("%y%m%d") - assert (len(edstr) == 6) + assert len(edstr) == 6 sp = "%08d" % (float(strike_price) * 1000) - assert (len(sp) == 8) + assert len(sp) == 8 opt_sym = symstr + edstr + call_put.strip().upper()[0] + sp - assert (len(opt_sym) == 21) + assert len(opt_sym) == 21 return opt_sym class OrderException(Exception): - """:description: Exception raised when giving bad args to a method not from Etrade calls - - """ + """:description: Exception raised when giving bad args to a method not from Etrade calls""" def __init__(self, explanation=None, params=None) -> None: super().__init__() @@ -102,38 +89,33 @@ def __str__(self) -> str: class ETradeOrder(object): """:description: Object to perform Orders - :param client_key: Client key provided by Etrade - :type client_key: str, required - :param client_secret: Client secret provided by Etrade - :type client_secret: str, required - :param resource_owner_key: Resource key from :class:`pyetrade.authorization.ETradeOAuth` - :type resource_owner_key: str, required - :param resource_owner_secret: Resource secret from - :class:`pyetrade.authorization.ETradeOAuth` - :type resource_owner_secret: str, required - :param dev: Defines Sandbox (True) or Live (False) ETrade, defaults to True - :type dev: bool, optional - :param timeout: Timeout value for OAuth, defaults to 30 - :type timeout: int, optional - :EtradeRef: https://apisb.etrade.com/docs/api/order/api-order-v1.html - + :param client_key: Client key provided by Etrade + :type client_key: str, required + :param client_secret: Client secret provided by Etrade + :type client_secret: str, required + :param resource_owner_key: Resource key from :class:`pyetrade.authorization.ETradeOAuth` + :type resource_owner_key: str, required + :param resource_owner_secret: Resource secret from + :class: `pyetrade.authorization.ETradeOAuth` + :type resource_owner_secret: str, required + :param dev: Defines Sandbox (True) or Live (False) ETrade, defaults to True + :type dev: bool, optional + :param timeout: Timeout value for OAuth, defaults to 30 + :type timeout: int, optional + :EtradeRef: https://apisb.etrade.com/docs/api/order/api-order-v1.html """ def __init__( self, - client_key, - client_secret, - resource_owner_key, - resource_owner_secret, - dev=True, - timeout=30, + client_key: str, + client_secret: str, + resource_owner_key: str, + resource_owner_secret: str, + dev: bool = True, + timeout: int = 30, ): - self.base_url = ( - r"https://apisb.etrade.com/v1/accounts" - if dev - else r"https://api.etrade.com/v1/accounts" - ) self.dev_environment = dev + self.base_url = f'https://{"apisb" if dev else "api"}.etrade.com/v1/accounts' self.timeout = timeout self.session = OAuth1Session( client_key, @@ -143,53 +125,165 @@ def __init__( signature_type="AUTH_HEADER", ) - def list_orders(self, account_id_key: str, resp_format: str = "json", **kwargs) -> dict: + def list_orders( + self, + account_id_key: str, + marker: str = None, + count: int = 25, + status: str = None, + from_date: datetime = None, + to_date: datetime = None, + symbols: list[str] = None, + security_type: str = None, + transaction_type: str = None, + market_session: str = "REGULAR", + resp_format: str = "json", + ) -> dict: """:description: Lists orders for a specific account ID Key - :param account_id_key: AccountIDKey from :class:`pyetrade.accounts.ETradeAccounts.list_accounts` - :type account_id_key: str, required - :param resp_format: Desired Response format, defaults to xml - :type resp_format: str, optional - :param kwargs: Parameters for api. Refer to EtradeRef for options - :type kwargs: ``**kwargs``, optional - :return: List of orders for an account - :rtype: ``xml`` or ``json`` based on ``resp_format`` - :EtradeRef: https://apisb.etrade.com/docs/api/order/api-order-v1.html + :param account_id_key: AccountIDKey from :class:`pyetrade.accounts.ETradeAccounts.list_accounts` + :type account_id_key: str, required + :param marker: Specifies the desired starting point of the set of items to return (defaults to None) + :type marker: str, optional + :param count: Number of transactions to return, defaults to 25 (max 100) + :type count: int, optional + :param status: Order status (defaults to None) + :type status: str, optional + :status values: + * OPEN + * EXECUTED + * CANCELLED + * INDIVIDUAL_FILLS + * CANCEL_REQUESTED + * EXPIRED + * REJECTED + :param from_date: The earliest date to include in the date range (history is available for two years). + Both fromDate and toDate should be used together, toDate should be greater than fromDate. + (defaults to None) + :type from_date: datetime obj, optional + :param to_date: The latest date to include in the date range (history is available for two years). + Both fromDate and toDate should be used together, toDate should be greater than fromDate. + (defaults to None) + :type to_date: datetime obj, optional + :param symbols: The market symbol(s) for the security being bought or sold. (defaults to None, Max 25 symbols) + :type symbols: list[str], optional + :param security_type: The security type (defaults to None - Returns all types) + :type security_type: str, optional + :security_type values: + * EQ + * OPTN + * MF + * MMF + :param transaction_type: Type of transaction (defaults to None - Returns all types) + :type transaction_type: str, optional + :transaction_type values: + * ATNM + * BUY + * SELL + * SELL_SHORT + * BUY_TO_COVER + * MF_EXCHANGE + :param market_session: The market session, defaults to REGULAR + :type market_session: str, optional + :market_session values: + * REGULAR + * EXTENDED + :param resp_format: Desired Response format, defaults to json + :type resp_format: str, optional + :return: List of orders for an account + :rtype: ``xml`` or ``json`` based on ``resp_format`` + :EtradeRef: https://apisb.etrade.com/docs/api/order/api-order-v1.html + """ - :return: List of orders in an account + if symbols and len(symbols) >= 26: + LOGGER.warning( + "list_orders asked for %d requests; only first 25 returned" + % len(symbols) + ) - """ + api_url = f"{self.base_url}/{account_id_key}/orders{'.json' if resp_format == 'json' else ''}" + LOGGER.debug(api_url) - api_url = f'{self.base_url}/{account_id_key}/orders' + if count >= 101: + LOGGER.debug( + f"Count {count} is greater than the max allowable value (100), using 100." + ) + count = 100 - if resp_format == "json": - api_url += ".json" + payload = { + "marker": marker, + "count": count, + "status": status, + "fromDate": from_date.date().strftime("%m%d%Y") if from_date else None, + "toDate": to_date.date().strftime("%m%d%Y") if to_date else None, + "symbol": ",".join([sym for sym in symbols[:25]]) if symbols else None, + "securityType": security_type, + "transactionType": transaction_type, + "marketSession": market_session, + } + + req = self.session.get(api_url, params=payload, timeout=self.timeout) + req.raise_for_status() + + LOGGER.debug(req.text) + + return get_request_result(req, resp_format) + + def list_order_details( + self, account_id_key: str, order_id: int, resp_format: str = "json" + ): + """ + :description: Lists order details of a specific account ID Key and order ID + + :param account_id_key: AccountIDKey from :class:`pyetrade.accounts.ETradeAccounts.list_accounts` + :type account_id_key: str, required + :param order_id: Order ID of placed order, order IDs can be retrieved from calling the list_orders() function + :type account_id_key: int, required + :param resp_format: Desired Response format, defaults to json + :type resp_format: str, optional + :return: List of orders for an account + :rtype: ``xml`` or ``json`` based on ``resp_format`` + :EtradeRef: https://apisb.etrade.com/docs/api/order/api-order-v1.html + """ + api_url = f"{self.base_url}/{account_id_key}/orders/{order_id}{'.json' if resp_format == 'json' else ''}" LOGGER.debug(api_url) - req = self.session.get(api_url, params=kwargs, timeout=self.timeout) - return get_request_result(req, {}, resp_format) + req = self.session.get(api_url) + req.raise_for_status() - def find_option_orders(self, account_id_key: str, symbol: str, call_put: str, expiry_date: str, strike_price: float) -> list: # noqa: E501 - """:description: Lists option orders for a specific account ID Key + LOGGER.debug(req.text) - :param account_id_key: AccountIDKey from :class:`pyetrade.accounts.ETradeAccounts.list_accounts` - :type account_id_key: str, required - :param symbol: ticker symbol for options chain - :type symbol: str, required - :param call_put: whether the option is a call or put - :type call_put: str, required - :param expiry_date: desired expiry of option (ex: 12-05-2021) - :type expiry_date: str, required - :param strike_price: strike price of desired option - :type strike_price: str, required + return xmltodict.parse(req.text) if resp_format.lower() == "xml" else req.json() - :return: List of matching option orders in an account + def find_option_orders( + self, + account_id_key: str, + symbol: str, + call_put: str, + expiry_date: str, + strike_price: float, + ) -> list: + """:description: Lists option orders for a specific account ID Key + :param account_id_key: AccountIDKey from :class:`pyetrade.accounts.ETradeAccounts.list_accounts` + :type account_id_key: str, required + :param symbol: ticker symbol for options chain + :type symbol: str, required + :param call_put: whether the option is a call or put + :type call_put: str, required + :param expiry_date: desired expiry of option (ex: 12-05-2021) + :type expiry_date: str, required + :param strike_price: strike price of desired option + :type strike_price: str, required + + :return: List of matching option orders in an account """ opt_sym = option_symbol(symbol, call_put, expiry_date, strike_price) - orders = self.list_orders(account_id_key, resp_format="json", status="OPEN") # this call may return empty + orders = self.list_orders( + account_id_key, resp_format="json", status="OPEN" + ) # this call may return empty results = [] @@ -198,7 +292,9 @@ def find_option_orders(self, account_id_key: str, symbol: str, call_put: str, ex product = o["OrderDetail"][0]["Instrument"][0]["Product"] if product["securityType"] == "OPTN": - symbol = product["productId"]["symbol"] # e.g. "PLTR--220218P00023000" + symbol = product["productId"][ + "symbol" + ] # e.g. "PLTR--220218P00023000" if symbol == opt_sym: results.append(o) @@ -208,7 +304,7 @@ def find_option_orders(self, account_id_key: str, symbol: str, call_put: str, ex def check_order(**kwargs): """:description: Check that required params for preview or place order are there and correct - (Used internally) + (Used internally) """ mandatory = [ @@ -230,39 +326,44 @@ def check_order(**kwargs): if kwargs["priceType"] == "LIMIT" and "limitPrice" not in kwargs: raise OrderException if ( - kwargs["priceType"] == "STOP_LIMIT" - and "limitPrice" not in kwargs - and "stopPrice" not in kwargs + kwargs["priceType"] == "STOP_LIMIT" + and "limitPrice" not in kwargs + and "stopPrice" not in kwargs ): raise OrderException - def build_order_payload(self, order_type: str, **kwargs) -> dict: + @staticmethod + def build_order_payload(order_type: str, **kwargs) -> dict: """:description: Builds the POST payload of a preview or place order - (Used internally) - - :param order_type: PreviewOrderRequest or PlaceOrderRequest - :type order_type: str, required - :securityType: EQ or OPTN - :orderAction: for OPTN: BUY_OPEN, SELL_CLOSE - :callPut: CALL or PUT - :expiryDate: string, e.g. "2022-02-18" - :return: Builds Order Payload - :rtype: ``xml`` or ``json`` based on ``resp_format`` - :EtradeRef: https://apisb.etrade.com/docs/api/order/api-order-v1.html + (Used internally) + + :param order_type: PreviewOrderRequest or PlaceOrderRequest + :type order_type: str, required + :securityType: EQ or OPTN + :orderAction: for OPTN: BUY_OPEN, SELL_CLOSE + :callPut: CALL or PUT + :expiryDate: string, e.g. "2022-02-18" + :return: Builds Order Payload + :rtype: ``xml`` or ``json`` based on ``resp_format`` + :EtradeRef: https://apisb.etrade.com/docs/api/order/api-order-v1.html """ securityType = kwargs.get("securityType", "EQ") # EQ by default product = {"securityType": securityType, "symbol": kwargs["symbol"]} if securityType == "OPTN": - expiryDate = dateutil.parser.parse(kwargs.pop("expiryDate")) # dateutil can handle most date formats - product.update({ - "expiryDay": expiryDate.day, - "expiryMonth": expiryDate.month, - "expiryYear": expiryDate.year, - "callPut": kwargs["callPut"], - "strikePrice": kwargs["strikePrice"] - }) + expiryDate = dateutil.parser.parse( + kwargs.pop("expiryDate") + ) # dateutil can handle most date formats + product.update( + { + "expiryDay": expiryDate.day, + "expiryMonth": expiryDate.month, + "expiryYear": expiryDate.year, + "callPut": kwargs["callPut"], + "strikePrice": kwargs["strikePrice"], + } + ) instrument = { "Product": product, @@ -283,7 +384,7 @@ def remove_invalid_price_from_kwargs(key: str) -> None: if "stopPrice" in kwargs: stopPrice = float(kwargs["stopPrice"]) - round_down = ("SELL" == kwargs["orderAction"][:4]) + round_down = "SELL" == kwargs["orderAction"][:4] spstr = to_decimal_str(stopPrice, round_down) order["stopPrice"] = spstr @@ -301,20 +402,22 @@ def remove_invalid_price_from_kwargs(key: str) -> None: return payload - def perform_request(self, method, api_url: str, payload: Union[dict, str], resp_format: str = "xml") -> dict: + def perform_request( + self, method, api_url: str, payload: Union[dict, str], resp_format: str = "xml" + ) -> dict: """:description: POST or PUT request with json or xml used by preview, place and cancel - :param method: PUT or POST method - :type method: session, required - :param resp_format: Desired Response format, defaults to xml - :type resp_format: str, required - :param api_url: API URL - :type api_url: str, required - :param payload: Payload - :type payload: json/dict or str xml, required - :return: Return request - :rtype: xml or json based on ``resp_format`` - :EtradeRef: https://apisb.etrade.com/docs/api/order/api-order-v1.html + :param method: PUT or POST method + :type method: session, required + :param resp_format: Desired Response format, defaults to xml + :type resp_format: str, required + :param api_url: API URL + :type api_url: str, required + :param payload: Payload + :type payload: json/dict or str xml, required + :return: Return request + :rtype: xml or json based on ``resp_format`` + :EtradeRef: https://apisb.etrade.com/docs/api/order/api-order-v1.html """ @@ -329,107 +432,107 @@ def perform_request(self, method, api_url: str, payload: Union[dict, str], resp_ LOGGER.debug("xml payload: %s", payload) req = method(api_url, data=payload, headers=headers, timeout=self.timeout) - return get_request_result(req, {}, resp_format) + return get_request_result(req, resp_format) def preview_equity_order(self, **kwargs) -> dict: """API is used to submit an order request for preview before placing it - :param accountIdKey: AccountIDkey retrieved from :class:`list_accounts` - :type accountIdKey: str, required - :param symbol: Market symbol for the security being bought or sold - :type symbol: str, required - :param orderAction: Action that the broker is requested to perform - :type orderAction: str, required - :orderAction values: - * BUY - * SELL - * BUY_TO_COVER - * SELL_SHORT - :param previewId: Required only if order was previewed. - Numeric preview ID from preview. - **Note** - Other parameters much match that of preview - :type previewId: long, conditional - :param clientOrderId: Reference number generated by developer. - Used to ensure duplicate order is not submitted. - Value can be of 20 alphanmeric characters or less - Must be uniquewithin this account. - Does not appear in any API responses. - :type clientOrderId: str, required - :param priceType: Type of pricing specified in equity order - :type priceType: str, required - :priceType values: - * MARKET - * LIMIT - Requires `limitPrice` - * STOP - Requires `stopPrice` - * STOP_LIMIT - Requires `limitPrice` - * MARKET_ON_CLOSE - :param limitPrice: Highest to buy or lowest to sell. - Required if `priceType` is `STOP` or `STOP_LIMIT` - :type limitPrice: double, conditional - :param stopPrice: Price to buy or sell if specified in a stop order. - Required if `priceType` is `STOP` or `STOP_LIMIT` - :type stopPrice: double, conditional - :param allOrNone: Specifies if order must be executed all at once. - TRUE triggers `allOrNone`, defaults to FALSE - :type allOrNone: bool, optional - :param quantity: Number of shares to buy or sell - :type quantity: int, required - :param reserveOrder: If set to TRUE, publicly displays only a limited - number of shares (the reserve quantity), instead - of the entire order, to avoid influencing other - traders. If TRUE, must also specify the - `reserveQuantity`, defaults to FALSE - :type reserveOrder: bool, optional - :param reserveQuantity: Number of shares to be publicly displayed if - this is a reserve order. Required if - `reserveOrder` is TRUE. - :type reserveQuantity: int, conditional - :param marketSession: Session to place the equity order - :type marketSession: str, required - :marketSession values: - * REGULAR - * EXTENDED - :param orderTerm: Term for which the order is in effect. - :type orderTerm: str, required - :orderTerm values: - * GOOD_UNTIL_CANCEL - * GOOD_FOR_DAY - * IMMEDIATE_OR_CANCEL (only for `LIMIT` orders) - * FILL_OR_KILL (only for `LIMIT` orders) - :param routingDestination: Exchange where the order should be executed. - :type routingDestination: str, optional - :routingDestination values: - * AUTO (default) - * ARCA - * NSDQ - * NYSE - :param estimatedCommission: Cost billed to the user to preform requested action - :type estimatedCommission: double - :param estimatedTotalAmount: Cost including commission - :type estimatedTotalAmount: double - :param messageList: Container for messages describing the result of the action - :type messageList: dict - :param msgDesc: Text of the result message, indicating order status, success - or failure, additional requirements that must be met before - placing the order, etc. Applications typically display this - message to the user, which may result in further user action - :type msgDesc: str - :param msgCode: Standard numeric code of the result message. Refer to - the Error Messages documentation for examples. May optionally - be displayed to the user, but is primarily intended for - internal use. - :type msgCode: int - :param orderNum: Numeric ID for this order in the E*TRADE system - :type orderNum: int - :param orderTime: The epoch time the order was submitted. - :type orderTime: long - :param symbolDesc: Text description of the security - :type symbolDesc: str - :param symbol: The market symbol for the underlier - :type symbol: str - :return: Confirmation of the Preview Equity Order - :rtype: ``xml`` or ``json`` based on ``resp_format`` - :EtradeRef: https://apisb.etrade.com/docs/api/order/api-order-v1.html + :param accountIdKey: AccountIDkey retrieved from :class:`list_accounts` + :type accountIdKey: str, required + :param symbol: Market symbol for the security being bought or sold + :type symbol: str, required + :param orderAction: Action that the broker is requested to perform + :type orderAction: str, required + :orderAction values: + * BUY + * SELL + * BUY_TO_COVER + * SELL_SHORT + :param previewId: Required only if order was previewed. + Numeric preview ID from preview. + **Note** - Other parameters much match that of preview + :type previewId: long, conditional + :param clientOrderId: Reference number generated by developer. + Used to ensure duplicate order is not submitted. + Value can be of 20 alphanmeric characters or less + Must be uniquewithin this account. + Does not appear in any API responses. + :type clientOrderId: str, required + :param priceType: Type of pricing specified in equity order + :type priceType: str, required + :priceType values: + * MARKET + * LIMIT - Requires `limitPrice` + * STOP - Requires `stopPrice` + * STOP_LIMIT - Requires `limitPrice` + * MARKET_ON_CLOSE + :param limitPrice: Highest to buy or lowest to sell. + Required if `priceType` is `STOP` or `STOP_LIMIT` + :type limitPrice: double, conditional + :param stopPrice: Price to buy or sell if specified in a stop order. + Required if `priceType` is `STOP` or `STOP_LIMIT` + :type stopPrice: double, conditional + :param allOrNone: Specifies if order must be executed all at once. + TRUE triggers `allOrNone`, defaults to FALSE + :type allOrNone: bool, optional + :param quantity: Number of shares to buy or sell + :type quantity: int, required + :param reserveOrder: If set to TRUE, publicly displays only a limited + number of shares (the reserve quantity), instead + of the entire order, to avoid influencing other + traders. If TRUE, must also specify the + `reserveQuantity`, defaults to FALSE + :type reserveOrder: bool, optional + :param reserveQuantity: Number of shares to be publicly displayed if + this is a reserve order. Required if + `reserveOrder` is TRUE. + :type reserveQuantity: int, conditional + :param marketSession: Session to place the equity order + :type marketSession: str, required + :marketSession values: + * REGULAR + * EXTENDED + :param orderTerm: Term for which the order is in effect. + :type orderTerm: str, required + :orderTerm values: + * GOOD_UNTIL_CANCEL + * GOOD_FOR_DAY + * IMMEDIATE_OR_CANCEL (only for `LIMIT` orders) + * FILL_OR_KILL (only for `LIMIT` orders) + :param routingDestination: Exchange where the order should be executed. + :type routingDestination: str, optional + :routingDestination values: + * AUTO (default) + * ARCA + * NSDQ + * NYSE + :param estimatedCommission: Cost billed to the user to preform requested action + :type estimatedCommission: double + :param estimatedTotalAmount: Cost including commission + :type estimatedTotalAmount: double + :param messageList: Container for messages describing the result of the action + :type messageList: dict + :param msgDesc: Text of the result message, indicating order status, success + or failure, additional requirements that must be met before + placing the order, etc. Applications typically display this + message to the user, which may result in further user action + :type msgDesc: str + :param msgCode: Standard numeric code of the result message. Refer to + the Error Messages documentation for examples. May optionally + be displayed to the user, but is primarily intended for + internal use. + :type msgCode: int + :param orderNum: Numeric ID for this order in the E*TRADE system + :type orderNum: int + :param orderTime: The epoch time the order was submitted. + :type orderTime: long + :param symbolDesc: Text description of the security + :type symbolDesc: str + :param symbol: The market symbol for the underlier + :type symbol: str + :return: Confirmation of the Preview Equity Order + :rtype: ``xml`` or ``json`` based on ``resp_format`` + :EtradeRef: https://apisb.etrade.com/docs/api/order/api-order-v1.html """ LOGGER.debug(kwargs) @@ -444,15 +547,17 @@ def preview_equity_order(self, **kwargs) -> dict: return self.perform_request(self.session.post, api_url, payload, "xml") - def change_preview_equity_order(self, account_id_key: str, order_id: str, **kwargs): + def change_preview_equity_order( + self, account_id_key: str, order_id: str, **kwargs + ) -> dict: """:description: Same as :class:`preview_equity_order` with orderId - :param order_id: order_id to modify, refer :class:`list_orders` - :type order_id: str, required - :param account_id_key: account_id_key retrieved from :class:`list_accounts` - :type account_id_key: str, required - :return: Previews Changed order with orderId for account with account_id_key - :rtype: dict/json - :EtradeRef: https://apisb.etrade.com/docs/api/order/api-order-v1.html + :param order_id: order_id to modify, refer :class:`list_orders` + :type order_id: str, required + :param account_id_key: account_id_key retrieved from :class:`list_accounts` + :type account_id_key: str, required + :return: Previews Changed order with orderId for account with account_id_key + :rtype: dict/json + :EtradeRef: https://apisb.etrade.com/docs/api/order/api-order-v1.html """ @@ -461,7 +566,7 @@ def change_preview_equity_order(self, account_id_key: str, order_id: str, **kwar # Test required values self.check_order(**kwargs) - api_url = f'{self.base_url}/{account_id_key}/orders/{order_id}/change/preview' + api_url = f"{self.base_url}/{account_id_key}/orders/{order_id}/change/preview" # payload creation payload = self.build_order_payload("PreviewOrderRequest", **kwargs) @@ -470,7 +575,7 @@ def change_preview_equity_order(self, account_id_key: str, order_id: str, **kwar def place_option_order(self, **kwargs) -> dict: """:description: Places Option Order, only single leg CALL or PUT is supported for now - :return: Returns confirmation of the equity order + :return: Returns confirmation of the equity order """ kwargs["securityType"] = "OPTN" @@ -479,11 +584,11 @@ def place_option_order(self, **kwargs) -> dict: def place_equity_order(self, **kwargs) -> dict: """:description: Places Equity Order - :param kwargs: Parameters for api, refer :class:`preview_equity_order` - :type kwargs: ``**kwargs``, required - :return: Returns confirmation of the equity order - :rtype: xml or json based on ``resp_format`` - :EtradeRef: https://apisb.etrade.com/docs/api/order/api-order-v1.html + :param kwargs: Parameters for api, refer :class:`preview_equity_order` + :type kwargs: ``**kwargs``, required + :return: Returns confirmation of the equity order + :rtype: xml or json based on ``resp_format`` + :EtradeRef: https://apisb.etrade.com/docs/api/order/api-order-v1.html """ LOGGER.debug(kwargs) @@ -496,10 +601,15 @@ def place_equity_order(self, **kwargs) -> dict: "No previewId given, previewing before placing order " "because of an Etrade bug as of 1/1/2019" ) + preview = self.preview_equity_order(**kwargs) - kwargs["previewId"] = preview["PreviewOrderResponse"]["PreviewIds"]["previewId"] + kwargs["previewId"] = preview["PreviewOrderResponse"]["PreviewIds"][ + "previewId" + ] - LOGGER.debug("Got a successful preview with previewId: %s", kwargs["previewId"]) + LOGGER.debug( + "Got a successful preview with previewId: %s", kwargs["previewId"] + ) api_url = f'{self.base_url}/{kwargs["accountIdKey"]}/orders/place' @@ -510,7 +620,7 @@ def place_equity_order(self, **kwargs) -> dict: def place_changed_option_order(self, **kwargs) -> dict: """:description: Places Option Order, only single leg CALL or PUT is supported for now - :return: Returns confirmation of the equity order + :return: Returns confirmation of the equity order """ kwargs["securityType"] = "OPTN" @@ -518,13 +628,13 @@ def place_changed_option_order(self, **kwargs) -> dict: def place_changed_equity_order(self, **kwargs) -> dict: """:description: Places changes to equity orders - NOTE: the ETrade server will actually cancel the old orderId, and create a new orderId + NOTE: the ETrade server will actually cancel the old orderId, and create a new orderId - :param kwargs: Parameters for api, refer :class:`change_preview_equity_order` - :type kwargs: ``**kwargs``, required - :return: Returns confirmation similar to :class:`preview_equity_order` - :rtype: xml or json based on ``resp_format`` - :EtradeRef: https://apisb.etrade.com/docs/api/order/api-order-v1.html + :param kwargs: Parameters for api, refer :class:`change_preview_equity_order` + :type kwargs: ``**kwargs``, required + :return: Returns confirmation similar to :class:`preview_equity_order` + :rtype: xml or json based on ``resp_format`` + :EtradeRef: https://apisb.etrade.com/docs/api/order/api-order-v1.html """ @@ -544,8 +654,12 @@ def place_changed_equity_order(self, **kwargs) -> dict: LOGGER.error(preview) raise Exception("Please check your order!") - kwargs["previewId"] = preview["PreviewOrderResponse"]["PreviewIds"]["previewId"] - LOGGER.debug("Got a successful preview with previewId: %s", kwargs["previewId"]) + kwargs["previewId"] = preview["PreviewOrderResponse"]["PreviewIds"][ + "previewId" + ] + LOGGER.debug( + "Got a successful preview with previewId: %s", kwargs["previewId"] + ) api_url = f'{self.base_url}/{kwargs["accountIdKey"]}/orders/{kwargs["orderId"]}/change/place' @@ -554,22 +668,24 @@ def place_changed_equity_order(self, **kwargs) -> dict: return self.perform_request(self.session.put, api_url, payload, "xml") - def cancel_order(self, account_id_key: str, order_num: int, resp_format: str = "xml") -> dict: + def cancel_order( + self, account_id_key: str, order_num: int, resp_format: str = "xml" + ) -> dict: """:description: Cancels a specific order for a given account - :param account_id_key: AccountIDkey retrieved from - :class:`pyetrade.accounts.ETradeAccounts.list_accounts` - :type account_id_key: str, required - :param order_num: Numeric id for this order listed in :class:`list_orders` - :type order_num: int, required - :param resp_format: Desired Response format, defaults to xml - :type resp_format: str, required - :return: Confirmation of cancellation - :rtype: ``dict/json`` - :EtradeRef: https://apisb.etrade.com/docs/api/order/api-order-v1.html + :param account_id_key: AccountIDkey retrieved from + :class:`pyetrade.accounts.ETradeAccounts.list_accounts` + :type account_id_key: str, required + :param order_num: Numeric id for this order listed in :class:`list_orders` + :type order_num: int, required + :param resp_format: Desired Response format, defaults to xml + :type resp_format: str, required + :return: Confirmation of cancellation + :rtype: ``dict/json`` + :EtradeRef: https://apisb.etrade.com/docs/api/order/api-order-v1.html """ - api_url = f'{self.base_url}/{account_id_key}/orders/cancel' + api_url = f"{self.base_url}/{account_id_key}/orders/cancel" payload = {"CancelOrderRequest": {"orderId": order_num}} return self.perform_request(self.session.put, api_url, payload, resp_format) diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..1f1e5d0 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,35 @@ +[tool.poetry] +name = "pyetrade" +version = "2.0.1" +description = "eTrade API wrapper" +authors = ["Jesse Cooper "] +license = "GPL-3.0" +readme = "README.md" + +[tool.poetry.dependencies] +python = "^3.9" +python-dateutil = "^2.9.0.post0" +requests = "^2.31.0" +requests-oauthlib = "^2.0.0" +xmltodict = "^0.13.0" +jxmlease = "^1.0.3" + +[tool.poetry.group.dev.dependencies] +black = "^24.4.0" +bumpversion = "0.5.3" +coverage = "^7.4.4" +flake8 = "^7.0.0" +pre-commit = "^3.7.0" +poetry = "^1.8.2" +poetry-plugin-export = "^1.7.1" +pylint = "^3.1.0" +pytest = "^8.1.1" +pytest-cov = "^5.0.0" +pytest-mock = "^3.14.0" +python-dateutil = "^2.9.0.post0" +Sphinx = "^7.3.6" +tox = "^4.14.2" + +[build-system] +requires = ["poetry-core"] +build-backend = "poetry.core.masonry.api" diff --git a/requirements.txt b/requirements.txt index 862c854..1b14759 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,5 +1,11 @@ -python-dateutil -requests -requests_oauthlib -xmltodict -jxmlease +certifi==2024.2.2 ; python_version >= "3.9" and python_version < "4.0" +charset-normalizer==3.3.2 ; python_version >= "3.9" and python_version < "4.0" +idna==3.7 ; python_version >= "3.9" and python_version < "4.0" +jxmlease==1.0.3 ; python_version >= "3.9" and python_version < "4.0" +oauthlib==3.2.2 ; python_version >= "3.9" and python_version < "4.0" +python-dateutil==2.9.0.post0 ; python_version >= "3.9" and python_version < "4.0" +requests-oauthlib==2.0.0 ; python_version >= "3.9" and python_version < "4.0" +requests==2.31.0 ; python_version >= "3.9" and python_version < "4.0" +six==1.16.0 ; python_version >= "3.9" and python_version < "4.0" +urllib3==2.2.1 ; python_version >= "3.9" and python_version < "4.0" +xmltodict==0.13.0 ; python_version >= "3.9" and python_version < "4.0" diff --git a/requirements_dev.txt b/requirements_dev.txt index 4c76d8f..1f74ac1 100644 --- a/requirements_dev.txt +++ b/requirements_dev.txt @@ -1,12 +1,97 @@ -pre-commit -black -pylint -flake8 -coverage -Sphinx -pytest -pytest-cov -pytest-mock -python-dateutil -tox -bumpversion==0.5.3 +alabaster==0.7.16 ; python_version >= "3.9" and python_version < "4.0" +astroid==3.1.0 ; python_version >= "3.9" and python_version < "4.0" +babel==2.14.0 ; python_version >= "3.9" and python_version < "4.0" +black==24.4.0 ; python_version >= "3.9" and python_version < "4.0" +build==1.2.1 ; python_version >= "3.9" and python_version < "4.0" +bumpversion==0.5.3 ; python_version >= "3.9" and python_version < "4.0" +cachecontrol[filecache]==0.14.0 ; python_version >= "3.9" and python_version < "4.0" +cachetools==5.3.3 ; python_version >= "3.9" and python_version < "4.0" +certifi==2024.2.2 ; python_version >= "3.9" and python_version < "4.0" +cffi==1.16.0 ; python_version >= "3.9" and python_version < "4.0" and (sys_platform == "darwin" or sys_platform == "linux") and (sys_platform == "darwin" or platform_python_implementation != "PyPy") +cfgv==3.4.0 ; python_version >= "3.9" and python_version < "4.0" +chardet==5.2.0 ; python_version >= "3.9" and python_version < "4.0" +charset-normalizer==3.3.2 ; python_version >= "3.9" and python_version < "4.0" +cleo==2.1.0 ; python_version >= "3.9" and python_version < "4.0" +click==8.1.7 ; python_version >= "3.9" and python_version < "4.0" +colorama==0.4.6 ; python_version >= "3.9" and python_version < "4.0" +coverage==7.4.4 ; python_version >= "3.9" and python_version < "4.0" +coverage[toml]==7.4.4 ; python_version >= "3.9" and python_version < "4.0" +crashtest==0.4.1 ; python_version >= "3.9" and python_version < "4.0" +cryptography==42.0.5 ; python_version >= "3.9" and python_version < "4.0" and sys_platform == "linux" +dill==0.3.8 ; python_version >= "3.9" and python_version < "4.0" +distlib==0.3.8 ; python_version >= "3.9" and python_version < "4.0" +docutils==0.21.1 ; python_version >= "3.9" and python_version < "4.0" +dulwich==0.21.7 ; python_version >= "3.9" and python_version < "4.0" +exceptiongroup==1.2.1 ; python_version >= "3.9" and python_version < "3.11" +fastjsonschema==2.19.1 ; python_version >= "3.9" and python_version < "4.0" +filelock==3.13.4 ; python_version >= "3.9" and python_version < "4.0" +flake8==7.0.0 ; python_version >= "3.9" and python_version < "4.0" +identify==2.5.35 ; python_version >= "3.9" and python_version < "4.0" +idna==3.7 ; python_version >= "3.9" and python_version < "4.0" +imagesize==1.4.1 ; python_version >= "3.9" and python_version < "4.0" +importlib-metadata==7.1.0 ; python_version >= "3.9" and python_version < "3.12" +iniconfig==2.0.0 ; python_version >= "3.9" and python_version < "4.0" +installer==0.7.0 ; python_version >= "3.9" and python_version < "4.0" +isort==5.13.2 ; python_version >= "3.9" and python_version < "4.0" +jaraco-classes==3.4.0 ; python_version >= "3.9" and python_version < "4.0" +jeepney==0.8.0 ; python_version >= "3.9" and python_version < "4.0" and sys_platform == "linux" +jinja2==3.1.3 ; python_version >= "3.9" and python_version < "4.0" +jxmlease==1.0.3 ; python_version >= "3.9" and python_version < "4.0" +keyring==24.3.1 ; python_version >= "3.9" and python_version < "4.0" +markupsafe==2.1.5 ; python_version >= "3.9" and python_version < "4.0" +mccabe==0.7.0 ; python_version >= "3.9" and python_version < "4.0" +more-itertools==10.2.0 ; python_version >= "3.9" and python_version < "4.0" +msgpack==1.0.8 ; python_version >= "3.9" and python_version < "4.0" +mypy-extensions==1.0.0 ; python_version >= "3.9" and python_version < "4.0" +nodeenv==1.8.0 ; python_version >= "3.9" and python_version < "4.0" +oauthlib==3.2.2 ; python_version >= "3.9" and python_version < "4.0" +packaging==24.0 ; python_version >= "3.9" and python_version < "4.0" +pathspec==0.12.1 ; python_version >= "3.9" and python_version < "4.0" +pexpect==4.9.0 ; python_version >= "3.9" and python_version < "4.0" +pkginfo==1.10.0 ; python_version >= "3.9" and python_version < "4.0" +platformdirs==4.2.0 ; python_version >= "3.9" and python_version < "4.0" +pluggy==1.4.0 ; python_version >= "3.9" and python_version < "4.0" +poetry-core==1.9.0 ; python_version >= "3.9" and python_version < "4.0" +poetry-plugin-export==1.7.1 ; python_version >= "3.9" and python_version < "4.0" +poetry==1.8.2 ; python_version >= "3.9" and python_version < "4.0" +pre-commit==3.7.0 ; python_version >= "3.9" and python_version < "4.0" +ptyprocess==0.7.0 ; python_version >= "3.9" and python_version < "4.0" +pycodestyle==2.11.1 ; python_version >= "3.9" and python_version < "4.0" +pycparser==2.22 ; python_version >= "3.9" and python_version < "4.0" and (sys_platform == "darwin" or sys_platform == "linux") and (sys_platform == "darwin" or platform_python_implementation != "PyPy") +pyflakes==3.2.0 ; python_version >= "3.9" and python_version < "4.0" +pygments==2.17.2 ; python_version >= "3.9" and python_version < "4.0" +pylint==3.1.0 ; python_version >= "3.9" and python_version < "4.0" +pyproject-api==1.6.1 ; python_version >= "3.9" and python_version < "4.0" +pyproject-hooks==1.0.0 ; python_version >= "3.9" and python_version < "4.0" +pytest-cov==5.0.0 ; python_version >= "3.9" and python_version < "4.0" +pytest-mock==3.14.0 ; python_version >= "3.9" and python_version < "4.0" +pytest==8.1.1 ; python_version >= "3.9" and python_version < "4.0" +python-dateutil==2.9.0.post0 ; python_version >= "3.9" and python_version < "4.0" +pywin32-ctypes==0.2.2 ; python_version >= "3.9" and python_version < "4.0" and sys_platform == "win32" +pyyaml==6.0.1 ; python_version >= "3.9" and python_version < "4.0" +rapidfuzz==3.8.1 ; python_version >= "3.9" and python_version < "4.0" +requests-oauthlib==2.0.0 ; python_version >= "3.9" and python_version < "4.0" +requests-toolbelt==1.0.0 ; python_version >= "3.9" and python_version < "4.0" +requests==2.31.0 ; python_version >= "3.9" and python_version < "4.0" +secretstorage==3.3.3 ; python_version >= "3.9" and python_version < "4.0" and sys_platform == "linux" +setuptools==69.5.1 ; python_version >= "3.9" and python_version < "4.0" +shellingham==1.5.4 ; python_version >= "3.9" and python_version < "4.0" +six==1.16.0 ; python_version >= "3.9" and python_version < "4.0" +snowballstemmer==2.2.0 ; python_version >= "3.9" and python_version < "4.0" +sphinx==7.3.6 ; python_version >= "3.9" and python_version < "4.0" +sphinxcontrib-applehelp==1.0.8 ; python_version >= "3.9" and python_version < "4.0" +sphinxcontrib-devhelp==1.0.6 ; python_version >= "3.9" and python_version < "4.0" +sphinxcontrib-htmlhelp==2.0.5 ; python_version >= "3.9" and python_version < "4.0" +sphinxcontrib-jsmath==1.0.1 ; python_version >= "3.9" and python_version < "4.0" +sphinxcontrib-qthelp==1.0.7 ; python_version >= "3.9" and python_version < "4.0" +sphinxcontrib-serializinghtml==1.1.10 ; python_version >= "3.9" and python_version < "4.0" +tomli==2.0.1 ; python_version >= "3.9" and python_full_version <= "3.11.0a6" +tomlkit==0.12.4 ; python_version >= "3.9" and python_version < "4.0" +tox==4.14.2 ; python_version >= "3.9" and python_version < "4.0" +trove-classifiers==2024.4.10 ; python_version >= "3.9" and python_version < "4.0" +typing-extensions==4.11.0 ; python_version >= "3.9" and python_version < "3.11" +urllib3==2.2.1 ; python_version >= "3.9" and python_version < "4.0" +virtualenv==20.25.3 ; python_version >= "3.9" and python_version < "4.0" +xattr==1.1.0 ; python_version >= "3.9" and python_version < "4.0" and sys_platform == "darwin" +xmltodict==0.13.0 ; python_version >= "3.9" and python_version < "4.0" +zipp==3.18.1 ; python_version >= "3.9" and python_version < "3.12" diff --git a/setup.py b/setup.py index 86956c5..a6148fb 100644 --- a/setup.py +++ b/setup.py @@ -1,9 +1,10 @@ #!/usr/bin/env python3 - -from setuptools import setup, find_packages from codecs import open from os import path +from setuptools import find_packages +from setuptools import setup + __version__ = "2.0.1" here = path.abspath(path.dirname(__file__)) @@ -39,10 +40,10 @@ "License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)", "Natural Language :: English", "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", "Topic :: Software Development :: Libraries", ], ) diff --git a/tests/test_accounts.py b/tests/test_accounts.py index 0a1cfd1..11eaf36 100644 --- a/tests/test_accounts.py +++ b/tests/test_accounts.py @@ -1,23 +1,24 @@ #!/usr/bin/env python3 """pyetrade authorization unit tests TODO: - * Test request error""" - + * Test request error +""" import unittest +from unittest.mock import MagicMock from unittest.mock import patch + from pyetrade import accounts class TestETradeAccounts(unittest.TestCase): """TestEtradeAccounts Unit Test""" - # Mock out OAuth1Session @patch("pyetrade.accounts.OAuth1Session") def test_list_accounts(self, MockOAuthSession): """test_list_accounts(MockOAuthSession) -> None - param: MockOAuthSession - type: mock.MagicMock - description: MagicMock object for OAuth1Sessions""" + param: MockOAuthSession + type: mock.MagicMock + description: MagicMock object for OAuth1Sessions""" # Set Mock returns MockOAuthSession().get().json.return_value = "{'account': 'abc123'}" MockOAuthSession().get().text = r" returns " @@ -51,13 +52,12 @@ def test_list_accounts(self, MockOAuthSession): self.assertTrue(isinstance(result, dict)) self.assertTrue(MockOAuthSession().get.called) - # Mock out OAuth1Session @patch("pyetrade.accounts.OAuth1Session") def test_get_account_balance(self, MockOAuthSession): """test_get_account_balance(MockOAuthSession) -> None - param: MockOAuthSession - type: mock.MagicMock - description: MagicMock object for OAuth1Sessions""" + param: MockOAuthSession + type: mock.MagicMock + description: MagicMock object for OAuth1Sessions""" # Set Mock returns MockOAuthSession().get().json.return_value = {"account": "abc123"} MockOAuthSession().get().text = r" returns " @@ -98,38 +98,44 @@ def test_get_account_balance(self, MockOAuthSession): self.assertTrue(MockOAuthSession().get.called) # Test API URL - result = account.get_account_balance("12345abcd", account_type="TRUST", resp_format="json") + result = account.get_account_balance( + "12345abcd", account_type="TRUST", resp_format="json" + ) self.assertTrue(isinstance(result, dict)) MockOAuthSession().get.assert_called_with( "https://api.etrade.com/v1/accounts/12345abcd/balance.json", - params={"realTimeNAV": True, "instType": "BROKERAGE", "accountType": "TRUST"}, + params={ + "realTimeNAV": True, + "instType": "BROKERAGE", + "accountType": "TRUST", + }, ) self.assertTrue(MockOAuthSession().get().json.called) self.assertTrue(MockOAuthSession().get.called) - # Mock out OAuth1Session @patch("pyetrade.accounts.OAuth1Session") def test_get_account_portfolio(self, MockOAuthSession): """test_get_account_positions(MockOAuthSession) -> None - param: MockOAuthSession - type: mock.MagicMock - description: MagicMock object for OAuth1Sessions""" + param: MockOAuthSession + type: mock.MagicMock + description: MagicMock object for OAuth1Sessions""" + # Set Mock returns MockOAuthSession().get().json.return_value = {"account": "abc123"} MockOAuthSession().get().text = r" returns " account = accounts.ETradeAccounts("abc123", "xyz123", "abctoken", "xyzsecret") default_params = { - 'count': 50, - 'sortBy': None, - 'sortOrder': 'DESC', - 'pageNumber': None, - 'marketSession': 'REGULAR', - 'totalsRequired': False, - 'lotsRequired': False, - 'view': 'QUICK' + "count": 50, + "sortBy": None, + "sortOrder": "DESC", + "pageNumber": None, + "marketSession": "REGULAR", + "totalsRequired": False, + "lotsRequired": False, + "view": "QUICK", } # Test Dev @@ -138,7 +144,8 @@ def test_get_account_portfolio(self, MockOAuthSession): # Test API URL MockOAuthSession().get.assert_called_with( - "https://apisb.etrade.com/v1/accounts/12345abcd/portfolio", params=default_params + "https://apisb.etrade.com/v1/accounts/12345abcd/portfolio", + params=default_params, ) result = account.get_account_portfolio("12345abcd", resp_format="json") self.assertTrue(isinstance(result, dict)) @@ -152,73 +159,161 @@ def test_get_account_portfolio(self, MockOAuthSession): # Test API URL MockOAuthSession().get.assert_called_with( - "https://api.etrade.com/v1/accounts/12345abcd/portfolio", params=default_params + "https://api.etrade.com/v1/accounts/12345abcd/portfolio", + params=default_params, ) self.assertTrue(MockOAuthSession().get().json.called) self.assertTrue(MockOAuthSession().get.called) result = account.get_account_portfolio("12345abcd", resp_format="xml") self.assertTrue(isinstance(result, dict)) - # Mock out OAuth1Session + @patch("pyetrade.accounts.OAuth1Session") + def test_get_portfolio_position_lot(self, MockOAuthSession): + """test_get_portfolio_position_lot(MockOAuthSession) -> None + param: MockOAuthSession + type: mock.MagicMock + description: MagicMock object for OAuth1Sessions""" + + # Set Mock returns + MockOAuthSession().get().json.return_value = { + "PositionLotsResponse": { + "shortType": 1, + "PositionLot": [ + { + "positionId": 297825015900, + "positionLotId": 1855385101103, + "price": 227.7, + "termCode": 1, + "daysGain": 5.0, + "daysGainPct": 1.4802, + "marketValue": 342.78, + "totalCost": 227.7, + "totalCostForGainPct": 50.5401, + "totalGain": 115.0799, + "lotSourceCode": 1, + "originalQty": 1, + "remainingQty": 1, + "availableQty": 0, + "orderNo": 18, + "legNo": 1, + "acquiredDate": 1674622900000, + "locationCode": 1, + "exchangeRate": 1.0, + "settlementCurrency": "USD", + "paymentCurrency": "USD", + "adjPrice": 0.0, + "commPerShare": 0.0, + "feesPerShare": 0.0, + "shortType": 1, + } + ], + } + } + + MockOAuthSession().get().text = r" returns " + + account = accounts.ETradeAccounts("abc123", "xyz123", "abctoken", "xyzsecret") + + account.get_account_portfolio = MagicMock( + return_value={ + "PortfolioResponse": { + "AccountPortfolio": [ + { + "Position": [ + {"positionId": "1", "Product": {"symbol": "AAPL"}} + ] + } + ] + } + } + ) + + result = account.get_portfolio_position_lot("AAPL", "account_id_key", "xml") + self.assertTrue(isinstance(result, dict)) + + # Check for when the symbol doesn't exist + with self.assertRaises(KeyError): + account.get_portfolio_position_lot("GOOG", "account_id_key", "xml") + @patch("pyetrade.accounts.OAuth1Session") def test_list_transactions(self, MockOAuthSession): """test_list_transactions(MockOAuthSession) -> None - param: MockOAuthSession - type: mock.MagicMock - description: MagicMock object for OAuth1Sessions""" - # Set Mock returns + param: MockOAuthSession + type: mock.MagicMock + description: MagicMock object for OAuth1Sessions + """ + MockOAuthSession().get().json.return_value = "{'transaction': 'abc123'}" MockOAuthSession().get().text = r" returns " account = accounts.ETradeAccounts("abc123", "xyz123", "abctoken", "xyzsecret") - default_params = {'startDate': None, 'endDate': None, 'sortOrder': 'DESC', 'marker': None, 'count': 50} + default_params = { + "startDate": None, + "endDate": None, + "sortOrder": "DESC", + "marker": None, + "count": 50, + } # Test Dev JSON self.assertEqual( - account.list_transactions('12345abcd', resp_format="json"), "{'transaction': 'abc123'}" - + account.list_transactions("12345abcd", resp_format="json"), + "{'transaction': 'abc123'}", ) # Test API URL MockOAuthSession().get.assert_called_with( - "https://apisb.etrade.com/v1/accounts/12345abcd/transactions.json", params=default_params, + "https://apisb.etrade.com/v1/accounts/12345abcd/transactions.json", + params=default_params, ) # Test Dev XML self.assertEqual( - dict(account.list_transactions('12345abcd', resp_format="xml")), {"xml": "returns"}, + dict(account.list_transactions("12345abcd", resp_format="xml")), + {"xml": "returns"}, ) # Test API URL MockOAuthSession().get.assert_called_with( - "https://apisb.etrade.com/v1/accounts/12345abcd/transactions", params=default_params, + "https://apisb.etrade.com/v1/accounts/12345abcd/transactions", + params=default_params, ) account = accounts.ETradeAccounts( "abc123", "xyz123", "abctoken", "xyzsecret", dev=False ) # Test Prod JSON self.assertEqual( - account.list_transactions('12345abcd', resp_format="json"), "{'transaction': 'abc123'}", + account.list_transactions("12345abcd", resp_format="json"), + "{'transaction': 'abc123'}", ) # Test API URL MockOAuthSession().get.assert_called_with( - "https://api.etrade.com/v1/accounts/12345abcd/transactions.json", params=default_params, + "https://api.etrade.com/v1/accounts/12345abcd/transactions.json", + params=default_params, ) # Test Prod XML self.assertEqual( - dict(account.list_transactions('12345abcd', resp_format="xml")), {"xml": "returns"}, + dict(account.list_transactions("12345abcd", resp_format="xml")), + {"xml": "returns"}, ) # Test API URL MockOAuthSession().get.assert_called_with( - "https://api.etrade.com/v1/accounts/12345abcd/transactions", params=default_params + "https://api.etrade.com/v1/accounts/12345abcd/transactions", + params=default_params, ) + + MockOAuthSession().get().text = "" + + # Test Dev JSON + self.assertEqual(account.list_transactions("12345abcd", resp_format="json"), {}) + self.assertTrue(MockOAuthSession().get().json.called) self.assertTrue(MockOAuthSession().get.called) - # Mock out OAuth1Session @patch("pyetrade.accounts.OAuth1Session") def test_list_transaction_details(self, MockOAuthSession): """test_get_transaction_details(MockOAuthSession) -> None - param: MockOAuthSession - type: mock.MagicMock - description: MagicMock object for OAuth1Sessions""" + param: MockOAuthSession + type: mock.MagicMock + description: MagicMock object for OAuth1Sessions + """ # Set Mock returns MockOAuthSession().get().json.return_value = "{'transaction': 'abc123'}" MockOAuthSession().get().text = r" returns " @@ -227,40 +322,48 @@ def test_list_transaction_details(self, MockOAuthSession): ) # Test Dev JSON self.assertEqual( - account.list_transaction_details('12345abcd', 67890, resp_format="json"), + account.list_transaction_details("12345abcd", 67890, resp_format="json"), "{'transaction': 'abc123'}", ) # Test API URL MockOAuthSession().get.assert_called_with( - "https://apisb.etrade.com/v1/accounts" "/12345abcd/transactions.json/67890", params={}, + "https://apisb.etrade.com/v1/accounts" "/12345abcd/transactions/67890.json", + params={"storeId": None}, ) # Test Dev XML self.assertEqual( - dict(account.list_transaction_details('12345abcd', 67890, resp_format="xml")), + dict( + account.list_transaction_details("12345abcd", 67890, resp_format="xml") + ), {"xml": "returns"}, ) MockOAuthSession().get.assert_called_with( - "https://apisb.etrade.com/v1/accounts/12345abcd/transactions/67890", params={}, + "https://apisb.etrade.com/v1/accounts/12345abcd/transactions/67890", + params={"storeId": None}, ) account = accounts.ETradeAccounts( "abc123", "xyz123", "abctoken", "xyzsecret", dev=False ) # Test Prod JSON self.assertEqual( - account.list_transaction_details('12345abcd', 67890, resp_format="json"), + account.list_transaction_details("12345abcd", 67890, resp_format="json"), "{'transaction': 'abc123'}", ) # Test API URL MockOAuthSession().get.assert_called_with( - "https://api.etrade.com/v1/accounts/12345abcd/transactions.json/67890", params={}, + "https://api.etrade.com/v1/accounts/12345abcd/transactions/67890.json", + params={"storeId": None}, ) # Test Prod XML self.assertEqual( - dict(account.list_transaction_details('12345abcd', 67890, resp_format="xml")), + dict( + account.list_transaction_details("12345abcd", 67890, resp_format="xml") + ), {"xml": "returns"}, ) MockOAuthSession().get.assert_called_with( - "https://api.etrade.com/v1/accounts/12345abcd/transactions/67890", params={}, + "https://api.etrade.com/v1/accounts/12345abcd/transactions/67890", + params={"storeId": None}, ) self.assertTrue(MockOAuthSession().get().json.called) diff --git a/tests/test_alerts.py b/tests/test_alerts.py index 8eb75c7..d29378a 100644 --- a/tests/test_alerts.py +++ b/tests/test_alerts.py @@ -4,36 +4,51 @@ # Mock out OAuth1Session def test_list_alerts(mocker): """test_list_alerts(MockOAuthSession) -> None - param: MockOAuthSession - type: mock.MagicMock - description: MagicMock object for OAuth1Sessions""" + param: MockOAuthSession + type: mock.MagicMock + description: MagicMock object for OAuth1Sessions""" MockOAuthSession = mocker.patch("pyetrade.alerts.OAuth1Session") # Set Mock returns MockOAuthSession().get().json.return_value = "{'alert': 'abc123'}" MockOAuthSession().get().text = r" returns " alert = alerts.ETradeAlerts("abc123", "xyz123", "abctoken", "xyzsecret", dev=True) - default_params = {'count': True, 'direction': 'DESC'} + default_params = {"count": 25, "direction": "DESC"} # Test Dev JSON assert alert.list_alerts(resp_format="json") == "{'alert': 'abc123'}" # Test API URL - MockOAuthSession().get.assert_called_with("https://apisb.etrade.com/v1/user/alerts.json", params=default_params) + MockOAuthSession().get.assert_called_with( + "https://apisb.etrade.com/v1/user/alerts.json", params=default_params + ) # Test Dev XML assert dict(alert.list_alerts(resp_format="xml")) == {"xml": "returns"} - MockOAuthSession().get.assert_called_with("https://apisb.etrade.com/v1/user/alerts", params=default_params) + MockOAuthSession().get.assert_called_with( + "https://apisb.etrade.com/v1/user/alerts", params=default_params + ) alert = alerts.ETradeAlerts("abc123", "xyz123", "abctoken", "xyzsecret", dev=False) # Test Prod JSON assert alert.list_alerts(resp_format="json") == "{'alert': 'abc123'}" # Test API URL - MockOAuthSession().get.assert_called_with("https://api.etrade.com/v1/user/alerts.json", params=default_params) + MockOAuthSession().get.assert_called_with( + "https://api.etrade.com/v1/user/alerts.json", params=default_params + ) # test Prod XML assert alert.list_alerts(resp_format="xml") == {"xml": "returns"} - MockOAuthSession().get.assert_called_with("https://api.etrade.com/v1/user/alerts", params=default_params) + MockOAuthSession().get.assert_called_with( + "https://api.etrade.com/v1/user/alerts", params=default_params + ) + + assert alert.list_alerts(count=301, resp_format="json") == "{'alert': 'abc123'}" + MockOAuthSession().get.assert_called_with( + "https://api.etrade.com/v1/user/alerts.json", + params={"count": 300, "direction": "DESC"}, + ) + assert MockOAuthSession().get().json.called assert MockOAuthSession().get.called @@ -41,25 +56,29 @@ def test_list_alerts(mocker): # Mock out OAuth1Session def test_list_alert_details(mocker): """test_list_alerts(MockOAuthSession) -> None - param: MockOAuthSession - type: mock.MagicMock - description: MagicMock object for OAuth1Sessions""" + param: MockOAuthSession + type: mock.MagicMock + description: MagicMock object for OAuth1Sessions""" MockOAuthSession = mocker.patch("pyetrade.alerts.OAuth1Session") # Set Mock returns MockOAuthSession().get().json.return_value = "{'alert': 'abc123'}" MockOAuthSession().get().text = r" returns " alert = alerts.ETradeAlerts("abc123", "xyz123", "abctoken", "xyzsecret", dev=True) - default_params = {'htmlTags': False} + default_params = {"htmlTags": False} # Test Dev JSON assert alert.list_alert_details(1234, resp_format="json") == "{'alert': 'abc123'}" # Test API URL - MockOAuthSession().get.assert_called_with("https://apisb.etrade.com/v1/user/alerts.json/1234", params=default_params) # noqa: E501 + MockOAuthSession().get.assert_called_with( + "https://apisb.etrade.com/v1/user/alerts.json/1234", params=default_params + ) # noqa: E501 # Test Dev XML assert dict(alert.list_alert_details(1234, resp_format="xml")) == {"xml": "returns"} - MockOAuthSession().get.assert_called_with("https://apisb.etrade.com/v1/user/alerts/1234", params=default_params) + MockOAuthSession().get.assert_called_with( + "https://apisb.etrade.com/v1/user/alerts/1234", params=default_params + ) assert dict(alert.list_alert_details(1234, resp_format="xml")) == {"xml": "returns"} alert = alerts.ETradeAlerts("abc123", "xyz123", "abctoken", "xyzsecret", dev=False) @@ -67,10 +86,14 @@ def test_list_alert_details(mocker): assert alert.list_alert_details(1234, resp_format="json") == "{'alert': 'abc123'}" # Test API URL - MockOAuthSession().get.assert_called_with("https://api.etrade.com/v1/user/alerts.json/1234", params=default_params) + MockOAuthSession().get.assert_called_with( + "https://api.etrade.com/v1/user/alerts.json/1234", params=default_params + ) assert dict(alert.list_alert_details(1234, resp_format="xml")) == {"xml": "returns"} - MockOAuthSession().get.assert_called_with("https://api.etrade.com/v1/user/alerts/1234", params=default_params) + MockOAuthSession().get.assert_called_with( + "https://api.etrade.com/v1/user/alerts/1234", params=default_params + ) assert MockOAuthSession().get().json.called assert MockOAuthSession().get.called @@ -78,9 +101,9 @@ def test_list_alert_details(mocker): # Mock out OAuth1Session def test_delete_alert(mocker): """test_list_alerts(MockOAuthSession) -> None - param: MockOAuthSession - type: mock.MagicMock - description: MagicMock object for OAuth1Sessions""" + param: MockOAuthSession + type: mock.MagicMock + description: MagicMock object for OAuth1Sessions""" MockOAuthSession = mocker.patch("pyetrade.alerts.OAuth1Session") # Set Mock returns MockOAuthSession().delete().json.return_value = "{'alert': 'abc123'}" diff --git a/tests/test_authorization.py b/tests/test_authorization.py index 30fd69f..411dd5f 100644 --- a/tests/test_authorization.py +++ b/tests/test_authorization.py @@ -3,9 +3,9 @@ TODO: * add more mock tests for revoke * add more mock tests for renew""" - import unittest from unittest.mock import patch + from pyetrade import authorization diff --git a/tests/test_market.py b/tests/test_market.py index 106a66a..0d92a27 100644 --- a/tests/test_market.py +++ b/tests/test_market.py @@ -6,6 +6,7 @@ import datetime as dt import unittest from unittest.mock import patch + from pyetrade import market @@ -13,16 +14,31 @@ class TestETradeMarket(unittest.TestCase): """TestEtradeAccounts Unit Test""" + def test__str__(self): + """test__str__() + type: mock.MagicMock + """ + + mark = market.ETradeMarket( + "abc123", "xyz123", "abctoken", "xyzsecret", dev=False + ) + + # Test __str__ response + self.assertEqual( + "Use development environment: False\nBase URL: https://api.etrade.com/v1/market/", + mark.__str__(), + ) + @patch("pyetrade.market.OAuth1Session") def test_look_up_product(self, MockOAuthSession): """test_look_up_product(MockOAuthSession) - param: MockOAuthSession - type: mock.MagicMock - description: MagicMock of OAuth1Session + param: MockOAuthSession + type: mock.MagicMock + description: MagicMock of OAuth1Session - 3 tests based on resp_format = (None,'xml') - test exception raised when resp_format is something - different from two choices + 3 tests based on resp_format = (None,'xml') + test exception raised when resp_format is something + different from two choices """ response = {"symbol": "MMM", "description": "3M CO COM", "type": "EQUITY"} @@ -34,7 +50,9 @@ def test_look_up_product(self, MockOAuthSession): """ # Set Mock returns for resp_format=xml MockOAuthSession().get().text = XML_response - mark = market.ETradeMarket("abc123", "xyz123", "abctoken", "xyzsecret", dev=False) + mark = market.ETradeMarket( + "abc123", "xyz123", "abctoken", "xyzsecret", dev=False + ) # Test Get Quote returning python dict resp = mark.look_up_product("mmm") assert isinstance(resp, dict) @@ -42,7 +60,9 @@ def test_look_up_product(self, MockOAuthSession): # Set Mock returns for resp_format=json MockOAuthSession().get().json.return_value = response - mark = market.ETradeMarket("abc123", "xyz123", "abctoken", "xyzsecret", dev=False) + mark = market.ETradeMarket( + "abc123", "xyz123", "abctoken", "xyzsecret", dev=False + ) # Test Get Quote returning python dict resp = mark.look_up_product("mmm", resp_format="json") assert isinstance(resp, dict) @@ -51,9 +71,9 @@ def test_look_up_product(self, MockOAuthSession): @patch("pyetrade.market.OAuth1Session") def test_get_quote(self, MockOAuthSession): """test_get_quote(MockOAuthSession) - param: MockOAuthSession - type: mock.MagicMock - description: MagicMock of OAuth1Session + param: MockOAuthSession + type: mock.MagicMock + description: MagicMock of OAuth1Session """ response = { @@ -83,7 +103,9 @@ def test_get_quote(self, MockOAuthSession): MockOAuthSession().get().text = XML_response MockOAuthSession().get().json.return_value = response - mark = market.ETradeMarket("abc123", "xyz123", "abctoken", "xyzsecret", dev=False) + mark = market.ETradeMarket( + "abc123", "xyz123", "abctoken", "xyzsecret", dev=False + ) # Test XML return resp = mark.get_quote(["MMM"]) @@ -96,7 +118,7 @@ def test_get_quote(self, MockOAuthSession): assert MockOAuthSession().get.called # Test list of symbols greater than 25 - resp = mark.get_quote(["MMM"]*26, resp_format="json") + resp = mark.get_quote(["MMM"] * 26, resp_format="json") assert isinstance(resp, dict) assert MockOAuthSession().get.called @@ -106,8 +128,13 @@ def test_get_quote(self, MockOAuthSession): assert MockOAuthSession().get.called # Test detail_flag, requireEarningsDate, skipMiniOptionsCheck - resp = mark.get_quote(["MMM"], detail_flag="ALL", require_earnings_date=True, - skip_mini_options_check=True, resp_format="json") + resp = mark.get_quote( + ["MMM"], + detail_flag="ALL", + require_earnings_date=True, + skip_mini_options_check=True, + resp_format="json", + ) assert isinstance(resp, dict) assert MockOAuthSession().get.called @@ -123,12 +150,16 @@ def test_get_quote(self, MockOAuthSession): @patch("pyetrade.market.OAuth1Session") def test_get_option_chains(self, MockOAuthSession): """test_get_optionexpiredate(MockOAuthSession) - param: MockOAuthSession - type: mock.MagicMock - description: MagicMock of OAuth1Session + param: MockOAuthSession + type: mock.MagicMock + description: MagicMock of OAuth1Session """ - response = {"timeStamp": 1546546266, "bid": 41.55, "OptionGreeks": {"iv": 0.6716}} + response = { + "timeStamp": 1546546266, + "bid": 41.55, + "OptionGreeks": {"iv": 0.6716}, + } XML_response = r""" 154654626641.55 @@ -138,31 +169,62 @@ def test_get_option_chains(self, MockOAuthSession): # Set Mock returns for resp_format=xml MockOAuthSession().get().text = XML_response - mark = market.ETradeMarket("abc123", "xyz123", "abctoken", "xyzsecret", dev=False) - resp = mark.get_option_chains("AAPL", expiry_date=dt.date(2019, 2, 15), resp_format="xml") + mark = market.ETradeMarket( + "abc123", "xyz123", "abctoken", "xyzsecret", dev=False + ) + resp = mark.get_option_chains( + "AAPL", expiry_date=dt.date(2019, 2, 15), resp_format="xml" + ) + assert isinstance(resp, dict) + assert MockOAuthSession().get.called + + # Set Mock returns for resp_format=xml and expiry_date=None + MockOAuthSession().get().text = XML_response + mark = market.ETradeMarket( + "abc123", "xyz123", "abctoken", "xyzsecret", dev=False + ) + resp = mark.get_option_chains("AAPL", expiry_date=None, resp_format="xml") assert isinstance(resp, dict) assert MockOAuthSession().get.called # Set Mock returns for resp_format=xml and dev=True MockOAuthSession().get().text = XML_response - mark = market.ETradeMarket("abc123", "xyz123", "abctoken", "xyzsecret", dev=True) - resp = mark.get_option_chains("AAPL", expiry_date=dt.date(2019, 2, 15), resp_format="xml") + mark = market.ETradeMarket( + "abc123", "xyz123", "abctoken", "xyzsecret", dev=True + ) + resp = mark.get_option_chains( + "AAPL", expiry_date=dt.date(2019, 2, 15), resp_format="xml" + ) assert isinstance(resp, dict) assert MockOAuthSession().get.called # Set Mock returns for resp_format=xml MockOAuthSession().get().json.return_value = response - mark = market.ETradeMarket("abc123", "xyz123", "abctoken", "xyzsecret", dev=True) - resp = mark.get_option_chains("AAPL", expiry_date=dt.date(2019, 2, 15), resp_format="xml") + mark = market.ETradeMarket( + "abc123", "xyz123", "abctoken", "xyzsecret", dev=True + ) + resp = mark.get_option_chains( + "AAPL", expiry_date=dt.date(2019, 2, 15), resp_format="xml" + ) assert isinstance(resp, dict) assert MockOAuthSession().get.called # Set Mock returns for resp_format=json MockOAuthSession().get().json.return_value = response - mark = market.ETradeMarket("abc123", "xyz123", "abctoken", "xyzsecret", dev=False) - resp = mark.get_option_chains("AAPL", expiry_date=dt.date(2019, 2, 15), - strike_price_near=100, chain_type="CALL", option_category="ALL", - price_type="ALL", skip_adjusted=False, no_of_strikes=5, resp_format="json") + mark = market.ETradeMarket( + "abc123", "xyz123", "abctoken", "xyzsecret", dev=False + ) + resp = mark.get_option_chains( + "AAPL", + expiry_date=dt.date(2019, 2, 15), + strike_price_near=100, + chain_type="CALL", + option_category="ALL", + price_type="ALL", + skip_adjusted=False, + no_of_strikes=5, + resp_format="json", + ) assert isinstance(resp, dict) assert MockOAuthSession().get.called @@ -178,9 +240,9 @@ def test_get_option_chains(self, MockOAuthSession): @patch("pyetrade.market.OAuth1Session") def test_get_option_expire_date(self, MockOAuthSession): """test_get_optionexpiredate(MockOAuthSession) - param: MockOAuthSession - type: mock.MagicMock - description: MagicMock of OAuth1Session + param: MockOAuthSession + type: mock.MagicMock + description: MagicMock of OAuth1Session """ # response = [dt.date(2019, 1, 18), dt.date(2019, 1, 25)] @@ -190,13 +252,17 @@ def test_get_option_expire_date(self, MockOAuthSession): ) # Set Mock returns for resp_format=None MockOAuthSession().get().text = XML_response - mark = market.ETradeMarket("abc123", "xyz123", "abctoken", "xyzsecret", dev=False) + mark = market.ETradeMarket( + "abc123", "xyz123", "abctoken", "xyzsecret", dev=False + ) resp = mark.get_option_expire_date("AAPL", resp_format="xml") assert isinstance(resp, dict) assert MockOAuthSession().get.called MockOAuthSession().get().text = XML_response - mark = market.ETradeMarket("abc123", "xyz123", "abctoken", "xyzsecret", dev=True) + mark = market.ETradeMarket( + "abc123", "xyz123", "abctoken", "xyzsecret", dev=True + ) resp = mark.get_option_expire_date("AAPL", resp_format="xml") assert isinstance(resp, dict) assert MockOAuthSession().get.called diff --git a/tests/test_order.py b/tests/test_order.py index 86e64bf..8222a45 100644 --- a/tests/test_order.py +++ b/tests/test_order.py @@ -4,9 +4,10 @@ * Test request error * Test API URL """ - import unittest +from unittest.mock import MagicMock from unittest.mock import patch + from pyetrade import order @@ -15,59 +16,123 @@ class TestETradeOrder(unittest.TestCase): def test_option_symbol(self): expected = "PLTR--220218P00023000" - self.assertEqual(expected, order.option_symbol("PLTR", order.PUT, "2022-02-18", 23)) - self.assertEqual(expected, order.option_symbol("PLTR", order.PUT, "2022-02-18", 23.00)) - self.assertEqual(expected, order.option_symbol("PLTR", order.PUT, "2022-02-18", "23.0")) + self.assertEqual( + expected, order.option_symbol("PLTR", order.PUT, "2022-02-18", 23) + ) + self.assertEqual( + expected, order.option_symbol("PLTR", order.PUT, "2022-02-18", 23.00) + ) + self.assertEqual( + expected, order.option_symbol("PLTR", order.PUT, "2022-02-18", "23.0") + ) @patch("pyetrade.order.OAuth1Session") def test_list_orders(self, MockOAuthSession): """test_place_equity_order(MockOAuthSession) -> None - param: MockOAuthSession - type: mock.MagicMock - description: MagicMock of OAuth1Session""" + param: MockOAuthSession + type: mock.MagicMock + description: MagicMock of OAuth1Session""" # Set Mock returns - MockOAuthSession().get().json.return_value = {'accountId': '12345'} + MockOAuthSession().get().json.return_value = {"accountId": "12345"} MockOAuthSession().get().text = r" returns " orders = order.ETradeOrder( "abc123", "xyz123", "abctoken", "xyzsecret", dev=False ) # Test Dev buy order equity - self.assertEqual(orders.list_orders("12345"), {'accountId': '12345'}) + self.assertEqual(orders.list_orders("12345"), {"accountId": "12345"}) self.assertTrue(MockOAuthSession().get().json.called) self.assertTrue(MockOAuthSession().get.called) # Test Prod buy order equity - self.assertEqual(orders.list_orders("12345"), {'accountId': '12345'}) + self.assertEqual(orders.list_orders("12345"), {"accountId": "12345"}) self.assertTrue(MockOAuthSession().get().json.called) self.assertTrue(MockOAuthSession().get.called) - self.assertTrue(isinstance(orders.list_orders("12345", resp_format="xml"), dict)) + self.assertTrue( + isinstance(orders.list_orders("12345", resp_format="xml"), dict) + ) + self.assertTrue(MockOAuthSession().get().json.called) + self.assertTrue(MockOAuthSession().get.called) + + @patch("pyetrade.order.OAuth1Session") + def test_list_order_details(self, MockOAuthSession): + MockOAuthSession().get().json.return_value = {"accountId": "12345"} + MockOAuthSession().get().text = r" returns " + + orders = order.ETradeOrder( + "abc123", "xyz123", "abctoken", "xyzsecret", dev=False + ) + + self.assertTrue( + isinstance(orders.list_order_details("12345", 123, "json"), dict) + ) self.assertTrue(MockOAuthSession().get().json.called) self.assertTrue(MockOAuthSession().get.called) + def test_find_option_orders(self): + orders = order.ETradeOrder( + "abc123", "xyz123", "abctoken", "xyzsecret", dev=False + ) + + orders.option_symbol = MagicMock(return_value="AAPL--220218C00065000") + + orders.list_orders = MagicMock( + return_value={ + "OrdersResponse": { + "Order": [ + { + "OrderDetail": [ + { + "Instrument": [ + { + "Product": { + "securityType": "OPTN", + "productId": { + "symbol": "AAPL--220218C00065000" + }, + } + } + ] + } + ] + } + ] + } + } + ) + + # Call the function being tested + result = orders.find_option_orders( + "34fsdf43f", "AAPL", "call", "02-08-2021", 65.0 + ) + + self.assertTrue(isinstance(result, list)) + # Mock out OAuth1Session @patch("pyetrade.order.OAuth1Session") def test_place_equity_order(self, MockOAuthSession): """test_place_equity_order(MockOAuthSession) -> None - param: MockOAuthSession - type: mock.MagicMock - description: MagicMock of OAuth1Session""" + param: MockOAuthSession + type: mock.MagicMock + description: MagicMock of OAuth1Session""" # Set Mock returns MockOAuthSession().post().text = r"321" # noqa: E501 - orders = order.ETradeOrder("abc123", "xyz123", "abctoken", "xyzsecret", dev=False) # noqa: E501 + orders = order.ETradeOrder( + "abc123", "xyz123", "abctoken", "xyzsecret", dev=False + ) result = orders.place_equity_order( - accountIdKey="12345", - symbol="ABC", - orderAction="BUY", - clientOrderId="1a2b3c", - priceType="MARKET", - quantity=100, - orderTerm="GOOD_UNTIL_CANCEL", - marketSession="REGULAR", - ) + accountIdKey="12345", + symbol="ABC", + orderAction="BUY", + clientOrderId="1a2b3c", + priceType="MARKET", + quantity=100, + orderTerm="GOOD_UNTIL_CANCEL", + marketSession="REGULAR", + ) # Test xml buy order equity self.assertTrue(isinstance(result, dict)) @@ -91,7 +156,7 @@ def test_place_equity_order(self, MockOAuthSession): self.assertTrue(MockOAuthSession().post.called) # Test json buy order equity - ret_val = {"PreviewOrderResponse": {"PreviewIds": {"previewId": '321'}}} + ret_val = {"PreviewOrderResponse": {"PreviewIds": {"previewId": "321"}}} MockOAuthSession().post().json.return_value = ret_val self.assertEqual( @@ -111,83 +176,113 @@ def test_place_equity_order(self, MockOAuthSession): self.assertTrue(MockOAuthSession().post.called) # Test payload: BUY MARKET - payload = orders.build_order_payload("PreviewOrderRequest", - resp_format="json", - accountId="12345", - symbol="ABC", - orderAction="BUY", - clientOrderId="1a2b3c", - priceType="MARKET", - quantity=100, - orderTerm="GOOD_UNTIL_CANCEL", - marketSession="REGULAR", - ) - # print(payload) # to debug - expected = {'PreviewOrderRequest': {'orderType': 'EQ', 'clientOrderId': '1a2b3c', - 'Order': {'resp_format': 'json', 'accountId': '12345', 'symbol': 'ABC', - 'orderAction': 'BUY', 'clientOrderId': '1a2b3c', - 'priceType': 'MARKET', 'quantity': 100, - 'orderTerm': 'GOOD_UNTIL_CANCEL', 'marketSession': 'REGULAR', - 'Instrument': {'Product': {'securityType': 'EQ', 'symbol': 'ABC'}, - 'orderAction': 'BUY', 'quantityType': 'QUANTITY', - 'quantity': 100}}}} + payload = orders.build_order_payload( + "PreviewOrderRequest", + resp_format="json", + accountId="12345", + symbol="ABC", + orderAction="BUY", + clientOrderId="1a2b3c", + priceType="MARKET", + quantity=100, + orderTerm="GOOD_UNTIL_CANCEL", + marketSession="REGULAR", + ) + + expected = { + "PreviewOrderRequest": { + "orderType": "EQ", + "clientOrderId": "1a2b3c", + "Order": { + "resp_format": "json", + "accountId": "12345", + "symbol": "ABC", + "orderAction": "BUY", + "clientOrderId": "1a2b3c", + "priceType": "MARKET", + "quantity": 100, + "orderTerm": "GOOD_UNTIL_CANCEL", + "marketSession": "REGULAR", + "Instrument": { + "Product": {"securityType": "EQ", "symbol": "ABC"}, + "orderAction": "BUY", + "quantityType": "QUANTITY", + "quantity": 100, + }, + }, + } + } self.assertTrue(expected == payload) # Test payload: SELL STOP float_decimals = [ - (19.99999, '19.99'), # double values are not exact; SELL: round down to decimal - (20, '20.00'), # exact int - (20.01001, '20.01'), - (20.01, '20.01'), - (20.00999, '20.00'), - (20.00001, '20.00'), + ( + 19.99999, + "19.99", + ), # double values are not exact; SELL: round down to decimal + (20, "20.00"), # exact int + (20.01001, "20.01"), + (20.01, "20.01"), + (20.00999, "20.00"), + (20.00001, "20.00"), ] for fd in float_decimals: for orderAction in ["SELL", "SELL_SHORT"]: - payload = orders.build_order_payload("PreviewOrderRequest", - accountIdKey="12345", - symbol="ABC", - orderAction=orderAction, - clientOrderId="1a2b3c", - priceType="STOP", - stopPrice=fd[0], - quantity=100, - orderTerm="GOOD_UNTIL_CANCEL", - marketSession="REGULAR", - ) - self.assertEqual(payload['PreviewOrderRequest']['Order']['stopPrice'], fd[1]) + payload = orders.build_order_payload( + "PreviewOrderRequest", + accountIdKey="12345", + symbol="ABC", + orderAction=orderAction, + clientOrderId="1a2b3c", + priceType="STOP", + stopPrice=fd[0], + quantity=100, + orderTerm="GOOD_UNTIL_CANCEL", + marketSession="REGULAR", + ) + + self.assertEqual( + payload["PreviewOrderRequest"]["Order"]["stopPrice"], fd[1] + ) # Test payload: BUY STOP float_decimals = [ - (19.99999, '20.00'), # double values are not exact; BUY: round up to decimal - (20, '20.00'), # exact int - (20.01001, '20.02'), - (20.01, '20.01'), - (20.00999, '20.01'), - (20.00001, '20.01'), + ( + 19.99999, + "20.00", + ), # double values are not exact; BUY: round up to decimal + (20, "20.00"), # exact int + (20.01001, "20.02"), + (20.01, "20.01"), + (20.00999, "20.01"), + (20.00001, "20.01"), ] + for fd in float_decimals: for orderAction in ["BUY", "BUY_TO_COVER"]: - payload = orders.build_order_payload("PreviewOrderRequest", - accountIdKey="12345", - symbol="ABC", - orderAction=orderAction, - clientOrderId="1a2b3c", - priceType="STOP", - stopPrice=fd[0], - quantity=100, - orderTerm="GOOD_UNTIL_CANCEL", - marketSession="REGULAR", - ) - self.assertEqual(payload['PreviewOrderRequest']['Order']['stopPrice'], fd[1]) + payload = orders.build_order_payload( + "PreviewOrderRequest", + accountIdKey="12345", + symbol="ABC", + orderAction=orderAction, + clientOrderId="1a2b3c", + priceType="STOP", + stopPrice=fd[0], + quantity=100, + orderTerm="GOOD_UNTIL_CANCEL", + marketSession="REGULAR", + ) - @patch("pyetrade.order.OAuth1Session") - def test_place_equity_order_exception(self, MockOAuthSession): + self.assertEqual( + payload["PreviewOrderRequest"]["Order"]["stopPrice"], fd[1] + ) + + def test_place_equity_order_exception(self): """test_place_equity_order_exception(MockOAuthSession) -> None - param: MockOAuthSession - type: mock.MagicMock - description: MagicMock of OAuth1Session""" + param: MockOAuthSession + type: mock.MagicMock + description: MagicMock of OAuth1Session""" orders = order.ETradeOrder( "abc123", "xyz123", "abctoken", "xyzsecret", dev=False ) @@ -236,26 +331,22 @@ def test_place_equity_order_exception(self, MockOAuthSession): orderTerm="GOOD_UNTIL_CANCEL", marketSession="REGULAR", ) - # Test Prod JSON - # self.assertEqual(orders.place_equity_order(), "{'account': 'abc123'}") - # Test Dev XML - # self.assertEqual(orders.place_equity_order(resp_format='xml'), r' returns ') - # self.assertTrue(MockOAuthSession().get().text.called) @patch("pyetrade.order.OAuth1Session") def test_cancel_order(self, MockOAuthSession): """test_cancel_order(MockOAuthSession) -> None - param: MockOAuthSession - type: mock.MagicMock - description: MagicMock of OAuth1Session""" - MockOAuthSession().put().json.return_value = {'accountIdKey': '12345'} + param: MockOAuthSession + type: mock.MagicMock + description: MagicMock of OAuth1Session""" + MockOAuthSession().put().json.return_value = {"accountIdKey": "12345"} MockOAuthSession().put().text = r" returns " orders = order.ETradeOrder( "abc123", "xyz123", "abctoken", "xyzsecret", dev=False ) # Prod self.assertEqual( - orders.cancel_order("12345", 42, resp_format="json"), {'accountIdKey': '12345'}, + orders.cancel_order("12345", 42, resp_format="json"), + {"accountIdKey": "12345"}, ) MockOAuthSession().put.assert_called_with( "https://api.etrade.com/v1/accounts" "/12345/orders/cancel", @@ -264,4 +355,6 @@ def test_cancel_order(self, MockOAuthSession): ) self.assertTrue(MockOAuthSession().put().json.called) self.assertTrue(MockOAuthSession().put.called) - self.assertTrue(isinstance(orders.cancel_order("12345", 42, resp_format="xml"), dict)) + self.assertTrue( + isinstance(orders.cancel_order("12345", 42, resp_format="xml"), dict) + ) diff --git a/tox.ini b/tox.ini index 0177e70..0c7c63a 100644 --- a/tox.ini +++ b/tox.ini @@ -6,8 +6,7 @@ max-line-length = 120 [testenv] deps = - -rrequirements_dev.txt - -rrequirements.txt + -r requirements_dev.txt + -r requirements.txt requires = pytest, pytest-cov -commands = pytest -s --cov-report=term-missing --cov-report=xml --cov=pyetrade - +commands = pytest --cov-config=.coveragerc --cov-branch --cov=pyetrade --cov-fail-under 90 --cov-report term-missing --cov-report xml tests/