├── .gitignore ├── LICENSE ├── README.md ├── llmfoo.webp ├── poetry.lock ├── pyproject.toml ├── src └── llmfoo │ ├── __init__.py │ ├── functions.py │ └── pdf2md.py └── tests ├── __init__.py ├── data └── pdfs │ ├── LoremIpsum.pdf │ ├── alianz.pdf │ ├── camelot-table.pdf │ ├── encrypted.pdf │ ├── foersom.pdf │ ├── generated.pdf │ ├── imagesandtext.pdf │ ├── instruction-manual-fisher-es-eas-easy-e-valves-cl125-through-cl600-en-124780.pdf │ ├── invoice.pdf │ ├── prospecting_scatter_chart.pdf │ ├── receipt.pdf │ ├── sparebin-receipt.pdf │ └── vero.pdf ├── test_bool.py ├── test_pdf2md.py └── test_tool.py /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | dist 3 | .DS_Store 4 | __pycache__ 5 | output 6 | .env 7 | test_tool.tool.json -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | --- 2 | title: MIT License 3 | spdx-id: MIT 4 | featured: true 5 | hidden: false 6 | 7 | description: A short and simple permissive license with conditions only requiring preservation of copyright and license notices. Licensed works, modifications, and larger works may be distributed under different terms and without source code. 8 | 9 | how: Create a text file (typically named LICENSE or LICENSE.txt) in the root of your source code and copy the text of the license into the file. Replace [year] with the current year and [fullname] with the name (or names) of the copyright holders. 10 | 11 | using: 12 | Babel: https://github.com/babel/babel/blob/master/LICENSE 13 | .NET: https://github.com/dotnet/runtime/blob/main/LICENSE.TXT 14 | Rails: https://github.com/rails/rails/blob/master/MIT-LICENSE 15 | 16 | permissions: 17 | - commercial-use 18 | - modifications 19 | - distribution 20 | - private-use 21 | 22 | conditions: 23 | - include-copyright 24 | 25 | limitations: 26 | - liability 27 | - warranty 28 | 29 | --- 30 | 31 | MIT License 32 | 33 | Copyright (c) 2023 Robocorp Technologies, Inc. 34 | 35 | Permission is hereby granted, free of charge, to any person obtaining a copy 36 | of this software and associated documentation files (the "Software"), to deal 37 | in the Software without restriction, including without limitation the rights 38 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 39 | copies of the Software, and to permit persons to whom the Software is 40 | furnished to do so, subject to the following conditions: 41 | 42 | The above copyright notice and this permission notice shall be included in all 43 | copies or substantial portions of the Software. 44 | 45 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 46 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 47 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 48 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 49 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 50 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 51 | SOFTWARE. 52 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # LLM FOO 2 | 3 | [![Version](https://img.shields.io/pypi/v/llmfoo.svg)](https://pypi.python.org/pypi/llmfoo) 4 | [![Downloads](http://pepy.tech/badge/llmfoo)](http://pepy.tech/project/llmfoo) 5 | 6 | ## Overview 7 | LLM FOO is a cutting-edge project blending the art of Kung Fu with the science of Large Language Models... or 8 | actually this is about automatically making the OpenAI tool JSON Schema, parsing call and constructing the 9 | result to the chat model. 10 | And then there is a second utility `is_statement_true` that uses [genius logit_bias trick](https://twitter.com/AAAzzam/status/1669753721574633473) 11 | that only uses one output token. 12 | 13 | But hey I hope this will become a set of small useful LLM helper functions that will make building stuff easier 14 | because current bleeding edge APIs are a bit of a mess and I think we can do better. 15 | 16 | ![](/llmfoo.webp) 17 | 18 | ## Installation 19 | ```bash 20 | pip install llmfoo 21 | ``` 22 | 23 | ## Usage 24 | 25 | * You need to have OPENAI_API_KEY in env and ability to call `gpt-4-1106-preview` model 26 | 27 | * `is_statement_true` should be easy to understand. 28 | Make some natural language statement, and check it against criteria or general truthfulness. You get back boolean. 29 | 30 | ### Converting PDF Documents to Markdown 31 | 32 | Newly introduced, `pdf2md` functionality allows for the conversion of PDF documents into Markdown format, making them easier to process and integrate with LLM-based systems. This feature is particularly useful for extracting text and tables from PDFs and transforming them into a more manageable format. 33 | 34 | #### Prerequisites 35 | 36 | - `pdftocairo` must be installed on your system for converting PDF pages to images. 37 | 38 | #### Example Usage 39 | 40 | ```python 41 | from llmfoo.pdf2md import process_pdf 42 | from pathlib import Path 43 | 44 | pdf_path = Path("path/to/your/document.pdf") 45 | output_dir = Path("path/to/output/directory") 46 | 47 | # Process the PDF and generate Markdown 48 | markdown_file = process_pdf(pdf_path, output_dir) 49 | ``` 50 | 51 | This function will process each page of the PDF, attempting to extract text, figures and tables, and convert them into a Markdown file in the specified output directory. 52 | 53 | 54 | ### For the LLM FOO tool: 55 | 56 | 1. Add `@tool` annotation. 57 | 2. llmfoo will generate the json schema to YOURFILE.tool.json with GPT-4-Turbo - "Never send a human to do a machine's job" .. like who wants to write boilerplate docs for Machines??? 58 | 3. Annotated functions have helpers: 59 | - `openai_schema` to return the schema (You can edit it from the json if your not happy with what the machines did) 60 | - `openai_tool_call` to make the tool call and return the result in chat API message format 61 | - `openai_tool_output` to make the tool call and return the result in assistant API tool output format 62 | 63 | ```python 64 | from time import sleep 65 | 66 | from openai import OpenAI 67 | 68 | from llmfoo.functions import tool 69 | from llmfoo import is_statement_true 70 | 71 | 72 | def test_is_statement_true_with_default_criteria(): 73 | assert is_statement_true("Earth is a planet.") 74 | assert not is_statement_true("1 + 2 = 5") 75 | 76 | 77 | def test_is_statement_true_with_own_criteria(): 78 | assert not is_statement_true("Temperature outside is -2 degrees celsius", 79 | criteria="Temperature above 0 degrees celsius") 80 | assert is_statement_true("1984 was written by George Orwell", 81 | criteria="George Orwell is the author of 1984") 82 | 83 | 84 | def test_is_statement_true_criteria_can_change_truth_value(): 85 | assert is_statement_true("Earth is 3rd planet from the Sun") 86 | assert not is_statement_true("Earth is 3rd planet from the Sun", 87 | criteria="Earth is stated to be 5th planet from the Sun") 88 | 89 | 90 | @tool 91 | def adder(x: int, y: int) -> int: 92 | return x + y 93 | 94 | 95 | @tool 96 | def multiplier(x: int, y: int) -> int: 97 | return x * y 98 | 99 | 100 | client = OpenAI() 101 | 102 | 103 | def test_chat_completion_with_adder(): 104 | number1 = 3267182746 105 | number2 = 798472847 106 | messages = [ 107 | { 108 | "role": "user", 109 | "content": f"What is {number1} + {number2}?" 110 | } 111 | ] 112 | response = client.chat.completions.create( 113 | model="gpt-4-1106-preview", 114 | messages=messages, 115 | tools=[adder.openai_schema] 116 | ) 117 | messages.append(response.choices[0].message) 118 | messages.append(adder.openai_tool_call(response.choices[0].message.tool_calls[0])) 119 | response2 = client.chat.completions.create( 120 | model="gpt-4-1106-preview", 121 | messages=messages, 122 | tools=[adder.openai_schema] 123 | ) 124 | assert str(adder(number1, number2)) in response2.choices[0].message.content.replace(",", "") 125 | 126 | 127 | def test_assistant_with_multiplier(): 128 | number1 = 1238763428176 129 | number2 = 172388743612 130 | assistant = client.beta.assistants.create( 131 | name="The Calc Machina", 132 | instructions="You are a calculator with a funny pirate accent.", 133 | tools=[multiplier.openai_schema], 134 | model="gpt-4-1106-preview" 135 | ) 136 | thread = client.beta.threads.create(messages=[ 137 | { 138 | "role":"user", 139 | "content":f"What is {number1} * {number2}?" 140 | } 141 | ]) 142 | run = client.beta.threads.runs.create( 143 | thread_id=thread.id, 144 | assistant_id=assistant.id 145 | ) 146 | while True: 147 | run_state = client.beta.threads.runs.retrieve( 148 | run_id=run.id, 149 | thread_id=thread.id, 150 | ) 151 | if run_state.status not in ['in_progress', 'requires_action']: 152 | break 153 | if run_state.status == 'requires_action': 154 | tool_call = run_state.required_action.submit_tool_outputs.tool_calls[0] 155 | run = client.beta.threads.runs.submit_tool_outputs( 156 | thread_id=thread.id, 157 | run_id=run.id, 158 | tool_outputs=[ 159 | multiplier.openai_tool_output(tool_call) 160 | ] 161 | ) 162 | sleep(1) 163 | sleep(0.1) 164 | messages = client.beta.threads.messages.list(thread_id=thread.id) 165 | assert str(multiplier(number1, number2)) in messages.data[0].content[0].text.value.replace(",", "") 166 | 167 | ``` 168 | 169 | ## Contributing 170 | Interested in contributing? Loved to get your help to make this project better! 171 | The APIs under are changing and system is still very much first version. 172 | 173 | ## License 174 | This project is licensed under the [MIT License](LICENSE). 175 | 176 | ## Acknowledgements 177 | - Thanks to all the contributors and maintainers. 178 | - Special thanks to the Kung Fu masters such as Bruce Lee who inspired this project. -------------------------------------------------------------------------------- /llmfoo.webp: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robocorp/llmfoo/6b981b9e8fbc1f1b9f8617381c589293efbee991/llmfoo.webp -------------------------------------------------------------------------------- /poetry.lock: -------------------------------------------------------------------------------- 1 | # This file is automatically @generated by Poetry 1.6.1 and should not be changed by hand. 2 | 3 | [[package]] 4 | name = "annotated-types" 5 | version = "0.6.0" 6 | description = "Reusable constraint types to use with typing.Annotated" 7 | optional = false 8 | python-versions = ">=3.8" 9 | files = [ 10 | {file = "annotated_types-0.6.0-py3-none-any.whl", hash = "sha256:0641064de18ba7a25dee8f96403ebc39113d0cb953a01429249d5c7564666a43"}, 11 | {file = "annotated_types-0.6.0.tar.gz", hash = "sha256:563339e807e53ffd9c267e99fc6d9ea23eb8443c08f112651963e24e22f84a5d"}, 12 | ] 13 | 14 | [[package]] 15 | name = "anyio" 16 | version = "4.3.0" 17 | description = "High level compatibility layer for multiple asynchronous event loop implementations" 18 | optional = false 19 | python-versions = ">=3.8" 20 | files = [ 21 | {file = "anyio-4.3.0-py3-none-any.whl", hash = "sha256:048e05d0f6caeed70d731f3db756d35dcc1f35747c8c403364a8332c630441b8"}, 22 | {file = "anyio-4.3.0.tar.gz", hash = "sha256:f75253795a87df48568485fd18cdd2a3fa5c4f7c5be8e5e36637733fce06fed6"}, 23 | ] 24 | 25 | [package.dependencies] 26 | exceptiongroup = {version = ">=1.0.2", markers = "python_version < \"3.11\""} 27 | idna = ">=2.8" 28 | sniffio = ">=1.1" 29 | typing-extensions = {version = ">=4.1", markers = "python_version < \"3.11\""} 30 | 31 | [package.extras] 32 | doc = ["Sphinx (>=7)", "packaging", "sphinx-autodoc-typehints (>=1.2.0)", "sphinx-rtd-theme"] 33 | test = ["anyio[trio]", "coverage[toml] (>=7)", "exceptiongroup (>=1.2.0)", "hypothesis (>=4.0)", "psutil (>=5.9)", "pytest (>=7.0)", "pytest-mock (>=3.6.1)", "trustme", "uvloop (>=0.17)"] 34 | trio = ["trio (>=0.23)"] 35 | 36 | [[package]] 37 | name = "camelot-py" 38 | version = "0.11.0" 39 | description = "PDF Table Extraction for Humans." 40 | optional = false 41 | python-versions = "*" 42 | files = [ 43 | {file = "camelot-py-0.11.0.tar.gz", hash = "sha256:97a7d906d685e4059a4a549a63ae3a51f0ab72a3c826557f8443c65a1181dfe6"}, 44 | {file = "camelot_py-0.11.0-py3-none-any.whl", hash = "sha256:96d0f0386c8993f8f6b0aaaddf5f14a4a6ec6e9d1e07b6128d1c3abfa9156683"}, 45 | ] 46 | 47 | [package.dependencies] 48 | chardet = ">=3.0.4" 49 | click = ">=6.7" 50 | numpy = ">=1.13.3" 51 | openpyxl = ">=2.5.8" 52 | pandas = ">=0.23.4" 53 | "pdfminer.six" = ">=20200726" 54 | pypdf = ">=3.0.0" 55 | tabulate = ">=0.8.9" 56 | 57 | [package.extras] 58 | all = ["ghostscript (>=0.7)", "matplotlib (>=2.2.3)", "opencv-python (>=3.4.2.17)", "pdftopng (>=0.2.3)"] 59 | base = ["ghostscript (>=0.7)", "opencv-python (>=3.4.2.17)", "pdftopng (>=0.2.3)"] 60 | cv = ["ghostscript (>=0.7)", "opencv-python (>=3.4.2.17)", "pdftopng (>=0.2.3)"] 61 | dev = ["Sphinx (>=3.1.2)", "codecov (>=2.0.15)", "ghostscript (>=0.7)", "matplotlib (>=2.2.3)", "opencv-python (>=3.4.2.17)", "pdftopng (>=0.2.3)", "pytest (>=5.4.3)", "pytest-cov (>=2.10.0)", "pytest-mpl (>=0.11)", "pytest-runner (>=5.2)", "sphinx-autobuild (>=2021.3.14)"] 62 | plot = ["matplotlib (>=2.2.3)"] 63 | 64 | [[package]] 65 | name = "certifi" 66 | version = "2024.2.2" 67 | description = "Python package for providing Mozilla's CA Bundle." 68 | optional = false 69 | python-versions = ">=3.6" 70 | files = [ 71 | {file = "certifi-2024.2.2-py3-none-any.whl", hash = "sha256:dc383c07b76109f368f6106eee2b593b04a011ea4d55f652c6ca24a754d1cdd1"}, 72 | {file = "certifi-2024.2.2.tar.gz", hash = "sha256:0569859f95fc761b18b45ef421b1290a0f65f147e92a1e5eb3e635f9a5e4e66f"}, 73 | ] 74 | 75 | [[package]] 76 | name = "cffi" 77 | version = "1.16.0" 78 | description = "Foreign Function Interface for Python calling C code." 79 | optional = false 80 | python-versions = ">=3.8" 81 | files = [ 82 | {file = "cffi-1.16.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:6b3d6606d369fc1da4fd8c357d026317fbb9c9b75d36dc16e90e84c26854b088"}, 83 | {file = "cffi-1.16.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:ac0f5edd2360eea2f1daa9e26a41db02dd4b0451b48f7c318e217ee092a213e9"}, 84 | {file = "cffi-1.16.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:7e61e3e4fa664a8588aa25c883eab612a188c725755afff6289454d6362b9673"}, 85 | {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a72e8961a86d19bdb45851d8f1f08b041ea37d2bd8d4fd19903bc3083d80c896"}, 86 | {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:5b50bf3f55561dac5438f8e70bfcdfd74543fd60df5fa5f62d94e5867deca684"}, 87 | {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:7651c50c8c5ef7bdb41108b7b8c5a83013bfaa8a935590c5d74627c047a583c7"}, 88 | {file = "cffi-1.16.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4108df7fe9b707191e55f33efbcb2d81928e10cea45527879a4749cbe472614"}, 89 | {file = "cffi-1.16.0-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:32c68ef735dbe5857c810328cb2481e24722a59a2003018885514d4c09af9743"}, 90 | {file = "cffi-1.16.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:673739cb539f8cdaa07d92d02efa93c9ccf87e345b9a0b556e3ecc666718468d"}, 91 | {file = "cffi-1.16.0-cp310-cp310-win32.whl", hash = "sha256:9f90389693731ff1f659e55c7d1640e2ec43ff725cc61b04b2f9c6d8d017df6a"}, 92 | {file = "cffi-1.16.0-cp310-cp310-win_amd64.whl", hash = "sha256:e6024675e67af929088fda399b2094574609396b1decb609c55fa58b028a32a1"}, 93 | {file = "cffi-1.16.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:b84834d0cf97e7d27dd5b7f3aca7b6e9263c56308ab9dc8aae9784abb774d404"}, 94 | {file = "cffi-1.16.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:1b8ebc27c014c59692bb2664c7d13ce7a6e9a629be20e54e7271fa696ff2b417"}, 95 | {file = "cffi-1.16.0-cp311-cp311-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:ee07e47c12890ef248766a6e55bd38ebfb2bb8edd4142d56db91b21ea68b7627"}, 96 | {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d8a9d3ebe49f084ad71f9269834ceccbf398253c9fac910c4fd7053ff1386936"}, 97 | {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e70f54f1796669ef691ca07d046cd81a29cb4deb1e5f942003f401c0c4a2695d"}, 98 | {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:5bf44d66cdf9e893637896c7faa22298baebcd18d1ddb6d2626a6e39793a1d56"}, 99 | {file = "cffi-1.16.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7b78010e7b97fef4bee1e896df8a4bbb6712b7f05b7ef630f9d1da00f6444d2e"}, 100 | {file = "cffi-1.16.0-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:c6a164aa47843fb1b01e941d385aab7215563bb8816d80ff3a363a9f8448a8dc"}, 101 | {file = "cffi-1.16.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:e09f3ff613345df5e8c3667da1d918f9149bd623cd9070c983c013792a9a62eb"}, 102 | {file = "cffi-1.16.0-cp311-cp311-win32.whl", hash = "sha256:2c56b361916f390cd758a57f2e16233eb4f64bcbeee88a4881ea90fca14dc6ab"}, 103 | {file = "cffi-1.16.0-cp311-cp311-win_amd64.whl", hash = "sha256:db8e577c19c0fda0beb7e0d4e09e0ba74b1e4c092e0e40bfa12fe05b6f6d75ba"}, 104 | {file = "cffi-1.16.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:fa3a0128b152627161ce47201262d3140edb5a5c3da88d73a1b790a959126956"}, 105 | {file = "cffi-1.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:68e7c44931cc171c54ccb702482e9fc723192e88d25a0e133edd7aff8fcd1f6e"}, 106 | {file = "cffi-1.16.0-cp312-cp312-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:abd808f9c129ba2beda4cfc53bde801e5bcf9d6e0f22f095e45327c038bfe68e"}, 107 | {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:88e2b3c14bdb32e440be531ade29d3c50a1a59cd4e51b1dd8b0865c54ea5d2e2"}, 108 | {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:fcc8eb6d5902bb1cf6dc4f187ee3ea80a1eba0a89aba40a5cb20a5087d961357"}, 109 | {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b7be2d771cdba2942e13215c4e340bfd76398e9227ad10402a8767ab1865d2e6"}, 110 | {file = "cffi-1.16.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e715596e683d2ce000574bae5d07bd522c781a822866c20495e52520564f0969"}, 111 | {file = "cffi-1.16.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:2d92b25dbf6cae33f65005baf472d2c245c050b1ce709cc4588cdcdd5495b520"}, 112 | {file = "cffi-1.16.0-cp312-cp312-win32.whl", hash = "sha256:b2ca4e77f9f47c55c194982e10f058db063937845bb2b7a86c84a6cfe0aefa8b"}, 113 | {file = "cffi-1.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:68678abf380b42ce21a5f2abde8efee05c114c2fdb2e9eef2efdb0257fba1235"}, 114 | {file = "cffi-1.16.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:0c9ef6ff37e974b73c25eecc13952c55bceed9112be2d9d938ded8e856138bcc"}, 115 | {file = "cffi-1.16.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a09582f178759ee8128d9270cd1344154fd473bb77d94ce0aeb2a93ebf0feaf0"}, 116 | {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e760191dd42581e023a68b758769e2da259b5d52e3103c6060ddc02c9edb8d7b"}, 117 | {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:80876338e19c951fdfed6198e70bc88f1c9758b94578d5a7c4c91a87af3cf31c"}, 118 | {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a6a14b17d7e17fa0d207ac08642c8820f84f25ce17a442fd15e27ea18d67c59b"}, 119 | {file = "cffi-1.16.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6602bc8dc6f3a9e02b6c22c4fc1e47aa50f8f8e6d3f78a5e16ac33ef5fefa324"}, 120 | {file = "cffi-1.16.0-cp38-cp38-win32.whl", hash = "sha256:131fd094d1065b19540c3d72594260f118b231090295d8c34e19a7bbcf2e860a"}, 121 | {file = "cffi-1.16.0-cp38-cp38-win_amd64.whl", hash = "sha256:31d13b0f99e0836b7ff893d37af07366ebc90b678b6664c955b54561fc36ef36"}, 122 | {file = "cffi-1.16.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:582215a0e9adbe0e379761260553ba11c58943e4bbe9c36430c4ca6ac74b15ed"}, 123 | {file = "cffi-1.16.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b29ebffcf550f9da55bec9e02ad430c992a87e5f512cd63388abb76f1036d8d2"}, 124 | {file = "cffi-1.16.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:dc9b18bf40cc75f66f40a7379f6a9513244fe33c0e8aa72e2d56b0196a7ef872"}, 125 | {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9cb4a35b3642fc5c005a6755a5d17c6c8b6bcb6981baf81cea8bfbc8903e8ba8"}, 126 | {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b86851a328eedc692acf81fb05444bdf1891747c25af7529e39ddafaf68a4f3f"}, 127 | {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:c0f31130ebc2d37cdd8e44605fb5fa7ad59049298b3f745c74fa74c62fbfcfc4"}, 128 | {file = "cffi-1.16.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8f8e709127c6c77446a8c0a8c8bf3c8ee706a06cd44b1e827c3e6a2ee6b8c098"}, 129 | {file = "cffi-1.16.0-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:748dcd1e3d3d7cd5443ef03ce8685043294ad6bd7c02a38d1bd367cfd968e000"}, 130 | {file = "cffi-1.16.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:8895613bcc094d4a1b2dbe179d88d7fb4a15cee43c052e8885783fac397d91fe"}, 131 | {file = "cffi-1.16.0-cp39-cp39-win32.whl", hash = "sha256:ed86a35631f7bfbb28e108dd96773b9d5a6ce4811cf6ea468bb6a359b256b1e4"}, 132 | {file = "cffi-1.16.0-cp39-cp39-win_amd64.whl", hash = "sha256:3686dffb02459559c74dd3d81748269ffb0eb027c39a6fc99502de37d501faa8"}, 133 | {file = "cffi-1.16.0.tar.gz", hash = "sha256:bcb3ef43e58665bbda2fb198698fcae6776483e0c4a631aa5647806c25e02cc0"}, 134 | ] 135 | 136 | [package.dependencies] 137 | pycparser = "*" 138 | 139 | [[package]] 140 | name = "chardet" 141 | version = "5.2.0" 142 | description = "Universal encoding detector for Python 3" 143 | optional = false 144 | python-versions = ">=3.7" 145 | files = [ 146 | {file = "chardet-5.2.0-py3-none-any.whl", hash = "sha256:e1cf59446890a00105fe7b7912492ea04b6e6f06d4b742b2c788469e34c82970"}, 147 | {file = "chardet-5.2.0.tar.gz", hash = "sha256:1b3b6ff479a8c414bc3fa2c0852995695c4a026dcd6d0633b2dd092ca39c1cf7"}, 148 | ] 149 | 150 | [[package]] 151 | name = "charset-normalizer" 152 | version = "3.3.2" 153 | description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." 154 | optional = false 155 | python-versions = ">=3.7.0" 156 | files = [ 157 | {file = "charset-normalizer-3.3.2.tar.gz", hash = "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5"}, 158 | {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3"}, 159 | {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027"}, 160 | {file = "charset_normalizer-3.3.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03"}, 161 | {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d"}, 162 | {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e"}, 163 | {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6"}, 164 | {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5"}, 165 | {file = "charset_normalizer-3.3.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537"}, 166 | {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c"}, 167 | {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12"}, 168 | {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_ppc64le.whl", hash = "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f"}, 169 | {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_s390x.whl", hash = "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269"}, 170 | {file = "charset_normalizer-3.3.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519"}, 171 | {file = "charset_normalizer-3.3.2-cp310-cp310-win32.whl", hash = "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73"}, 172 | {file = "charset_normalizer-3.3.2-cp310-cp310-win_amd64.whl", hash = "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09"}, 173 | {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_universal2.whl", hash = "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db"}, 174 | {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96"}, 175 | {file = "charset_normalizer-3.3.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e"}, 176 | {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f"}, 177 | {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574"}, 178 | {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4"}, 179 | {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8"}, 180 | {file = "charset_normalizer-3.3.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc"}, 181 | {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae"}, 182 | {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_i686.whl", hash = "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887"}, 183 | {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_ppc64le.whl", hash = "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae"}, 184 | {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_s390x.whl", hash = "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce"}, 185 | {file = "charset_normalizer-3.3.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f"}, 186 | {file = "charset_normalizer-3.3.2-cp311-cp311-win32.whl", hash = "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab"}, 187 | {file = "charset_normalizer-3.3.2-cp311-cp311-win_amd64.whl", hash = "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77"}, 188 | {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_universal2.whl", hash = "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8"}, 189 | {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b"}, 190 | {file = "charset_normalizer-3.3.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6"}, 191 | {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a"}, 192 | {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389"}, 193 | {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa"}, 194 | {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b"}, 195 | {file = "charset_normalizer-3.3.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed"}, 196 | {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26"}, 197 | {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_i686.whl", hash = "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d"}, 198 | {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_ppc64le.whl", hash = "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068"}, 199 | {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_s390x.whl", hash = "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143"}, 200 | {file = "charset_normalizer-3.3.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4"}, 201 | {file = "charset_normalizer-3.3.2-cp312-cp312-win32.whl", hash = "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7"}, 202 | {file = "charset_normalizer-3.3.2-cp312-cp312-win_amd64.whl", hash = "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001"}, 203 | {file = "charset_normalizer-3.3.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c"}, 204 | {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5"}, 205 | {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985"}, 206 | {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6"}, 207 | {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714"}, 208 | {file = "charset_normalizer-3.3.2-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786"}, 209 | {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5"}, 210 | {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c"}, 211 | {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_ppc64le.whl", hash = "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8"}, 212 | {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_s390x.whl", hash = "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711"}, 213 | {file = "charset_normalizer-3.3.2-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811"}, 214 | {file = "charset_normalizer-3.3.2-cp37-cp37m-win32.whl", hash = "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4"}, 215 | {file = "charset_normalizer-3.3.2-cp37-cp37m-win_amd64.whl", hash = "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99"}, 216 | {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a"}, 217 | {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac"}, 218 | {file = "charset_normalizer-3.3.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a"}, 219 | {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33"}, 220 | {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238"}, 221 | {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a"}, 222 | {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2"}, 223 | {file = "charset_normalizer-3.3.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8"}, 224 | {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898"}, 225 | {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99"}, 226 | {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_ppc64le.whl", hash = "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d"}, 227 | {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_s390x.whl", hash = "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04"}, 228 | {file = "charset_normalizer-3.3.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087"}, 229 | {file = "charset_normalizer-3.3.2-cp38-cp38-win32.whl", hash = "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25"}, 230 | {file = "charset_normalizer-3.3.2-cp38-cp38-win_amd64.whl", hash = "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b"}, 231 | {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4"}, 232 | {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d"}, 233 | {file = "charset_normalizer-3.3.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0"}, 234 | {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269"}, 235 | {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c"}, 236 | {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519"}, 237 | {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796"}, 238 | {file = "charset_normalizer-3.3.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185"}, 239 | {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c"}, 240 | {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458"}, 241 | {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_ppc64le.whl", hash = "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2"}, 242 | {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_s390x.whl", hash = "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8"}, 243 | {file = "charset_normalizer-3.3.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561"}, 244 | {file = "charset_normalizer-3.3.2-cp39-cp39-win32.whl", hash = "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f"}, 245 | {file = "charset_normalizer-3.3.2-cp39-cp39-win_amd64.whl", hash = "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d"}, 246 | {file = "charset_normalizer-3.3.2-py3-none-any.whl", hash = "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc"}, 247 | ] 248 | 249 | [[package]] 250 | name = "click" 251 | version = "8.1.7" 252 | description = "Composable command line interface toolkit" 253 | optional = false 254 | python-versions = ">=3.7" 255 | files = [ 256 | {file = "click-8.1.7-py3-none-any.whl", hash = "sha256:ae74fb96c20a0277a1d615f1e4d73c8414f5a98db8b799a7931d1582f3390c28"}, 257 | {file = "click-8.1.7.tar.gz", hash = "sha256:ca9853ad459e787e2192211578cc907e7594e294c7ccc834310722b41b9ca6de"}, 258 | ] 259 | 260 | [package.dependencies] 261 | colorama = {version = "*", markers = "platform_system == \"Windows\""} 262 | 263 | [[package]] 264 | name = "colorama" 265 | version = "0.4.6" 266 | description = "Cross-platform colored terminal text." 267 | optional = false 268 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,!=3.5.*,!=3.6.*,>=2.7" 269 | files = [ 270 | {file = "colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6"}, 271 | {file = "colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44"}, 272 | ] 273 | 274 | [[package]] 275 | name = "cryptography" 276 | version = "42.0.4" 277 | description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." 278 | optional = false 279 | python-versions = ">=3.7" 280 | files = [ 281 | {file = "cryptography-42.0.4-cp37-abi3-macosx_10_12_universal2.whl", hash = "sha256:ffc73996c4fca3d2b6c1c8c12bfd3ad00def8621da24f547626bf06441400449"}, 282 | {file = "cryptography-42.0.4-cp37-abi3-macosx_10_12_x86_64.whl", hash = "sha256:db4b65b02f59035037fde0998974d84244a64c3265bdef32a827ab9b63d61b18"}, 283 | {file = "cryptography-42.0.4-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:dad9c385ba8ee025bb0d856714f71d7840020fe176ae0229de618f14dae7a6e2"}, 284 | {file = "cryptography-42.0.4-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:69b22ab6506a3fe483d67d1ed878e1602bdd5912a134e6202c1ec672233241c1"}, 285 | {file = "cryptography-42.0.4-cp37-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:e09469a2cec88fb7b078e16d4adec594414397e8879a4341c6ace96013463d5b"}, 286 | {file = "cryptography-42.0.4-cp37-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:3e970a2119507d0b104f0a8e281521ad28fc26f2820687b3436b8c9a5fcf20d1"}, 287 | {file = "cryptography-42.0.4-cp37-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:e53dc41cda40b248ebc40b83b31516487f7db95ab8ceac1f042626bc43a2f992"}, 288 | {file = "cryptography-42.0.4-cp37-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:c3a5cbc620e1e17009f30dd34cb0d85c987afd21c41a74352d1719be33380885"}, 289 | {file = "cryptography-42.0.4-cp37-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:6bfadd884e7280df24d26f2186e4e07556a05d37393b0f220a840b083dc6a824"}, 290 | {file = "cryptography-42.0.4-cp37-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:01911714117642a3f1792c7f376db572aadadbafcd8d75bb527166009c9f1d1b"}, 291 | {file = "cryptography-42.0.4-cp37-abi3-win32.whl", hash = "sha256:fb0cef872d8193e487fc6bdb08559c3aa41b659a7d9be48b2e10747f47863925"}, 292 | {file = "cryptography-42.0.4-cp37-abi3-win_amd64.whl", hash = "sha256:c1f25b252d2c87088abc8bbc4f1ecbf7c919e05508a7e8628e6875c40bc70923"}, 293 | {file = "cryptography-42.0.4-cp39-abi3-macosx_10_12_universal2.whl", hash = "sha256:15a1fb843c48b4a604663fa30af60818cd28f895572386e5f9b8a665874c26e7"}, 294 | {file = "cryptography-42.0.4-cp39-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a1327f280c824ff7885bdeef8578f74690e9079267c1c8bd7dc5cc5aa065ae52"}, 295 | {file = "cryptography-42.0.4-cp39-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:6ffb03d419edcab93b4b19c22ee80c007fb2d708429cecebf1dd3258956a563a"}, 296 | {file = "cryptography-42.0.4-cp39-abi3-manylinux_2_28_aarch64.whl", hash = "sha256:1df6fcbf60560d2113b5ed90f072dc0b108d64750d4cbd46a21ec882c7aefce9"}, 297 | {file = "cryptography-42.0.4-cp39-abi3-manylinux_2_28_x86_64.whl", hash = "sha256:44a64043f743485925d3bcac548d05df0f9bb445c5fcca6681889c7c3ab12764"}, 298 | {file = "cryptography-42.0.4-cp39-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:3c6048f217533d89f2f8f4f0fe3044bf0b2090453b7b73d0b77db47b80af8dff"}, 299 | {file = "cryptography-42.0.4-cp39-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:6d0fbe73728c44ca3a241eff9aefe6496ab2656d6e7a4ea2459865f2e8613257"}, 300 | {file = "cryptography-42.0.4-cp39-abi3-musllinux_1_2_aarch64.whl", hash = "sha256:887623fe0d70f48ab3f5e4dbf234986b1329a64c066d719432d0698522749929"}, 301 | {file = "cryptography-42.0.4-cp39-abi3-musllinux_1_2_x86_64.whl", hash = "sha256:ce8613beaffc7c14f091497346ef117c1798c202b01153a8cc7b8e2ebaaf41c0"}, 302 | {file = "cryptography-42.0.4-cp39-abi3-win32.whl", hash = "sha256:810bcf151caefc03e51a3d61e53335cd5c7316c0a105cc695f0959f2c638b129"}, 303 | {file = "cryptography-42.0.4-cp39-abi3-win_amd64.whl", hash = "sha256:a0298bdc6e98ca21382afe914c642620370ce0470a01e1bef6dd9b5354c36854"}, 304 | {file = "cryptography-42.0.4-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5f8907fcf57392cd917892ae83708761c6ff3c37a8e835d7246ff0ad251d9298"}, 305 | {file = "cryptography-42.0.4-pp310-pypy310_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:12d341bd42cdb7d4937b0cabbdf2a94f949413ac4504904d0cdbdce4a22cbf88"}, 306 | {file = "cryptography-42.0.4-pp310-pypy310_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:1cdcdbd117681c88d717437ada72bdd5be9de117f96e3f4d50dab3f59fd9ab20"}, 307 | {file = "cryptography-42.0.4-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:0e89f7b84f421c56e7ff69f11c441ebda73b8a8e6488d322ef71746224c20fce"}, 308 | {file = "cryptography-42.0.4-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:f1e85a178384bf19e36779d91ff35c7617c885da487d689b05c1366f9933ad74"}, 309 | {file = "cryptography-42.0.4-pp39-pypy39_pp73-manylinux_2_28_aarch64.whl", hash = "sha256:d2a27aca5597c8a71abbe10209184e1a8e91c1fd470b5070a2ea60cafec35bcd"}, 310 | {file = "cryptography-42.0.4-pp39-pypy39_pp73-manylinux_2_28_x86_64.whl", hash = "sha256:4e36685cb634af55e0677d435d425043967ac2f3790ec652b2b88ad03b85c27b"}, 311 | {file = "cryptography-42.0.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:f47be41843200f7faec0683ad751e5ef11b9a56a220d57f300376cd8aba81660"}, 312 | {file = "cryptography-42.0.4.tar.gz", hash = "sha256:831a4b37accef30cccd34fcb916a5d7b5be3cbbe27268a02832c3e450aea39cb"}, 313 | ] 314 | 315 | [package.dependencies] 316 | cffi = {version = ">=1.12", markers = "platform_python_implementation != \"PyPy\""} 317 | 318 | [package.extras] 319 | docs = ["sphinx (>=5.3.0)", "sphinx-rtd-theme (>=1.1.1)"] 320 | docstest = ["pyenchant (>=1.6.11)", "readme-renderer", "sphinxcontrib-spelling (>=4.0.1)"] 321 | nox = ["nox"] 322 | pep8test = ["check-sdist", "click", "mypy", "ruff"] 323 | sdist = ["build"] 324 | ssh = ["bcrypt (>=3.1.5)"] 325 | test = ["certifi", "pretend", "pytest (>=6.2.0)", "pytest-benchmark", "pytest-cov", "pytest-xdist"] 326 | test-randomorder = ["pytest-randomly"] 327 | 328 | [[package]] 329 | name = "distro" 330 | version = "1.9.0" 331 | description = "Distro - an OS platform information API" 332 | optional = false 333 | python-versions = ">=3.6" 334 | files = [ 335 | {file = "distro-1.9.0-py3-none-any.whl", hash = "sha256:7bffd925d65168f85027d8da9af6bddab658135b840670a223589bc0c8ef02b2"}, 336 | {file = "distro-1.9.0.tar.gz", hash = "sha256:2fa77c6fd8940f116ee1d6b94a2f90b13b5ea8d019b98bc8bafdcabcdd9bdbed"}, 337 | ] 338 | 339 | [[package]] 340 | name = "et-xmlfile" 341 | version = "1.1.0" 342 | description = "An implementation of lxml.xmlfile for the standard library" 343 | optional = false 344 | python-versions = ">=3.6" 345 | files = [ 346 | {file = "et_xmlfile-1.1.0-py3-none-any.whl", hash = "sha256:a2ba85d1d6a74ef63837eed693bcb89c3f752169b0e3e7ae5b16ca5e1b3deada"}, 347 | {file = "et_xmlfile-1.1.0.tar.gz", hash = "sha256:8eb9e2bc2f8c97e37a2dc85a09ecdcdec9d8a396530a6d5a33b30b9a92da0c5c"}, 348 | ] 349 | 350 | [[package]] 351 | name = "exceptiongroup" 352 | version = "1.2.0" 353 | description = "Backport of PEP 654 (exception groups)" 354 | optional = false 355 | python-versions = ">=3.7" 356 | files = [ 357 | {file = "exceptiongroup-1.2.0-py3-none-any.whl", hash = "sha256:4bfd3996ac73b41e9b9628b04e079f193850720ea5945fc96a08633c66912f14"}, 358 | {file = "exceptiongroup-1.2.0.tar.gz", hash = "sha256:91f5c769735f051a4290d52edd0858999b57e5876e9f85937691bd4c9fa3ed68"}, 359 | ] 360 | 361 | [package.extras] 362 | test = ["pytest (>=6)"] 363 | 364 | [[package]] 365 | name = "ghostscript" 366 | version = "0.7" 367 | description = "Interface to the Ghostscript C-API, both high- and low-level, based on ctypes" 368 | optional = false 369 | python-versions = "*" 370 | files = [ 371 | {file = "ghostscript-0.7-py2.py3-none-any.whl", hash = "sha256:97c70e27ba6b1cab4ab1d9b4cc82d89b8b53e57971f608ded4950b8aa20c78a7"}, 372 | {file = "ghostscript-0.7.tar.gz", hash = "sha256:b7875a87098740eb0be3de2d9662d15db727305ca9a6d4b7534a3cc33a4b965a"}, 373 | ] 374 | 375 | [package.dependencies] 376 | setuptools = ">=38.6.0" 377 | 378 | [[package]] 379 | name = "h11" 380 | version = "0.14.0" 381 | description = "A pure-Python, bring-your-own-I/O implementation of HTTP/1.1" 382 | optional = false 383 | python-versions = ">=3.7" 384 | files = [ 385 | {file = "h11-0.14.0-py3-none-any.whl", hash = "sha256:e3fe4ac4b851c468cc8363d500db52c2ead036020723024a109d37346efaa761"}, 386 | {file = "h11-0.14.0.tar.gz", hash = "sha256:8f19fbbe99e72420ff35c00b27a34cb9937e902a8b810e2c88300c6f0a3b699d"}, 387 | ] 388 | 389 | [[package]] 390 | name = "httpcore" 391 | version = "1.0.4" 392 | description = "A minimal low-level HTTP client." 393 | optional = false 394 | python-versions = ">=3.8" 395 | files = [ 396 | {file = "httpcore-1.0.4-py3-none-any.whl", hash = "sha256:ac418c1db41bade2ad53ae2f3834a3a0f5ae76b56cf5aa497d2d033384fc7d73"}, 397 | {file = "httpcore-1.0.4.tar.gz", hash = "sha256:cb2839ccfcba0d2d3c1131d3c3e26dfc327326fbe7a5dc0dbfe9f6c9151bb022"}, 398 | ] 399 | 400 | [package.dependencies] 401 | certifi = "*" 402 | h11 = ">=0.13,<0.15" 403 | 404 | [package.extras] 405 | asyncio = ["anyio (>=4.0,<5.0)"] 406 | http2 = ["h2 (>=3,<5)"] 407 | socks = ["socksio (==1.*)"] 408 | trio = ["trio (>=0.22.0,<0.25.0)"] 409 | 410 | [[package]] 411 | name = "httpx" 412 | version = "0.27.0" 413 | description = "The next generation HTTP client." 414 | optional = false 415 | python-versions = ">=3.8" 416 | files = [ 417 | {file = "httpx-0.27.0-py3-none-any.whl", hash = "sha256:71d5465162c13681bff01ad59b2cc68dd838ea1f10e51574bac27103f00c91a5"}, 418 | {file = "httpx-0.27.0.tar.gz", hash = "sha256:a0cb88a46f32dc874e04ee956e4c2764aba2aa228f650b06788ba6bda2962ab5"}, 419 | ] 420 | 421 | [package.dependencies] 422 | anyio = "*" 423 | certifi = "*" 424 | httpcore = "==1.*" 425 | idna = "*" 426 | sniffio = "*" 427 | 428 | [package.extras] 429 | brotli = ["brotli", "brotlicffi"] 430 | cli = ["click (==8.*)", "pygments (==2.*)", "rich (>=10,<14)"] 431 | http2 = ["h2 (>=3,<5)"] 432 | socks = ["socksio (==1.*)"] 433 | 434 | [[package]] 435 | name = "idna" 436 | version = "3.6" 437 | description = "Internationalized Domain Names in Applications (IDNA)" 438 | optional = false 439 | python-versions = ">=3.5" 440 | files = [ 441 | {file = "idna-3.6-py3-none-any.whl", hash = "sha256:c05567e9c24a6b9faaa835c4821bad0590fbb9d5779e7caa6e1cc4978e7eb24f"}, 442 | {file = "idna-3.6.tar.gz", hash = "sha256:9ecdbbd083b06798ae1e86adcbfe8ab1479cf864e4ee30fe4e46a003d12491ca"}, 443 | ] 444 | 445 | [[package]] 446 | name = "iniconfig" 447 | version = "2.0.0" 448 | description = "brain-dead simple config-ini parsing" 449 | optional = false 450 | python-versions = ">=3.7" 451 | files = [ 452 | {file = "iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374"}, 453 | {file = "iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3"}, 454 | ] 455 | 456 | [[package]] 457 | name = "numpy" 458 | version = "1.26.4" 459 | description = "Fundamental package for array computing in Python" 460 | optional = false 461 | python-versions = ">=3.9" 462 | files = [ 463 | {file = "numpy-1.26.4-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:9ff0f4f29c51e2803569d7a51c2304de5554655a60c5d776e35b4a41413830d0"}, 464 | {file = "numpy-1.26.4-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:2e4ee3380d6de9c9ec04745830fd9e2eccb3e6cf790d39d7b98ffd19b0dd754a"}, 465 | {file = "numpy-1.26.4-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d209d8969599b27ad20994c8e41936ee0964e6da07478d6c35016bc386b66ad4"}, 466 | {file = "numpy-1.26.4-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ffa75af20b44f8dba823498024771d5ac50620e6915abac414251bd971b4529f"}, 467 | {file = "numpy-1.26.4-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:62b8e4b1e28009ef2846b4c7852046736bab361f7aeadeb6a5b89ebec3c7055a"}, 468 | {file = "numpy-1.26.4-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:a4abb4f9001ad2858e7ac189089c42178fcce737e4169dc61321660f1a96c7d2"}, 469 | {file = "numpy-1.26.4-cp310-cp310-win32.whl", hash = "sha256:bfe25acf8b437eb2a8b2d49d443800a5f18508cd811fea3181723922a8a82b07"}, 470 | {file = "numpy-1.26.4-cp310-cp310-win_amd64.whl", hash = "sha256:b97fe8060236edf3662adfc2c633f56a08ae30560c56310562cb4f95500022d5"}, 471 | {file = "numpy-1.26.4-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:4c66707fabe114439db9068ee468c26bbdf909cac0fb58686a42a24de1760c71"}, 472 | {file = "numpy-1.26.4-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:edd8b5fe47dab091176d21bb6de568acdd906d1887a4584a15a9a96a1dca06ef"}, 473 | {file = "numpy-1.26.4-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7ab55401287bfec946ced39700c053796e7cc0e3acbef09993a9ad2adba6ca6e"}, 474 | {file = "numpy-1.26.4-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:666dbfb6ec68962c033a450943ded891bed2d54e6755e35e5835d63f4f6931d5"}, 475 | {file = "numpy-1.26.4-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:96ff0b2ad353d8f990b63294c8986f1ec3cb19d749234014f4e7eb0112ceba5a"}, 476 | {file = "numpy-1.26.4-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:60dedbb91afcbfdc9bc0b1f3f402804070deed7392c23eb7a7f07fa857868e8a"}, 477 | {file = "numpy-1.26.4-cp311-cp311-win32.whl", hash = "sha256:1af303d6b2210eb850fcf03064d364652b7120803a0b872f5211f5234b399f20"}, 478 | {file = "numpy-1.26.4-cp311-cp311-win_amd64.whl", hash = "sha256:cd25bcecc4974d09257ffcd1f098ee778f7834c3ad767fe5db785be9a4aa9cb2"}, 479 | {file = "numpy-1.26.4-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:b3ce300f3644fb06443ee2222c2201dd3a89ea6040541412b8fa189341847218"}, 480 | {file = "numpy-1.26.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:03a8c78d01d9781b28a6989f6fa1bb2c4f2d51201cf99d3dd875df6fbd96b23b"}, 481 | {file = "numpy-1.26.4-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:9fad7dcb1aac3c7f0584a5a8133e3a43eeb2fe127f47e3632d43d677c66c102b"}, 482 | {file = "numpy-1.26.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:675d61ffbfa78604709862923189bad94014bef562cc35cf61d3a07bba02a7ed"}, 483 | {file = "numpy-1.26.4-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:ab47dbe5cc8210f55aa58e4805fe224dac469cde56b9f731a4c098b91917159a"}, 484 | {file = "numpy-1.26.4-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:1dda2e7b4ec9dd512f84935c5f126c8bd8b9f2fc001e9f54af255e8c5f16b0e0"}, 485 | {file = "numpy-1.26.4-cp312-cp312-win32.whl", hash = "sha256:50193e430acfc1346175fcbdaa28ffec49947a06918b7b92130744e81e640110"}, 486 | {file = "numpy-1.26.4-cp312-cp312-win_amd64.whl", hash = "sha256:08beddf13648eb95f8d867350f6a018a4be2e5ad54c8d8caed89ebca558b2818"}, 487 | {file = "numpy-1.26.4-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:7349ab0fa0c429c82442a27a9673fc802ffdb7c7775fad780226cb234965e53c"}, 488 | {file = "numpy-1.26.4-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:52b8b60467cd7dd1e9ed082188b4e6bb35aa5cdd01777621a1658910745b90be"}, 489 | {file = "numpy-1.26.4-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:d5241e0a80d808d70546c697135da2c613f30e28251ff8307eb72ba696945764"}, 490 | {file = "numpy-1.26.4-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f870204a840a60da0b12273ef34f7051e98c3b5961b61b0c2c1be6dfd64fbcd3"}, 491 | {file = "numpy-1.26.4-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:679b0076f67ecc0138fd2ede3a8fd196dddc2ad3254069bcb9faf9a79b1cebcd"}, 492 | {file = "numpy-1.26.4-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:47711010ad8555514b434df65f7d7b076bb8261df1ca9bb78f53d3b2db02e95c"}, 493 | {file = "numpy-1.26.4-cp39-cp39-win32.whl", hash = "sha256:a354325ee03388678242a4d7ebcd08b5c727033fcff3b2f536aea978e15ee9e6"}, 494 | {file = "numpy-1.26.4-cp39-cp39-win_amd64.whl", hash = "sha256:3373d5d70a5fe74a2c1bb6d2cfd9609ecf686d47a2d7b1d37a8f3b6bf6003aea"}, 495 | {file = "numpy-1.26.4-pp39-pypy39_pp73-macosx_10_9_x86_64.whl", hash = "sha256:afedb719a9dcfc7eaf2287b839d8198e06dcd4cb5d276a3df279231138e83d30"}, 496 | {file = "numpy-1.26.4-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:95a7476c59002f2f6c590b9b7b998306fba6a5aa646b1e22ddfeaf8f78c3a29c"}, 497 | {file = "numpy-1.26.4-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:7e50d0a0cc3189f9cb0aeb3a6a6af18c16f59f004b866cd2be1c14b36134a4a0"}, 498 | {file = "numpy-1.26.4.tar.gz", hash = "sha256:2a02aba9ed12e4ac4eb3ea9421c420301a0c6460d9830d74a9df87efa4912010"}, 499 | ] 500 | 501 | [[package]] 502 | name = "openai" 503 | version = "1.12.0" 504 | description = "The official Python library for the openai API" 505 | optional = false 506 | python-versions = ">=3.7.1" 507 | files = [ 508 | {file = "openai-1.12.0-py3-none-any.whl", hash = "sha256:a54002c814e05222e413664f651b5916714e4700d041d5cf5724d3ae1a3e3481"}, 509 | {file = "openai-1.12.0.tar.gz", hash = "sha256:99c5d257d09ea6533d689d1cc77caa0ac679fa21efef8893d8b0832a86877f1b"}, 510 | ] 511 | 512 | [package.dependencies] 513 | anyio = ">=3.5.0,<5" 514 | distro = ">=1.7.0,<2" 515 | httpx = ">=0.23.0,<1" 516 | pydantic = ">=1.9.0,<3" 517 | sniffio = "*" 518 | tqdm = ">4" 519 | typing-extensions = ">=4.7,<5" 520 | 521 | [package.extras] 522 | datalib = ["numpy (>=1)", "pandas (>=1.2.3)", "pandas-stubs (>=1.1.0.11)"] 523 | 524 | [[package]] 525 | name = "opencv-python-headless" 526 | version = "4.9.0.80" 527 | description = "Wrapper package for OpenCV python bindings." 528 | optional = false 529 | python-versions = ">=3.6" 530 | files = [ 531 | {file = "opencv-python-headless-4.9.0.80.tar.gz", hash = "sha256:71a4cd8cf7c37122901d8e81295db7fb188730e33a0e40039a4e59c1030b0958"}, 532 | {file = "opencv_python_headless-4.9.0.80-cp37-abi3-macosx_10_16_x86_64.whl", hash = "sha256:2ea8a2edc4db87841991b2fbab55fc07b97ecb602e0f47d5d485bd75cee17c1a"}, 533 | {file = "opencv_python_headless-4.9.0.80-cp37-abi3-macosx_11_0_arm64.whl", hash = "sha256:e0ee54e27be493e8f7850847edae3128e18b540dac1d7b2e4001b8944e11e1c6"}, 534 | {file = "opencv_python_headless-4.9.0.80-cp37-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:57ce2865e8fec431c6f97a81e9faaf23fa5be61011d0a75ccf47a3c0d65fa73d"}, 535 | {file = "opencv_python_headless-4.9.0.80-cp37-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:976656362d68d9f40a5c66f83901430538002465f7db59142784f3893918f3df"}, 536 | {file = "opencv_python_headless-4.9.0.80-cp37-abi3-win32.whl", hash = "sha256:11e3849d83e6651d4e7699aadda9ec7ed7c38957cbbcb99db074f2a2d2de9670"}, 537 | {file = "opencv_python_headless-4.9.0.80-cp37-abi3-win_amd64.whl", hash = "sha256:a8056c2cb37cd65dfcdf4153ca16f7362afcf3a50d600d6bb69c660fc61ee29c"}, 538 | ] 539 | 540 | [package.dependencies] 541 | numpy = [ 542 | {version = ">=1.26.0", markers = "python_version >= \"3.12\""}, 543 | {version = ">=1.23.5", markers = "python_version >= \"3.11\" and python_version < \"3.12\""}, 544 | {version = ">=1.21.4", markers = "python_version >= \"3.10\" and platform_system == \"Darwin\" and python_version < \"3.11\""}, 545 | {version = ">=1.21.2", markers = "platform_system != \"Darwin\" and python_version >= \"3.10\" and python_version < \"3.11\""}, 546 | ] 547 | 548 | [[package]] 549 | name = "openpyxl" 550 | version = "3.1.2" 551 | description = "A Python library to read/write Excel 2010 xlsx/xlsm files" 552 | optional = false 553 | python-versions = ">=3.6" 554 | files = [ 555 | {file = "openpyxl-3.1.2-py2.py3-none-any.whl", hash = "sha256:f91456ead12ab3c6c2e9491cf33ba6d08357d802192379bb482f1033ade496f5"}, 556 | {file = "openpyxl-3.1.2.tar.gz", hash = "sha256:a6f5977418eff3b2d5500d54d9db50c8277a368436f4e4f8ddb1be3422870184"}, 557 | ] 558 | 559 | [package.dependencies] 560 | et-xmlfile = "*" 561 | 562 | [[package]] 563 | name = "packaging" 564 | version = "23.2" 565 | description = "Core utilities for Python packages" 566 | optional = false 567 | python-versions = ">=3.7" 568 | files = [ 569 | {file = "packaging-23.2-py3-none-any.whl", hash = "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7"}, 570 | {file = "packaging-23.2.tar.gz", hash = "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5"}, 571 | ] 572 | 573 | [[package]] 574 | name = "pandas" 575 | version = "2.2.0" 576 | description = "Powerful data structures for data analysis, time series, and statistics" 577 | optional = false 578 | python-versions = ">=3.9" 579 | files = [ 580 | {file = "pandas-2.2.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:8108ee1712bb4fa2c16981fba7e68b3f6ea330277f5ca34fa8d557e986a11670"}, 581 | {file = "pandas-2.2.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:736da9ad4033aeab51d067fc3bd69a0ba36f5a60f66a527b3d72e2030e63280a"}, 582 | {file = "pandas-2.2.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:38e0b4fc3ddceb56ec8a287313bc22abe17ab0eb184069f08fc6a9352a769b18"}, 583 | {file = "pandas-2.2.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:20404d2adefe92aed3b38da41d0847a143a09be982a31b85bc7dd565bdba0f4e"}, 584 | {file = "pandas-2.2.0-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:7ea3ee3f125032bfcade3a4cf85131ed064b4f8dd23e5ce6fa16473e48ebcaf5"}, 585 | {file = "pandas-2.2.0-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:f9670b3ac00a387620489dfc1bca66db47a787f4e55911f1293063a78b108df1"}, 586 | {file = "pandas-2.2.0-cp310-cp310-win_amd64.whl", hash = "sha256:5a946f210383c7e6d16312d30b238fd508d80d927014f3b33fb5b15c2f895430"}, 587 | {file = "pandas-2.2.0-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:a1b438fa26b208005c997e78672f1aa8138f67002e833312e6230f3e57fa87d5"}, 588 | {file = "pandas-2.2.0-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:8ce2fbc8d9bf303ce54a476116165220a1fedf15985b09656b4b4275300e920b"}, 589 | {file = "pandas-2.2.0-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2707514a7bec41a4ab81f2ccce8b382961a29fbe9492eab1305bb075b2b1ff4f"}, 590 | {file = "pandas-2.2.0-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:85793cbdc2d5bc32620dc8ffa715423f0c680dacacf55056ba13454a5be5de88"}, 591 | {file = "pandas-2.2.0-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:cfd6c2491dc821b10c716ad6776e7ab311f7df5d16038d0b7458bc0b67dc10f3"}, 592 | {file = "pandas-2.2.0-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:a146b9dcacc3123aa2b399df1a284de5f46287a4ab4fbfc237eac98a92ebcb71"}, 593 | {file = "pandas-2.2.0-cp311-cp311-win_amd64.whl", hash = "sha256:fbc1b53c0e1fdf16388c33c3cca160f798d38aea2978004dd3f4d3dec56454c9"}, 594 | {file = "pandas-2.2.0-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:a41d06f308a024981dcaa6c41f2f2be46a6b186b902c94c2674e8cb5c42985bc"}, 595 | {file = "pandas-2.2.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:159205c99d7a5ce89ecfc37cb08ed179de7783737cea403b295b5eda8e9c56d1"}, 596 | {file = "pandas-2.2.0-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:eb1e1f3861ea9132b32f2133788f3b14911b68102d562715d71bd0013bc45440"}, 597 | {file = "pandas-2.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:761cb99b42a69005dec2b08854fb1d4888fdf7b05db23a8c5a099e4b886a2106"}, 598 | {file = "pandas-2.2.0-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:a20628faaf444da122b2a64b1e5360cde100ee6283ae8effa0d8745153809a2e"}, 599 | {file = "pandas-2.2.0-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:f5be5d03ea2073627e7111f61b9f1f0d9625dc3c4d8dda72cc827b0c58a1d042"}, 600 | {file = "pandas-2.2.0-cp312-cp312-win_amd64.whl", hash = "sha256:a626795722d893ed6aacb64d2401d017ddc8a2341b49e0384ab9bf7112bdec30"}, 601 | {file = "pandas-2.2.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9f66419d4a41132eb7e9a73dcec9486cf5019f52d90dd35547af11bc58f8637d"}, 602 | {file = "pandas-2.2.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:57abcaeda83fb80d447f28ab0cc7b32b13978f6f733875ebd1ed14f8fbc0f4ab"}, 603 | {file = "pandas-2.2.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e60f1f7dba3c2d5ca159e18c46a34e7ca7247a73b5dd1a22b6d59707ed6b899a"}, 604 | {file = "pandas-2.2.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:eb61dc8567b798b969bcc1fc964788f5a68214d333cade8319c7ab33e2b5d88a"}, 605 | {file = "pandas-2.2.0-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:52826b5f4ed658fa2b729264d63f6732b8b29949c7fd234510d57c61dbeadfcd"}, 606 | {file = "pandas-2.2.0-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:bde2bc699dbd80d7bc7f9cab1e23a95c4375de615860ca089f34e7c64f4a8de7"}, 607 | {file = "pandas-2.2.0-cp39-cp39-win_amd64.whl", hash = "sha256:3de918a754bbf2da2381e8a3dcc45eede8cd7775b047b923f9006d5f876802ae"}, 608 | {file = "pandas-2.2.0.tar.gz", hash = "sha256:30b83f7c3eb217fb4d1b494a57a2fda5444f17834f5df2de6b2ffff68dc3c8e2"}, 609 | ] 610 | 611 | [package.dependencies] 612 | numpy = [ 613 | {version = ">=1.26.0,<2", markers = "python_version >= \"3.12\""}, 614 | {version = ">=1.23.2,<2", markers = "python_version == \"3.11\""}, 615 | {version = ">=1.22.4,<2", markers = "python_version < \"3.11\""}, 616 | ] 617 | python-dateutil = ">=2.8.2" 618 | pytz = ">=2020.1" 619 | tzdata = ">=2022.7" 620 | 621 | [package.extras] 622 | all = ["PyQt5 (>=5.15.9)", "SQLAlchemy (>=2.0.0)", "adbc-driver-postgresql (>=0.8.0)", "adbc-driver-sqlite (>=0.8.0)", "beautifulsoup4 (>=4.11.2)", "bottleneck (>=1.3.6)", "dataframe-api-compat (>=0.1.7)", "fastparquet (>=2022.12.0)", "fsspec (>=2022.11.0)", "gcsfs (>=2022.11.0)", "html5lib (>=1.1)", "hypothesis (>=6.46.1)", "jinja2 (>=3.1.2)", "lxml (>=4.9.2)", "matplotlib (>=3.6.3)", "numba (>=0.56.4)", "numexpr (>=2.8.4)", "odfpy (>=1.4.1)", "openpyxl (>=3.1.0)", "pandas-gbq (>=0.19.0)", "psycopg2 (>=2.9.6)", "pyarrow (>=10.0.1)", "pymysql (>=1.0.2)", "pyreadstat (>=1.2.0)", "pytest (>=7.3.2)", "pytest-xdist (>=2.2.0)", "python-calamine (>=0.1.7)", "pyxlsb (>=1.0.10)", "qtpy (>=2.3.0)", "s3fs (>=2022.11.0)", "scipy (>=1.10.0)", "tables (>=3.8.0)", "tabulate (>=0.9.0)", "xarray (>=2022.12.0)", "xlrd (>=2.0.1)", "xlsxwriter (>=3.0.5)", "zstandard (>=0.19.0)"] 623 | aws = ["s3fs (>=2022.11.0)"] 624 | clipboard = ["PyQt5 (>=5.15.9)", "qtpy (>=2.3.0)"] 625 | compression = ["zstandard (>=0.19.0)"] 626 | computation = ["scipy (>=1.10.0)", "xarray (>=2022.12.0)"] 627 | consortium-standard = ["dataframe-api-compat (>=0.1.7)"] 628 | excel = ["odfpy (>=1.4.1)", "openpyxl (>=3.1.0)", "python-calamine (>=0.1.7)", "pyxlsb (>=1.0.10)", "xlrd (>=2.0.1)", "xlsxwriter (>=3.0.5)"] 629 | feather = ["pyarrow (>=10.0.1)"] 630 | fss = ["fsspec (>=2022.11.0)"] 631 | gcp = ["gcsfs (>=2022.11.0)", "pandas-gbq (>=0.19.0)"] 632 | hdf5 = ["tables (>=3.8.0)"] 633 | html = ["beautifulsoup4 (>=4.11.2)", "html5lib (>=1.1)", "lxml (>=4.9.2)"] 634 | mysql = ["SQLAlchemy (>=2.0.0)", "pymysql (>=1.0.2)"] 635 | output-formatting = ["jinja2 (>=3.1.2)", "tabulate (>=0.9.0)"] 636 | parquet = ["pyarrow (>=10.0.1)"] 637 | performance = ["bottleneck (>=1.3.6)", "numba (>=0.56.4)", "numexpr (>=2.8.4)"] 638 | plot = ["matplotlib (>=3.6.3)"] 639 | postgresql = ["SQLAlchemy (>=2.0.0)", "adbc-driver-postgresql (>=0.8.0)", "psycopg2 (>=2.9.6)"] 640 | spss = ["pyreadstat (>=1.2.0)"] 641 | sql-other = ["SQLAlchemy (>=2.0.0)", "adbc-driver-postgresql (>=0.8.0)", "adbc-driver-sqlite (>=0.8.0)"] 642 | test = ["hypothesis (>=6.46.1)", "pytest (>=7.3.2)", "pytest-xdist (>=2.2.0)"] 643 | xml = ["lxml (>=4.9.2)"] 644 | 645 | [[package]] 646 | name = "pdfminer-six" 647 | version = "20231228" 648 | description = "PDF parser and analyzer" 649 | optional = false 650 | python-versions = ">=3.6" 651 | files = [ 652 | {file = "pdfminer.six-20231228-py3-none-any.whl", hash = "sha256:e8d3c3310e6fbc1fe414090123ab01351634b4ecb021232206c4c9a8ca3e3b8f"}, 653 | {file = "pdfminer.six-20231228.tar.gz", hash = "sha256:6004da3ad1a7a4d45930cb950393df89b068e73be365a6ff64a838d37bcb08c4"}, 654 | ] 655 | 656 | [package.dependencies] 657 | charset-normalizer = ">=2.0.0" 658 | cryptography = ">=36.0.0" 659 | 660 | [package.extras] 661 | dev = ["black", "mypy (==0.931)", "nox", "pytest"] 662 | docs = ["sphinx", "sphinx-argparse"] 663 | image = ["Pillow"] 664 | 665 | [[package]] 666 | name = "pluggy" 667 | version = "1.4.0" 668 | description = "plugin and hook calling mechanisms for python" 669 | optional = false 670 | python-versions = ">=3.8" 671 | files = [ 672 | {file = "pluggy-1.4.0-py3-none-any.whl", hash = "sha256:7db9f7b503d67d1c5b95f59773ebb58a8c1c288129a88665838012cfb07b8981"}, 673 | {file = "pluggy-1.4.0.tar.gz", hash = "sha256:8c85c2876142a764e5b7548e7d9a0e0ddb46f5185161049a79b7e974454223be"}, 674 | ] 675 | 676 | [package.extras] 677 | dev = ["pre-commit", "tox"] 678 | testing = ["pytest", "pytest-benchmark"] 679 | 680 | [[package]] 681 | name = "pycparser" 682 | version = "2.21" 683 | description = "C parser in Python" 684 | optional = false 685 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" 686 | files = [ 687 | {file = "pycparser-2.21-py2.py3-none-any.whl", hash = "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9"}, 688 | {file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"}, 689 | ] 690 | 691 | [[package]] 692 | name = "pydantic" 693 | version = "2.6.1" 694 | description = "Data validation using Python type hints" 695 | optional = false 696 | python-versions = ">=3.8" 697 | files = [ 698 | {file = "pydantic-2.6.1-py3-none-any.whl", hash = "sha256:0b6a909df3192245cb736509a92ff69e4fef76116feffec68e93a567347bae6f"}, 699 | {file = "pydantic-2.6.1.tar.gz", hash = "sha256:4fd5c182a2488dc63e6d32737ff19937888001e2a6d86e94b3f233104a5d1fa9"}, 700 | ] 701 | 702 | [package.dependencies] 703 | annotated-types = ">=0.4.0" 704 | pydantic-core = "2.16.2" 705 | typing-extensions = ">=4.6.1" 706 | 707 | [package.extras] 708 | email = ["email-validator (>=2.0.0)"] 709 | 710 | [[package]] 711 | name = "pydantic-core" 712 | version = "2.16.2" 713 | description = "" 714 | optional = false 715 | python-versions = ">=3.8" 716 | files = [ 717 | {file = "pydantic_core-2.16.2-cp310-cp310-macosx_10_12_x86_64.whl", hash = "sha256:3fab4e75b8c525a4776e7630b9ee48aea50107fea6ca9f593c98da3f4d11bf7c"}, 718 | {file = "pydantic_core-2.16.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:8bde5b48c65b8e807409e6f20baee5d2cd880e0fad00b1a811ebc43e39a00ab2"}, 719 | {file = "pydantic_core-2.16.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2924b89b16420712e9bb8192396026a8fbd6d8726224f918353ac19c4c043d2a"}, 720 | {file = "pydantic_core-2.16.2-cp310-cp310-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:16aa02e7a0f539098e215fc193c8926c897175d64c7926d00a36188917717a05"}, 721 | {file = "pydantic_core-2.16.2-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:936a787f83db1f2115ee829dd615c4f684ee48ac4de5779ab4300994d8af325b"}, 722 | {file = "pydantic_core-2.16.2-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:459d6be6134ce3b38e0ef76f8a672924460c455d45f1ad8fdade36796df1ddc8"}, 723 | {file = "pydantic_core-2.16.2-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:4f9ee4febb249c591d07b2d4dd36ebcad0ccd128962aaa1801508320896575ef"}, 724 | {file = "pydantic_core-2.16.2-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:40a0bd0bed96dae5712dab2aba7d334a6c67cbcac2ddfca7dbcc4a8176445990"}, 725 | {file = "pydantic_core-2.16.2-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:870dbfa94de9b8866b37b867a2cb37a60c401d9deb4a9ea392abf11a1f98037b"}, 726 | {file = "pydantic_core-2.16.2-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:308974fdf98046db28440eb3377abba274808bf66262e042c412eb2adf852731"}, 727 | {file = "pydantic_core-2.16.2-cp310-none-win32.whl", hash = "sha256:a477932664d9611d7a0816cc3c0eb1f8856f8a42435488280dfbf4395e141485"}, 728 | {file = "pydantic_core-2.16.2-cp310-none-win_amd64.whl", hash = "sha256:8f9142a6ed83d90c94a3efd7af8873bf7cefed2d3d44387bf848888482e2d25f"}, 729 | {file = "pydantic_core-2.16.2-cp311-cp311-macosx_10_12_x86_64.whl", hash = "sha256:406fac1d09edc613020ce9cf3f2ccf1a1b2f57ab00552b4c18e3d5276c67eb11"}, 730 | {file = "pydantic_core-2.16.2-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:ce232a6170dd6532096cadbf6185271e4e8c70fc9217ebe105923ac105da9978"}, 731 | {file = "pydantic_core-2.16.2-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a90fec23b4b05a09ad988e7a4f4e081711a90eb2a55b9c984d8b74597599180f"}, 732 | {file = "pydantic_core-2.16.2-cp311-cp311-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:8aafeedb6597a163a9c9727d8a8bd363a93277701b7bfd2749fbefee2396469e"}, 733 | {file = "pydantic_core-2.16.2-cp311-cp311-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:9957433c3a1b67bdd4c63717eaf174ebb749510d5ea612cd4e83f2d9142f3fc8"}, 734 | {file = "pydantic_core-2.16.2-cp311-cp311-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:b0d7a9165167269758145756db43a133608a531b1e5bb6a626b9ee24bc38a8f7"}, 735 | {file = "pydantic_core-2.16.2-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:dffaf740fe2e147fedcb6b561353a16243e654f7fe8e701b1b9db148242e1272"}, 736 | {file = "pydantic_core-2.16.2-cp311-cp311-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:f8ed79883b4328b7f0bd142733d99c8e6b22703e908ec63d930b06be3a0e7113"}, 737 | {file = "pydantic_core-2.16.2-cp311-cp311-musllinux_1_1_aarch64.whl", hash = "sha256:cf903310a34e14651c9de056fcc12ce090560864d5a2bb0174b971685684e1d8"}, 738 | {file = "pydantic_core-2.16.2-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:46b0d5520dbcafea9a8645a8164658777686c5c524d381d983317d29687cce97"}, 739 | {file = "pydantic_core-2.16.2-cp311-none-win32.whl", hash = "sha256:70651ff6e663428cea902dac297066d5c6e5423fda345a4ca62430575364d62b"}, 740 | {file = "pydantic_core-2.16.2-cp311-none-win_amd64.whl", hash = "sha256:98dc6f4f2095fc7ad277782a7c2c88296badcad92316b5a6e530930b1d475ebc"}, 741 | {file = "pydantic_core-2.16.2-cp311-none-win_arm64.whl", hash = "sha256:ef6113cd31411eaf9b39fc5a8848e71c72656fd418882488598758b2c8c6dfa0"}, 742 | {file = "pydantic_core-2.16.2-cp312-cp312-macosx_10_12_x86_64.whl", hash = "sha256:88646cae28eb1dd5cd1e09605680c2b043b64d7481cdad7f5003ebef401a3039"}, 743 | {file = "pydantic_core-2.16.2-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:7b883af50eaa6bb3299780651e5be921e88050ccf00e3e583b1e92020333304b"}, 744 | {file = "pydantic_core-2.16.2-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:7bf26c2e2ea59d32807081ad51968133af3025c4ba5753e6a794683d2c91bf6e"}, 745 | {file = "pydantic_core-2.16.2-cp312-cp312-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:99af961d72ac731aae2a1b55ccbdae0733d816f8bfb97b41909e143de735f522"}, 746 | {file = "pydantic_core-2.16.2-cp312-cp312-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:02906e7306cb8c5901a1feb61f9ab5e5c690dbbeaa04d84c1b9ae2a01ebe9379"}, 747 | {file = "pydantic_core-2.16.2-cp312-cp312-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:d5362d099c244a2d2f9659fb3c9db7c735f0004765bbe06b99be69fbd87c3f15"}, 748 | {file = "pydantic_core-2.16.2-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:3ac426704840877a285d03a445e162eb258924f014e2f074e209d9b4ff7bf380"}, 749 | {file = "pydantic_core-2.16.2-cp312-cp312-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:b94cbda27267423411c928208e89adddf2ea5dd5f74b9528513f0358bba019cb"}, 750 | {file = "pydantic_core-2.16.2-cp312-cp312-musllinux_1_1_aarch64.whl", hash = "sha256:6db58c22ac6c81aeac33912fb1af0e930bc9774166cdd56eade913d5f2fff35e"}, 751 | {file = "pydantic_core-2.16.2-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:396fdf88b1b503c9c59c84a08b6833ec0c3b5ad1a83230252a9e17b7dfb4cffc"}, 752 | {file = "pydantic_core-2.16.2-cp312-none-win32.whl", hash = "sha256:7c31669e0c8cc68400ef0c730c3a1e11317ba76b892deeefaf52dcb41d56ed5d"}, 753 | {file = "pydantic_core-2.16.2-cp312-none-win_amd64.whl", hash = "sha256:a3b7352b48fbc8b446b75f3069124e87f599d25afb8baa96a550256c031bb890"}, 754 | {file = "pydantic_core-2.16.2-cp312-none-win_arm64.whl", hash = "sha256:a9e523474998fb33f7c1a4d55f5504c908d57add624599e095c20fa575b8d943"}, 755 | {file = "pydantic_core-2.16.2-cp38-cp38-macosx_10_12_x86_64.whl", hash = "sha256:ae34418b6b389d601b31153b84dce480351a352e0bb763684a1b993d6be30f17"}, 756 | {file = "pydantic_core-2.16.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:732bd062c9e5d9582a30e8751461c1917dd1ccbdd6cafb032f02c86b20d2e7ec"}, 757 | {file = "pydantic_core-2.16.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e4b52776a2e3230f4854907a1e0946eec04d41b1fc64069ee774876bbe0eab55"}, 758 | {file = "pydantic_core-2.16.2-cp38-cp38-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:ef551c053692b1e39e3f7950ce2296536728871110e7d75c4e7753fb30ca87f4"}, 759 | {file = "pydantic_core-2.16.2-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:ebb892ed8599b23fa8f1799e13a12c87a97a6c9d0f497525ce9858564c4575a4"}, 760 | {file = "pydantic_core-2.16.2-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:aa6c8c582036275997a733427b88031a32ffa5dfc3124dc25a730658c47a572f"}, 761 | {file = "pydantic_core-2.16.2-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e4ba0884a91f1aecce75202473ab138724aa4fb26d7707f2e1fa6c3e68c84fbf"}, 762 | {file = "pydantic_core-2.16.2-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:7924e54f7ce5d253d6160090ddc6df25ed2feea25bfb3339b424a9dd591688bc"}, 763 | {file = "pydantic_core-2.16.2-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:69a7b96b59322a81c2203be537957313b07dd333105b73db0b69212c7d867b4b"}, 764 | {file = "pydantic_core-2.16.2-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:7e6231aa5bdacda78e96ad7b07d0c312f34ba35d717115f4b4bff6cb87224f0f"}, 765 | {file = "pydantic_core-2.16.2-cp38-none-win32.whl", hash = "sha256:41dac3b9fce187a25c6253ec79a3f9e2a7e761eb08690e90415069ea4a68ff7a"}, 766 | {file = "pydantic_core-2.16.2-cp38-none-win_amd64.whl", hash = "sha256:f685dbc1fdadb1dcd5b5e51e0a378d4685a891b2ddaf8e2bba89bd3a7144e44a"}, 767 | {file = "pydantic_core-2.16.2-cp39-cp39-macosx_10_12_x86_64.whl", hash = "sha256:55749f745ebf154c0d63d46c8c58594d8894b161928aa41adbb0709c1fe78b77"}, 768 | {file = "pydantic_core-2.16.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:b30b0dd58a4509c3bd7eefddf6338565c4905406aee0c6e4a5293841411a1286"}, 769 | {file = "pydantic_core-2.16.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:18de31781cdc7e7b28678df7c2d7882f9692ad060bc6ee3c94eb15a5d733f8f7"}, 770 | {file = "pydantic_core-2.16.2-cp39-cp39-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:5864b0242f74b9dd0b78fd39db1768bc3f00d1ffc14e596fd3e3f2ce43436a33"}, 771 | {file = "pydantic_core-2.16.2-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:b8f9186ca45aee030dc8234118b9c0784ad91a0bb27fc4e7d9d6608a5e3d386c"}, 772 | {file = "pydantic_core-2.16.2-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:cc6f6c9be0ab6da37bc77c2dda5f14b1d532d5dbef00311ee6e13357a418e646"}, 773 | {file = "pydantic_core-2.16.2-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:aa057095f621dad24a1e906747179a69780ef45cc8f69e97463692adbcdae878"}, 774 | {file = "pydantic_core-2.16.2-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:6ad84731a26bcfb299f9eab56c7932d46f9cad51c52768cace09e92a19e4cf55"}, 775 | {file = "pydantic_core-2.16.2-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:3b052c753c4babf2d1edc034c97851f867c87d6f3ea63a12e2700f159f5c41c3"}, 776 | {file = "pydantic_core-2.16.2-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:e0f686549e32ccdb02ae6f25eee40cc33900910085de6aa3790effd391ae10c2"}, 777 | {file = "pydantic_core-2.16.2-cp39-none-win32.whl", hash = "sha256:7afb844041e707ac9ad9acad2188a90bffce2c770e6dc2318be0c9916aef1469"}, 778 | {file = "pydantic_core-2.16.2-cp39-none-win_amd64.whl", hash = "sha256:9da90d393a8227d717c19f5397688a38635afec89f2e2d7af0df037f3249c39a"}, 779 | {file = "pydantic_core-2.16.2-pp310-pypy310_pp73-macosx_10_12_x86_64.whl", hash = "sha256:5f60f920691a620b03082692c378661947d09415743e437a7478c309eb0e4f82"}, 780 | {file = "pydantic_core-2.16.2-pp310-pypy310_pp73-macosx_11_0_arm64.whl", hash = "sha256:47924039e785a04d4a4fa49455e51b4eb3422d6eaacfde9fc9abf8fdef164e8a"}, 781 | {file = "pydantic_core-2.16.2-pp310-pypy310_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e6294e76b0380bb7a61eb8a39273c40b20beb35e8c87ee101062834ced19c545"}, 782 | {file = "pydantic_core-2.16.2-pp310-pypy310_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:fe56851c3f1d6f5384b3051c536cc81b3a93a73faf931f404fef95217cf1e10d"}, 783 | {file = "pydantic_core-2.16.2-pp310-pypy310_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:9d776d30cde7e541b8180103c3f294ef7c1862fd45d81738d156d00551005784"}, 784 | {file = "pydantic_core-2.16.2-pp310-pypy310_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:72f7919af5de5ecfaf1eba47bf9a5d8aa089a3340277276e5636d16ee97614d7"}, 785 | {file = "pydantic_core-2.16.2-pp310-pypy310_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:4bfcbde6e06c56b30668a0c872d75a7ef3025dc3c1823a13cf29a0e9b33f67e8"}, 786 | {file = "pydantic_core-2.16.2-pp310-pypy310_pp73-win_amd64.whl", hash = "sha256:ff7c97eb7a29aba230389a2661edf2e9e06ce616c7e35aa764879b6894a44b25"}, 787 | {file = "pydantic_core-2.16.2-pp39-pypy39_pp73-macosx_10_12_x86_64.whl", hash = "sha256:9b5f13857da99325dcabe1cc4e9e6a3d7b2e2c726248ba5dd4be3e8e4a0b6d0e"}, 788 | {file = "pydantic_core-2.16.2-pp39-pypy39_pp73-macosx_11_0_arm64.whl", hash = "sha256:a7e41e3ada4cca5f22b478c08e973c930e5e6c7ba3588fb8e35f2398cdcc1545"}, 789 | {file = "pydantic_core-2.16.2-pp39-pypy39_pp73-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:60eb8ceaa40a41540b9acae6ae7c1f0a67d233c40dc4359c256ad2ad85bdf5e5"}, 790 | {file = "pydantic_core-2.16.2-pp39-pypy39_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:7beec26729d496a12fd23cf8da9944ee338c8b8a17035a560b585c36fe81af20"}, 791 | {file = "pydantic_core-2.16.2-pp39-pypy39_pp73-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:22c5f022799f3cd6741e24f0443ead92ef42be93ffda0d29b2597208c94c3753"}, 792 | {file = "pydantic_core-2.16.2-pp39-pypy39_pp73-musllinux_1_1_aarch64.whl", hash = "sha256:eca58e319f4fd6df004762419612122b2c7e7d95ffafc37e890252f869f3fb2a"}, 793 | {file = "pydantic_core-2.16.2-pp39-pypy39_pp73-musllinux_1_1_x86_64.whl", hash = "sha256:ed957db4c33bc99895f3a1672eca7e80e8cda8bd1e29a80536b4ec2153fa9804"}, 794 | {file = "pydantic_core-2.16.2-pp39-pypy39_pp73-win_amd64.whl", hash = "sha256:459c0d338cc55d099798618f714b21b7ece17eb1a87879f2da20a3ff4c7628e2"}, 795 | {file = "pydantic_core-2.16.2.tar.gz", hash = "sha256:0ba503850d8b8dcc18391f10de896ae51d37fe5fe43dbfb6a35c5c5cad271a06"}, 796 | ] 797 | 798 | [package.dependencies] 799 | typing-extensions = ">=4.6.0,<4.7.0 || >4.7.0" 800 | 801 | [[package]] 802 | name = "pypdf" 803 | version = "4.0.2" 804 | description = "A pure-python PDF library capable of splitting, merging, cropping, and transforming PDF files" 805 | optional = false 806 | python-versions = ">=3.6" 807 | files = [ 808 | {file = "pypdf-4.0.2-py3-none-any.whl", hash = "sha256:a62daa2a24d5a608ba1b6284dde185317ce3644f89b9ebe5314d0c5d1c9f257d"}, 809 | {file = "pypdf-4.0.2.tar.gz", hash = "sha256:3316d9ddfcff5df67ae3cdfe8b945c432aa43e7f970bae7c2a4ab4fe129cd937"}, 810 | ] 811 | 812 | [package.extras] 813 | crypto = ["PyCryptodome", "cryptography"] 814 | dev = ["black", "flit", "pip-tools", "pre-commit (<2.18.0)", "pytest-cov", "pytest-socket", "pytest-timeout", "pytest-xdist", "wheel"] 815 | docs = ["myst_parser", "sphinx", "sphinx_rtd_theme"] 816 | full = ["Pillow (>=8.0.0)", "PyCryptodome", "cryptography"] 817 | image = ["Pillow (>=8.0.0)"] 818 | 819 | [[package]] 820 | name = "pytest" 821 | version = "7.4.4" 822 | description = "pytest: simple powerful testing with Python" 823 | optional = false 824 | python-versions = ">=3.7" 825 | files = [ 826 | {file = "pytest-7.4.4-py3-none-any.whl", hash = "sha256:b090cdf5ed60bf4c45261be03239c2c1c22df034fbffe691abe93cd80cea01d8"}, 827 | {file = "pytest-7.4.4.tar.gz", hash = "sha256:2cf0005922c6ace4a3e2ec8b4080eb0d9753fdc93107415332f50ce9e7994280"}, 828 | ] 829 | 830 | [package.dependencies] 831 | colorama = {version = "*", markers = "sys_platform == \"win32\""} 832 | exceptiongroup = {version = ">=1.0.0rc8", markers = "python_version < \"3.11\""} 833 | iniconfig = "*" 834 | packaging = "*" 835 | pluggy = ">=0.12,<2.0" 836 | tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""} 837 | 838 | [package.extras] 839 | testing = ["argcomplete", "attrs (>=19.2.0)", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "setuptools", "xmlschema"] 840 | 841 | [[package]] 842 | name = "python-dateutil" 843 | version = "2.8.2" 844 | description = "Extensions to the standard Python datetime module" 845 | optional = false 846 | python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,>=2.7" 847 | files = [ 848 | {file = "python-dateutil-2.8.2.tar.gz", hash = "sha256:0123cacc1627ae19ddf3c27a5de5bd67ee4586fbdd6440d9748f8abb483d3e86"}, 849 | {file = "python_dateutil-2.8.2-py2.py3-none-any.whl", hash = "sha256:961d03dc3453ebbc59dbdea9e4e11c5651520a876d0f4db161e8674aae935da9"}, 850 | ] 851 | 852 | [package.dependencies] 853 | six = ">=1.5" 854 | 855 | [[package]] 856 | name = "pytz" 857 | version = "2024.1" 858 | description = "World timezone definitions, modern and historical" 859 | optional = false 860 | python-versions = "*" 861 | files = [ 862 | {file = "pytz-2024.1-py2.py3-none-any.whl", hash = "sha256:328171f4e3623139da4983451950b28e95ac706e13f3f2630a879749e7a8b319"}, 863 | {file = "pytz-2024.1.tar.gz", hash = "sha256:2a29735ea9c18baf14b448846bde5a48030ed267578472d8955cd0e7443a9812"}, 864 | ] 865 | 866 | [[package]] 867 | name = "setuptools" 868 | version = "69.1.0" 869 | description = "Easily download, build, install, upgrade, and uninstall Python packages" 870 | optional = false 871 | python-versions = ">=3.8" 872 | files = [ 873 | {file = "setuptools-69.1.0-py3-none-any.whl", hash = "sha256:c054629b81b946d63a9c6e732bc8b2513a7c3ea645f11d0139a2191d735c60c6"}, 874 | {file = "setuptools-69.1.0.tar.gz", hash = "sha256:850894c4195f09c4ed30dba56213bf7c3f21d86ed6bdaafb5df5972593bfc401"}, 875 | ] 876 | 877 | [package.extras] 878 | docs = ["furo", "jaraco.packaging (>=9.3)", "jaraco.tidelift (>=1.4)", "pygments-github-lexers (==0.0.5)", "rst.linker (>=1.9)", "sphinx (<7.2.5)", "sphinx (>=3.5)", "sphinx-favicon", "sphinx-inline-tabs", "sphinx-lint", "sphinx-notfound-page (>=1,<2)", "sphinx-reredirects", "sphinxcontrib-towncrier"] 879 | testing = ["build[virtualenv]", "filelock (>=3.4.0)", "flake8-2020", "ini2toml[lite] (>=0.9)", "jaraco.develop (>=7.21)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "pip (>=19.1)", "pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-cov", "pytest-enabler (>=2.2)", "pytest-home (>=0.5)", "pytest-mypy (>=0.9.1)", "pytest-perf", "pytest-ruff (>=0.2.1)", "pytest-timeout", "pytest-xdist", "tomli-w (>=1.0.0)", "virtualenv (>=13.0.0)", "wheel"] 880 | testing-integration = ["build[virtualenv] (>=1.0.3)", "filelock (>=3.4.0)", "jaraco.envs (>=2.2)", "jaraco.path (>=3.2.0)", "packaging (>=23.1)", "pytest", "pytest-enabler", "pytest-xdist", "tomli", "virtualenv (>=13.0.0)", "wheel"] 881 | 882 | [[package]] 883 | name = "six" 884 | version = "1.16.0" 885 | description = "Python 2 and 3 compatibility utilities" 886 | optional = false 887 | python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" 888 | files = [ 889 | {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, 890 | {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, 891 | ] 892 | 893 | [[package]] 894 | name = "sniffio" 895 | version = "1.3.0" 896 | description = "Sniff out which async library your code is running under" 897 | optional = false 898 | python-versions = ">=3.7" 899 | files = [ 900 | {file = "sniffio-1.3.0-py3-none-any.whl", hash = "sha256:eecefdce1e5bbfb7ad2eeaabf7c1eeb404d7757c379bd1f7e5cce9d8bf425384"}, 901 | {file = "sniffio-1.3.0.tar.gz", hash = "sha256:e60305c5e5d314f5389259b7f22aaa33d8f7dee49763119234af3755c55b9101"}, 902 | ] 903 | 904 | [[package]] 905 | name = "tabulate" 906 | version = "0.9.0" 907 | description = "Pretty-print tabular data" 908 | optional = false 909 | python-versions = ">=3.7" 910 | files = [ 911 | {file = "tabulate-0.9.0-py3-none-any.whl", hash = "sha256:024ca478df22e9340661486f85298cff5f6dcdba14f3813e8830015b9ed1948f"}, 912 | {file = "tabulate-0.9.0.tar.gz", hash = "sha256:0095b12bf5966de529c0feb1fa08671671b3368eec77d7ef7ab114be2c068b3c"}, 913 | ] 914 | 915 | [package.extras] 916 | widechars = ["wcwidth"] 917 | 918 | [[package]] 919 | name = "tomli" 920 | version = "2.0.1" 921 | description = "A lil' TOML parser" 922 | optional = false 923 | python-versions = ">=3.7" 924 | files = [ 925 | {file = "tomli-2.0.1-py3-none-any.whl", hash = "sha256:939de3e7a6161af0c887ef91b7d41a53e7c5a1ca976325f429cb46ea9bc30ecc"}, 926 | {file = "tomli-2.0.1.tar.gz", hash = "sha256:de526c12914f0c550d15924c62d72abc48d6fe7364aa87328337a31007fe8a4f"}, 927 | ] 928 | 929 | [[package]] 930 | name = "tqdm" 931 | version = "4.66.2" 932 | description = "Fast, Extensible Progress Meter" 933 | optional = false 934 | python-versions = ">=3.7" 935 | files = [ 936 | {file = "tqdm-4.66.2-py3-none-any.whl", hash = "sha256:1ee4f8a893eb9bef51c6e35730cebf234d5d0b6bd112b0271e10ed7c24a02bd9"}, 937 | {file = "tqdm-4.66.2.tar.gz", hash = "sha256:6cd52cdf0fef0e0f543299cfc96fec90d7b8a7e88745f411ec33eb44d5ed3531"}, 938 | ] 939 | 940 | [package.dependencies] 941 | colorama = {version = "*", markers = "platform_system == \"Windows\""} 942 | 943 | [package.extras] 944 | dev = ["pytest (>=6)", "pytest-cov", "pytest-timeout", "pytest-xdist"] 945 | notebook = ["ipywidgets (>=6)"] 946 | slack = ["slack-sdk"] 947 | telegram = ["requests"] 948 | 949 | [[package]] 950 | name = "typing-extensions" 951 | version = "4.9.0" 952 | description = "Backported and Experimental Type Hints for Python 3.8+" 953 | optional = false 954 | python-versions = ">=3.8" 955 | files = [ 956 | {file = "typing_extensions-4.9.0-py3-none-any.whl", hash = "sha256:af72aea155e91adfc61c3ae9e0e342dbc0cba726d6cba4b6c72c1f34e47291cd"}, 957 | {file = "typing_extensions-4.9.0.tar.gz", hash = "sha256:23478f88c37f27d76ac8aee6c905017a143b0b1b886c3c9f66bc2fd94f9f5783"}, 958 | ] 959 | 960 | [[package]] 961 | name = "tzdata" 962 | version = "2024.1" 963 | description = "Provider of IANA time zone data" 964 | optional = false 965 | python-versions = ">=2" 966 | files = [ 967 | {file = "tzdata-2024.1-py2.py3-none-any.whl", hash = "sha256:9068bc196136463f5245e51efda838afa15aaeca9903f49050dfa2679db4d252"}, 968 | {file = "tzdata-2024.1.tar.gz", hash = "sha256:2674120f8d891909751c38abcdfd386ac0a5a1127954fbc332af6b5ceae07efd"}, 969 | ] 970 | 971 | [metadata] 972 | lock-version = "2.0" 973 | python-versions = "^3.10" 974 | content-hash = "ea062591864c08d4c0d532d669c4f7529579afe1a32f4512d6417e51f7a93288" 975 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.poetry] 2 | name = "llmfoo" 3 | version = "0.5.0" 4 | description = "Automatically make the OpenAI tool JSON Schema, parsing call and constructing the result to the chat model." 5 | authors = ["Mikko Korpela "] 6 | readme = "README.md" 7 | packages = [{include = "llmfoo", from = "src"}] 8 | 9 | [tool.poetry.dependencies] 10 | python = "^3.10" 11 | openai = "^1.3.7" 12 | pypdf = "^4.0.2" 13 | camelot-py = "^0.11.0" 14 | opencv-python-headless = "^4.9.0.80" 15 | ghostscript = "^0.7" 16 | 17 | [tool.poetry.group.dev.dependencies] 18 | pytest = "^7.4.3" 19 | 20 | [build-system] 21 | requires = ["poetry-core"] 22 | build-backend = "poetry.core.masonry.api" 23 | -------------------------------------------------------------------------------- /src/llmfoo/__init__.py: -------------------------------------------------------------------------------- 1 | from openai import OpenAI 2 | 3 | 4 | def is_statement_true(statement: str, criteria: str = "statement should be true") -> bool: 5 | client = OpenAI() 6 | instructions = f""" 7 | We have a statement "{statement}". 8 | Based on the following criteria: "{criteria}" is the statement true? 9 | """.rstrip() 10 | response = client.chat.completions.create( 11 | model="gpt-4-1106-preview", 12 | messages=[ 13 | { 14 | "role": "system", 15 | "content": instructions 16 | } 17 | ], 18 | logit_bias={ 19 | "1904": 100, # true 20 | "3934": 100 # false 21 | }, 22 | max_tokens=1, 23 | temperature=0 24 | ) 25 | result = response.choices[0].message.content 26 | return result == "true" 27 | -------------------------------------------------------------------------------- /src/llmfoo/functions.py: -------------------------------------------------------------------------------- 1 | import inspect 2 | import json 3 | import os 4 | from typing import Callable, Dict, Any 5 | 6 | from openai import OpenAI 7 | from openai.types.chat import ChatCompletionMessageToolCall 8 | from openai.types.chat.chat_completion_message_tool_call import Function 9 | 10 | 11 | def tool(func: Callable) -> Callable: 12 | file_path = inspect.getfile(func) 13 | json_file_path = file_path.replace('.py', '.tool.json') 14 | 15 | # Load existing data from .tool.json 16 | if os.path.exists(json_file_path): 17 | with open(json_file_path, 'r') as file: 18 | existing_data = json.load(file) 19 | else: 20 | existing_data = {} 21 | 22 | # If the function's definition does not exist, extract metadata, generate schema, and update the file 23 | if func.__name__ not in existing_data: 24 | # Extract metadata using OpenAI API 25 | function_metadata = extract_metadata_with_openai_api(func) 26 | 27 | # Generate JSON schema from the metadata 28 | json_schema = generate_json_schema(function_metadata) 29 | 30 | # Update the .tool.json file with the new schema 31 | existing_data[func.__name__] = json_schema 32 | with open(json_file_path, 'w') as file: 33 | json.dump(existing_data, file, indent=4) 34 | else: 35 | # Use existing schema from the .tool.json file 36 | json_schema = existing_data[func.__name__] 37 | 38 | # Set definition to func.openai_xxx 39 | func.openai_schema = json_schema 40 | func.openai_tool_call = create_tool_call_handler(func, json_schema) 41 | func.openai_tool_output = create_tool_output_handler(func, json_schema) 42 | 43 | return func 44 | 45 | 46 | def call_function(func: Callable, openai_func: Function) -> str: 47 | kwargs = json.loads(openai_func.arguments) 48 | result = func(**kwargs) 49 | return str(result) 50 | 51 | 52 | def create_tool_output_handler(func: Callable, json_schema): 53 | def handler(msg: ChatCompletionMessageToolCall): 54 | if func.__name__ == msg.function.name: 55 | return { 56 | "tool_call_id": msg.id, 57 | "output": call_function(func, msg.function) 58 | } 59 | return None 60 | 61 | return handler 62 | 63 | 64 | def create_tool_call_handler(func: Callable, json_schema): 65 | def handler(msg: ChatCompletionMessageToolCall): 66 | if func.__name__ == msg.function.name: 67 | return { 68 | "role": "tool", 69 | "tool_call_id": msg.id, 70 | "name": msg.function.name, 71 | "content": call_function(func, msg.function) 72 | } 73 | return None 74 | 75 | return handler 76 | 77 | 78 | def extract_metadata_with_openai_api(func: Callable) -> Dict[str, Any]: 79 | source = inspect.getsource(func) 80 | client = OpenAI() 81 | instructions = f""" 82 | Extract function metadata from the following function definition: 83 | ```python 84 | {source} 85 | ``` 86 | Focus on what the function can be used for in the description. 87 | Explain the parameters from user point of view in their descriptions. 88 | Tool decorator explanation can be omitted. 89 | 90 | Use the following Pydantic style JSON schema in your response: 91 | ```json 92 | {schema} 93 | ``` 94 | """.rstrip() 95 | print(instructions) 96 | response = client.chat.completions.create( 97 | model="gpt-4-1106-preview", 98 | response_format={"type": "json_object"}, 99 | messages=[ 100 | { 101 | "role": "system", 102 | "content": instructions 103 | } 104 | ], 105 | temperature=0 106 | ) 107 | print(repr(response)) 108 | metadata = response.choices[0].message.content 109 | print(metadata) 110 | return json.loads(metadata) # Assuming the response is in JSON format 111 | 112 | 113 | def generate_json_schema(metadata: Dict[str, Any]) -> Dict[str, Any]: 114 | func = metadata["function"] 115 | params = func["parameters"] 116 | props = params["properties"] 117 | return { 118 | "type": "function", 119 | "function": { 120 | "name": func["name"], 121 | "description": func["description"], 122 | "parameters": { 123 | "type": "object", 124 | "properties": {prop: { 125 | "type": props[prop]["type"], 126 | "description": props[prop]["description"] 127 | } for prop in props 128 | }, 129 | "required": params["required"] 130 | }, 131 | } 132 | } 133 | 134 | 135 | schema = """ 136 | { 137 | "type": "function", 138 | "function": { 139 | "name": "", 140 | "description": "", 141 | "parameters": { 142 | "type": "object", 143 | "properties": { 144 | "": { 145 | "type": "", 146 | "description": "" 147 | }, 148 | "": { 149 | "type": "", 150 | "description": "" 151 | } 152 | // Add more parameters as needed 153 | }, 154 | "required": ["", ""] 155 | // List all required parameters 156 | } 157 | } 158 | } 159 | """ 160 | -------------------------------------------------------------------------------- /src/llmfoo/pdf2md.py: -------------------------------------------------------------------------------- 1 | from __future__ import annotations 2 | 3 | import os 4 | import logging 5 | import subprocess 6 | import pypdf 7 | from pathlib import Path 8 | from typing import TypedDict, Callable 9 | import base64 10 | 11 | from openai import OpenAI 12 | from camelot.core import TableList 13 | from dotenv import load_dotenv 14 | import camelot 15 | from pypdf import PageObject 16 | 17 | load_dotenv() 18 | 19 | 20 | class PageData(TypedDict): 21 | content: str 22 | page_image: str 23 | page: int 24 | source: str 25 | 26 | 27 | logging.basicConfig(level=logging.INFO, format="%(asctime)s - %(levelname)s - %(message)s") 28 | 29 | 30 | def clean_workdir(workdir: Path): 31 | if workdir.exists(): 32 | for file in workdir.iterdir(): 33 | if file.is_file(): 34 | file.unlink() 35 | 36 | 37 | def setup_workdir(workdir: Path): 38 | clean_workdir(workdir) 39 | workdir.mkdir(exist_ok=True) 40 | 41 | 42 | def encode_image(image_path: str): 43 | logging.info(f"Attempting to encode image at: {image_path}") 44 | try: 45 | with open(image_path, "rb") as image_file: 46 | encoded = base64.b64encode(image_file.read()).decode("utf-8") 47 | logging.info(f"Image encoded successfully: {image_path}") 48 | return encoded 49 | except FileNotFoundError: 50 | logging.error(f"Image file not found: {image_path}") 51 | return "" 52 | 53 | 54 | def get_page_description_from_openai(base64_image: str, page_content: str, tables_markdown: str) -> str: 55 | api_key = os.getenv("OPENAI_API_KEY") 56 | if not api_key: 57 | raise ValueError("No OPENAI_API_KEY in environment variables!") 58 | instruction = f""" 59 | Convert the text content of this PDF document page into well-structured Markdown format. 60 | Below is the PyPDF extracted text and potential Camelot extracted tables from the page, followed by screenshot 61 | of the same page. Insert detailed, vivid descriptions for each figure directly where mentioned. 62 | Ensure descriptions are comprehensive, allowing readers to visualize and understand without seeing the figure. 63 | Apply Markdown formatting appropriately, using headers, lists, tables, bold and italic emphasis, and links. 64 | At the beginning, add an HTML comment in Markdown to discuss how to best represent the visual elements from 65 | the image of the page, such as tables and figures. Specifically, note details like the number of columns and 66 | rows in a table and information defined in figures. Number of columns are calculated based on data rows. 67 | Here is an example of how the result should look in Markdown format: 68 | 69 | ```markdown" 70 | 74 | 75 | # Quarterly Sales Report 76 | 77 | - Introduction to sales trends 78 | - Analysis of product performance 79 | 80 | **Summary:** 81 | 82 | This quarter showed a significant uptick in sales for both Product A and Product B, reflecting our strategic marketing efforts and expanded distribution channels. The following table breaks down the sales figures: 83 | 84 | **Table: Quarterly Sales Data** 85 | 86 | | Quarter | Product A Sales (Units) | Product B Sales (Units) | 87 | |----------|-------------------------|-------------------------| 88 | | Q1 2021 | 1,500 | 1,200 | 89 | | Q2 2021 | 1,800 | 1,400 | 90 | 91 | **Figure 1: Annual Revenue Trend (2015-2020)** 92 | 93 | A bar graph illustrates a consistent rise in annual revenue from 2015 to 2020. The graph details: 94 | - The x-axis labels each year from 2015 through 2020. 95 | - The y-axis shows revenue in millions, starting at $10 million in 2015 and reaching $25 million by 2020. 96 | - Each bar represents the total revenue for the year, with a noticeable increase year-over-year, highlighting the company's growth and the successful introduction of new products in 2018. 97 | 98 | The steady growth trajectory underscores the effectiveness of our long-term business strategies and the increasing market demand for our products. 99 | ``` 100 | 101 | Here is the text extracted from the page with PyPDF and then the screenshot of the same page: 102 | ``` 103 | {page_content} 104 | ``` 105 | 106 | Here are the markdown formatted tables from the page extracted with Camelot: 107 | ``` 108 | {tables_markdown} 109 | ``` 110 | """.strip() 111 | logging.info("Sending request to OpenAI API for page improvement.") 112 | llm_client = OpenAI() 113 | chat_completion = llm_client.chat.completions.create( 114 | messages=[ 115 | {"role": "system", "content": "You are a helpful assistant in document page image to text processing."}, 116 | {"role": "user", "content": [ 117 | { 118 | "type": "text", 119 | "text": instruction, 120 | }, 121 | { 122 | "type": "image_url", 123 | "image_url": {"url": f"data:image/png;base64,{base64_image}", "detail": "high"}, 124 | }, 125 | ]} 126 | ], 127 | model="gpt-4-vision-preview", 128 | max_tokens=4096, 129 | temperature=1.0, 130 | ) 131 | content = chat_completion.choices[0].message.content 132 | if not content: 133 | raise Exception("Received empty content from OpenAI API.") 134 | logging.info(f"Received response from OpenAI API: {content[:120]}...") 135 | return content 136 | 137 | 138 | def convert_pdf_page_to_png(pdf_path: Path, page_num: int, output_dir: Path) -> str: 139 | output_png_base = f"page_{page_num}" 140 | # Preparing the command without assuming the page number format in the output file name. 141 | command = ["pdftocairo", "-png", "-f", str(page_num), "-l", str(page_num), str(pdf_path), 142 | str(output_dir / output_png_base)] 143 | 144 | logging.info(f"Running command: {' '.join(command)}") 145 | try: 146 | subprocess.run(command, check=True) 147 | except subprocess.CalledProcessError as e: 148 | logging.error(f"Error converting PDF page to PNG: {e}") 149 | raise 150 | 151 | # Attempt to find the generated PNG file without relying on zfill. 152 | # This searches for any file that starts with the base name and ends with .png. 153 | potential_files = list(output_dir.glob(f"{output_png_base}-*.png")) 154 | if not potential_files: 155 | logging.error(f"Expected PNG file not found for page {page_num}") 156 | raise FileNotFoundError(f"Expected PNG file not found for page {page_num}") 157 | 158 | # Assuming pdftocairo generates only one file per page, taking the first match. 159 | output_png_path = potential_files[0] 160 | logging.info(f"Using generated PNG file: {output_png_path}") 161 | return str(output_png_path) 162 | 163 | 164 | def extract_text_from_page(page) -> str: 165 | try: 166 | return page.extract_text() or "" 167 | except Exception as e: 168 | logging.error(f"Error extracting text from page: {e}") 169 | return "" 170 | 171 | 172 | def _use_file_or_create(filename: Path, creator: Callable) -> str: 173 | if not filename.exists(): 174 | content = creator() 175 | with open(filename, "w") as f: 176 | f.write(content) 177 | logging.info(f"File {filename.name} stored.") 178 | else: 179 | logging.info(f"File {filename.name} exists, using content from there.") 180 | with open(filename, "r") as f: 181 | content = f.read() 182 | return content 183 | 184 | 185 | def process_pdf_page(page: PageObject, tables: TableList, page_num: int, pdf_path: Path, pages_dir: Path) -> PageData: 186 | page_image_path = convert_pdf_page_to_png(pdf_path, page_num, pages_dir) 187 | if not page_image_path: 188 | logging.error(f"Error no image! {page_num}") 189 | return {"content": "", "page_image": "", "page": page_num, "source": pdf_path.name} 190 | base64_image = encode_image(page_image_path) 191 | 192 | extract_file = pages_dir / f"page_pypdf_extract_{page_num}.txt" 193 | tables_file = pages_dir / f"page_tables_extract_{page_num}.txt" 194 | page_description_file = pages_dir / f"page_description_{page_num}.txt" 195 | 196 | page_content = _use_file_or_create(extract_file, lambda: extract_text_from_page(page)) 197 | tables_markdown = _use_file_or_create(tables_file, lambda: "\n\n".join(table.df.to_markdown() for table in tables)) 198 | page_description = _use_file_or_create(page_description_file, 199 | lambda: get_page_description_from_openai(base64_image, page_content, 200 | tables_markdown)) 201 | 202 | logging.info(f"Page data from page {page_num} ready") 203 | return { 204 | "content": page_description, 205 | "page_image": page_image_path, 206 | "page": page_num, 207 | "source": pdf_path.name 208 | } 209 | 210 | 211 | def process_pdf(pdf_path: Path, output_dir: Path) -> Path | None: 212 | try: 213 | pdf_reader = pypdf.PdfReader(str(pdf_path)) 214 | except Exception as e: 215 | logging.error(f"Error reading PDF {pdf_path.name}: {e}") 216 | return None 217 | 218 | pages_dir = output_dir / f"{pdf_path.stem}_pages" 219 | pages_dir.mkdir(exist_ok=True) 220 | 221 | pages = [process_pdf_page(page, camelot.read_pdf(str(pdf_path), pages=str(i)), i, pdf_path, pages_dir) for i, page 222 | in enumerate(pdf_reader.pages, start=1)] 223 | 224 | md_file_path = output_dir / f"{pdf_path.stem}.md" 225 | with open(md_file_path, "w") as f: 226 | for page in pages: 227 | f.write(format_content(page)) 228 | return md_file_path 229 | 230 | 231 | def format_content(item: PageData) -> str: 232 | # Strip the markdown code block delimiters 233 | content = item['content'] 234 | if content.startswith("```markdown"): 235 | # Removing the markdown code block delimiters 236 | content = content.strip("```markdown") 237 | parts = content.rsplit("```", 1) 238 | content = parts[0].strip() if len(parts) > 1 else content.strip() 239 | 240 | # Add the page number as a Markdown footer 241 | page_footer = f"\n\n---\n_Page {item['page']}_\n" 242 | return content + page_footer 243 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robocorp/llmfoo/6b981b9e8fbc1f1b9f8617381c589293efbee991/tests/__init__.py -------------------------------------------------------------------------------- /tests/data/pdfs/LoremIpsum.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robocorp/llmfoo/6b981b9e8fbc1f1b9f8617381c589293efbee991/tests/data/pdfs/LoremIpsum.pdf -------------------------------------------------------------------------------- /tests/data/pdfs/alianz.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robocorp/llmfoo/6b981b9e8fbc1f1b9f8617381c589293efbee991/tests/data/pdfs/alianz.pdf -------------------------------------------------------------------------------- /tests/data/pdfs/camelot-table.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robocorp/llmfoo/6b981b9e8fbc1f1b9f8617381c589293efbee991/tests/data/pdfs/camelot-table.pdf -------------------------------------------------------------------------------- /tests/data/pdfs/encrypted.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robocorp/llmfoo/6b981b9e8fbc1f1b9f8617381c589293efbee991/tests/data/pdfs/encrypted.pdf -------------------------------------------------------------------------------- /tests/data/pdfs/foersom.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robocorp/llmfoo/6b981b9e8fbc1f1b9f8617381c589293efbee991/tests/data/pdfs/foersom.pdf -------------------------------------------------------------------------------- /tests/data/pdfs/generated.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robocorp/llmfoo/6b981b9e8fbc1f1b9f8617381c589293efbee991/tests/data/pdfs/generated.pdf -------------------------------------------------------------------------------- /tests/data/pdfs/imagesandtext.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robocorp/llmfoo/6b981b9e8fbc1f1b9f8617381c589293efbee991/tests/data/pdfs/imagesandtext.pdf -------------------------------------------------------------------------------- /tests/data/pdfs/instruction-manual-fisher-es-eas-easy-e-valves-cl125-through-cl600-en-124780.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robocorp/llmfoo/6b981b9e8fbc1f1b9f8617381c589293efbee991/tests/data/pdfs/instruction-manual-fisher-es-eas-easy-e-valves-cl125-through-cl600-en-124780.pdf -------------------------------------------------------------------------------- /tests/data/pdfs/invoice.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robocorp/llmfoo/6b981b9e8fbc1f1b9f8617381c589293efbee991/tests/data/pdfs/invoice.pdf -------------------------------------------------------------------------------- /tests/data/pdfs/prospecting_scatter_chart.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robocorp/llmfoo/6b981b9e8fbc1f1b9f8617381c589293efbee991/tests/data/pdfs/prospecting_scatter_chart.pdf -------------------------------------------------------------------------------- /tests/data/pdfs/receipt.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robocorp/llmfoo/6b981b9e8fbc1f1b9f8617381c589293efbee991/tests/data/pdfs/receipt.pdf -------------------------------------------------------------------------------- /tests/data/pdfs/sparebin-receipt.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robocorp/llmfoo/6b981b9e8fbc1f1b9f8617381c589293efbee991/tests/data/pdfs/sparebin-receipt.pdf -------------------------------------------------------------------------------- /tests/data/pdfs/vero.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/robocorp/llmfoo/6b981b9e8fbc1f1b9f8617381c589293efbee991/tests/data/pdfs/vero.pdf -------------------------------------------------------------------------------- /tests/test_bool.py: -------------------------------------------------------------------------------- 1 | from llmfoo import is_statement_true 2 | 3 | 4 | def test_is_statement_true_with_default_criteria(): 5 | assert is_statement_true("Earth is a planet.") 6 | assert not is_statement_true("1 + 2 = 5") 7 | 8 | 9 | def test_is_statement_true_with_own_criteria(): 10 | assert not is_statement_true("Temperature outside is -2 degrees celsius", 11 | criteria="Temperature above 0 degrees celsius") 12 | assert is_statement_true("1984 was written by George Orwell", 13 | criteria="George Orwell is the author of 1984") 14 | 15 | 16 | def test_is_statement_true_criteria_can_change_truth_value(): 17 | assert is_statement_true("Earth is 3rd planet from the Sun") 18 | assert not is_statement_true("Earth is 3rd planet from the Sun", 19 | criteria="Earth is stated to be 5th planet from the Sun") 20 | -------------------------------------------------------------------------------- /tests/test_pdf2md.py: -------------------------------------------------------------------------------- 1 | import logging 2 | from pathlib import Path 3 | from llmfoo.pdf2md import process_pdf 4 | 5 | 6 | def test_pdf_to_markdown_conversions(): 7 | pdf_dir = Path(__file__).parent / "data" / "pdfs" 8 | output_dir = Path(__file__).parent / "data" / "output" 9 | 10 | skip_files = ["instruction-manual-fisher-es-eas-easy-e-valves-cl125-through-cl600-en-124780.pdf"] 11 | 12 | for pdf_file in pdf_dir.glob("*.pdf"): 13 | if pdf_file.name not in skip_files: 14 | logging.info(f"Processing {pdf_file.name}...") 15 | try: 16 | process_pdf(pdf_file, output_dir) 17 | except Exception as e: 18 | logging.error(f"Failed to process {pdf_file.name}: {e}") 19 | 20 | 21 | def test_pdf_to_markdown_conversion_huge(): 22 | pdf_file = "instruction-manual-fisher-es-eas-easy-e-valves-cl125-through-cl600-en-124780.pdf" 23 | pdf_path = Path(__file__).parent / "data" / "pdfs" / pdf_file 24 | output_dir = Path(__file__).parent / "data" / "output" 25 | 26 | # Run the conversion process 27 | filename = process_pdf(pdf_path, output_dir) 28 | 29 | # Validate the output 30 | assert filename.exists(), "Markdown file was not created." 31 | 32 | with open(filename, "r", encoding="utf-8") as output_file: 33 | output_md = output_file.read() 34 | 35 | assert output_md == "something", "The output Markdown does not match the expected Markdown." 36 | -------------------------------------------------------------------------------- /tests/test_tool.py: -------------------------------------------------------------------------------- 1 | from time import sleep 2 | 3 | from openai import OpenAI 4 | 5 | from llmfoo.functions import tool 6 | 7 | 8 | @tool 9 | def adder(x: int, y: int) -> int: 10 | return x + y 11 | 12 | 13 | @tool 14 | def multiplier(x: int, y: int) -> int: 15 | return x * y 16 | 17 | 18 | @tool 19 | def complicated(foo: str, bar: str = "Steve") -> str: 20 | """ Funny joke function with no real use """ 21 | return f"FOO BAR? {foo} {bar}" 22 | 23 | 24 | client = OpenAI() 25 | 26 | 27 | def test_chat_completion_with_adder(): 28 | number1 = 3267182746 29 | number2 = 798472847 30 | messages = [ 31 | { 32 | "role": "user", 33 | "content": f"What is {number1} + {number2}?" 34 | } 35 | ] 36 | response = client.chat.completions.create( 37 | model="gpt-4-1106-preview", 38 | messages=messages, 39 | tools=[adder.openai_schema] 40 | ) 41 | messages.append(response.choices[0].message) 42 | messages.append(adder.openai_tool_call(response.choices[0].message.tool_calls[0])) 43 | response2 = client.chat.completions.create( 44 | model="gpt-4-1106-preview", 45 | messages=messages, 46 | tools=[adder.openai_schema] 47 | ) 48 | assert str(adder(number1, number2)) in response2.choices[0].message.content.replace(",", "") 49 | 50 | 51 | def test_assistant_with_multiplier(): 52 | number1 = 1238763428176 53 | number2 = 172388743612 54 | assistant = client.beta.assistants.create( 55 | name="The Calc Machina", 56 | instructions="You are a calculator with a funny pirate accent.", 57 | tools=[multiplier.openai_schema], 58 | model="gpt-4-1106-preview" 59 | ) 60 | thread = client.beta.threads.create(messages=[ 61 | { 62 | "role":"user", 63 | "content":f"What is {number1} * {number2}?" 64 | } 65 | ]) 66 | run = client.beta.threads.runs.create( 67 | thread_id=thread.id, 68 | assistant_id=assistant.id 69 | ) 70 | while True: 71 | run_state = client.beta.threads.runs.retrieve( 72 | run_id=run.id, 73 | thread_id=thread.id, 74 | ) 75 | if run_state.status not in ['in_progress', 'requires_action']: 76 | break 77 | if run_state.status == 'requires_action': 78 | tool_call = run_state.required_action.submit_tool_outputs.tool_calls[0] 79 | run = client.beta.threads.runs.submit_tool_outputs( 80 | thread_id=thread.id, 81 | run_id=run.id, 82 | tool_outputs=[ 83 | multiplier.openai_tool_output(tool_call) 84 | ] 85 | ) 86 | sleep(1) 87 | sleep(0.1) 88 | messages = client.beta.threads.messages.list(thread_id=thread.id) 89 | assert str(multiplier(number1, number2)) in messages.data[0].content[0].text.value.replace(",", "") 90 | --------------------------------------------------------------------------------