From 40838dd084115d76cee88ff538d006134223d3bd Mon Sep 17 00:00:00 2001 From: Nightshade14 Date: Sun, 10 Nov 2024 03:51:07 -0500 Subject: [PATCH] fix: hw1 feedback fix - Upgrade python from 3.11 to 3.12 - Use uv to transform the project from app to a library, exposing api only via modules (modules named with "_" to discourage direct use and import) - Add mypy for static type checking - Add pytest-cov for test coverage and reporting - Add circleCI tests and coverage report - Untracked artifact files - The project is capable of being built with build-tools (maigic uses hatchling) and probably being published to PyPi --- .circleci/config.yml | 30 ++++-- .gitignore | 173 ++++++++++++++++++++++++++++++- .python-version | 1 - LICENSE | 12 ++- README.md | 15 --- SlackChatbot/.DS_Store | Bin 6148 -> 0 bytes SlackChatbot/ClientID_Secret.env | 3 - SlackChatbot/Commands | 3 - SlackChatbot/slack_bot.py | 35 ------- SlackChatbot/slack_events.py | 21 ---- SlackChatbot/slack_oauth.py | 48 --------- pyproject.toml | 15 ++- src/TrelloManager.py | 98 ----------------- src/hello.py | 16 --- src/maigic_nyu/__init__.py | 1 + src/maigic_nyu/_demo_math.py | 11 ++ src/maigic_nyu/api.py | 4 + tests/test_api.py | 23 ++++ tests/test_hello.py | 19 ---- uv.lock | 141 ------------------------- 20 files changed, 255 insertions(+), 414 deletions(-) delete mode 100644 .python-version delete mode 100644 SlackChatbot/.DS_Store delete mode 100644 SlackChatbot/ClientID_Secret.env delete mode 100644 SlackChatbot/Commands delete mode 100644 SlackChatbot/slack_bot.py delete mode 100644 SlackChatbot/slack_events.py delete mode 100644 SlackChatbot/slack_oauth.py delete mode 100644 src/TrelloManager.py delete mode 100644 src/hello.py create mode 100644 src/maigic_nyu/__init__.py create mode 100644 src/maigic_nyu/_demo_math.py create mode 100644 src/maigic_nyu/api.py create mode 100644 tests/test_api.py delete mode 100644 tests/test_hello.py delete mode 100644 uv.lock diff --git a/.circleci/config.yml b/.circleci/config.yml index 687abed..ac10a3b 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -3,7 +3,7 @@ version: 2.1 executors: my-docker-executor: docker: - - image: cimg/python:3.11.10 + - image: cimg/python:3.12 jobs: build: @@ -19,7 +19,7 @@ jobs: - persist_to_workspace: root: /home/circleci/ paths: - - .cargo + - .local test: executor: my-docker-executor @@ -31,19 +31,35 @@ jobs: - run: name: Set-up dependencies path - command: echo 'export PATH=$HOME/.cargo/bin:$PATH' >> $BASH_ENV + command: echo 'export PATH=$HOME/.local/bin:$PATH' >> $BASH_ENV - run: name: Set-up project dependencies command: uv sync - run: - name: Format python files with ruff - command: uv run ruff check . --fix + name: Create test results directory + command: mkdir -p test-results - run: - name: Run test files - command: uv run pytest + name: Static Type Checking with mypy + command: uv run mypy . + + - run: + name: Linting checks with Ruff + # ignore = ["S101", "D203", "D213", "COM812", "ISC001"] + command: uv run ruff check . + + - run: + name: Run unit tests with coverage + command: uv run pytest tests/ --junitxml=test-results/coverage.xml + + - store_test_results: + path: test-results + + - store_artifacts: + path: test-results/coverage.xml + destination: coverage-report workflows: build_and_test: diff --git a/.gitignore b/.gitignore index b5f7c6a..7fa6b33 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,172 @@ -**/**cache**/** \ No newline at end of file +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ +.coverage +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/latest/usage/project/#working-with-version-control +.pdm.toml +.pdm-python +.pdm-build/ + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +#.idea/ + +**/**cache** + +**/.python-version + +**/uv.lock + +**/py.typed + +**/**.xml \ No newline at end of file diff --git a/.python-version b/.python-version deleted file mode 100644 index 2c07333..0000000 --- a/.python-version +++ /dev/null @@ -1 +0,0 @@ -3.11 diff --git a/LICENSE b/LICENSE index d2a6fc3..482512d 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,16 @@ MIT License -Copyright (c) 2024 Satyam Chatrola +Copyright (c) 2024 NYU-OSS +Contributors: + +- Kamen Yotov +- Satyam Chatrola +- Frank Zhao +- Sidhved Warik +- Yaxin Ke +- Eli Edme +- Abdullah Suri +- Mihir Lovekar Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index bc43301..2cbf06d 100644 --- a/README.md +++ b/README.md @@ -51,21 +51,6 @@ Note: One can run tools like ruff and pytest independently or can run them throu Note: Request to keep all `communication` in the Google Chats Project Group. -## Trello Setup - -- Create a Trello account. -- Go to https://trello.com/power-ups/admin to create a new Power-up. -- Check the new power-up, click `API key` on the left side panel. -- Copy the API key and visit https://trello.com/1/authorize?expiration=1day&name=yourAppName&scope=read,write&response_type=token&key=your_api_key, replace `your_api_key` with your own api key. The expiration and the name can be changed if needed. -- Create a .env file, input your API key and token: - -``` -TRELLO_API_KEY=your_api_key -TRELLO_OAUTH_TOKEN=your_token -``` - -- Run TrelloManager.py and you will see the test result. - # License mAIgic has a MIT-style license, as found in the [LICENSE](LICENSE) file. diff --git a/SlackChatbot/.DS_Store b/SlackChatbot/.DS_Store deleted file mode 100644 index 1ab21021321b1ed227a21616641c27701e30eaca..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 6148 zcmeHK%}T>S5Z-O0O({YS3Oz1(E!ftdDqcdYFJMFuDm5VigE3p0wmFnS&iX<=iO=KA z?zULWSwv@G_M6$6Nj4v3cZV^?y;;;_%wde#pokoe3c=~hP|YNBKv0K;MLrHw8G`?A zV!s`5?G~G}m}PAKb^YNuP4a2C`@w5aF+dFbF$TCb z^oJgNlsQ|!eIA~*0`w6S1@lTYK)~L(1b~D4NLvMUT%ZkkuEA0xJ_YAhIUrpG6d^Pa I13$pPCmKLSX8-^I diff --git a/SlackChatbot/ClientID_Secret.env b/SlackChatbot/ClientID_Secret.env deleted file mode 100644 index 4393f97..0000000 --- a/SlackChatbot/ClientID_Secret.env +++ /dev/null @@ -1,3 +0,0 @@ -CLIENT_ID = '7855184757495.7892528086800' -CLIENT_SECRET = '918fec90decd47b23d25fe58e02a73e1' -BOT_TOKEN='xoxb-7855184757495-7855216149575-kPNmwbhC4mlAyVyUT7QXWpkJ' \ No newline at end of file diff --git a/SlackChatbot/Commands b/SlackChatbot/Commands deleted file mode 100644 index 4bd3541..0000000 --- a/SlackChatbot/Commands +++ /dev/null @@ -1,3 +0,0 @@ -Activate virtual env : source venv/bin/activate -Start server : python3 slack_oauth.py -Check if server is running by going to this URL http://127.0.0.1:3000/ diff --git a/SlackChatbot/slack_bot.py b/SlackChatbot/slack_bot.py deleted file mode 100644 index 69289fd..0000000 --- a/SlackChatbot/slack_bot.py +++ /dev/null @@ -1,35 +0,0 @@ -from flask import Flask, request, jsonify -from slack_sdk import WebClient -from slack_sdk.errors import SlackApiError - -client = WebClient(token='xoxb-7855184757495-7855216149575-kPNmwbhC4mlAyVyUT7QXWpkJ') - -def send_message(channel, message): - try: - response = client.chat_postMessage(channel=channel, text=message) - print("Message sent successfully:", response["message"]["text"]) - except SlackApiError as e: - print("Error sending message:", e.response["error"]) - -app = Flask(__name__) - -@app.route('/slack/events', methods=['POST']) -def slack_events(): - data = request.json - if 'challenge' in data: - return jsonify({'challenge': data['challenge']}) - elif 'event' in data: - event = data['event'] - if event['type'] == 'message' and 'subtype' not in event: - # Check the message text and respond appropriately - user_message = event['text'].lower() - if "hello" in user_message: - send_message(event['channel'], "Hello there! How can I help you?") - elif "help" in user_message: - send_message(event['channel'], "Here's a list of commands you can use...") - else: - send_message(event['channel'], "I'm not sure how to respond to that.") - return jsonify({'status': 'ok'}) - -if __name__ == '__main__': - app.run(port=3000, debug=True) diff --git a/SlackChatbot/slack_events.py b/SlackChatbot/slack_events.py deleted file mode 100644 index 794d9ed..0000000 --- a/SlackChatbot/slack_events.py +++ /dev/null @@ -1,21 +0,0 @@ -from flask import Flask, request, jsonify - -app = Flask(__name__) - -@app.route('/slack/events', methods=['POST']) -def slack_events(): - data = request.json - # Slack sends a challenge parameter when verifying the request URL - if 'challenge' in data: - return jsonify({'challenge': data['challenge']}) - # Your logic for other events - elif 'event' in data: - event = data['event'] - if event['type'] == 'message' and 'subtype' not in event: - # Logic to handle incoming messages - # Respond to messages or other interactions here - pass - return jsonify({'status': 'ok'}) - -if __name__ == '__main__': - app.run(port=3000, debug=True) diff --git a/SlackChatbot/slack_oauth.py b/SlackChatbot/slack_oauth.py deleted file mode 100644 index d8b06f8..0000000 --- a/SlackChatbot/slack_oauth.py +++ /dev/null @@ -1,48 +0,0 @@ -from flask import Flask, request, redirect, url_for -import requests -import os - -app = Flask(__name__) - -# Load credentials from environment variables -CLIENT_ID = '7855184757495.7892528086800' -CLIENT_SECRET = '918fec90decd47b23d25fe58e02a73e1' - -@app.route('/') -def home(): - return "Welcome to the Slack OAuth Handler! Ready to authenticate." - -@app.route('/oauth/callback') -def oauth_callback(): - # Retrieve the code from the query string - code = request.args.get('code') - if not code: - return 'Error: Missing authorization code.', 400 - - # Prepare the request data for exchanging the code for a token - data = { - 'client_id': CLIENT_ID, - 'client_secret': CLIENT_SECRET, - 'code': code - } - - # Make a POST request to Slack's OAuth API to exchange the code for an access token - response = requests.post('https://slack.com/api/oauth.v2.access', data=data) - - # Check if the request was successful - response_json = response.json() - if response.status_code == 200 and response_json.get('ok'): - # Extract the token from the response - token = response_json.get('access_token') - # Log or store the token securely (e.g., in a database) - return redirect(url_for('success')) - else: - error_message = response_json.get('error', 'Unknown error') - return f"Failed to retrieve access token: {error_message}", 400 - -@app.route('/success') -def success(): - return "OAuth flow completed successfully!" - -if __name__ == '__main__': - app.run(port=3000, debug=True) diff --git a/pyproject.toml b/pyproject.toml index d847649..37b2f31 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,14 +1,19 @@ [project] -name = "maigic" +name = "maigic-nyu" version = "0.1.0" description = "Add your description here" readme = "README.md" -requires-python = ">=3.11" +requires-python = ">=3.12" dependencies = [ - "numpy>=2.1.1", + "mypy>=1.13.0", "pytest>=8.3.3", - "ruff>=0.6.5", + "ruff>=0.7.3", ] + +[build-system] +requires = ["hatchling"] +build-backend = "hatchling.build" + [tool.ruff.lint] select = ["ALL"] -extend-ignore = ["S101", "D211", "D213"] +ignore = ["S101", "D203", "D213", "COM812", "ISC001"] \ No newline at end of file diff --git a/src/TrelloManager.py b/src/TrelloManager.py deleted file mode 100644 index 2ea2223..0000000 --- a/src/TrelloManager.py +++ /dev/null @@ -1,98 +0,0 @@ -"""TrelloManager module interacts with the Trello API to create cards.""" -import logging -import os - -import requests -from dotenv import load_dotenv - -load_dotenv() -# Retrieve API key and token from environment variables -api_key = os.getenv("TRELLO_API_KEY") -token = os.getenv("TRELLO_OAUTH_TOKEN") - -BASE_URL = "https://api.trello.com/1" - -logging.basicConfig(level=logging.INFO) -logger = logging.getLogger(__name__) - -HTTP_OK = 200 -def get_full_board_info(short_board_id: str) -> str: - """Get full board information from Trello. - - Args: - short_board_id (str): The short ID of the Trello board. - - Returns: - str: The full board ID. - - """ - url = f"{BASE_URL}/boards/{short_board_id}" - query = { - "key": api_key, - "token": token, - } - response = requests.get(url, params=query, timeout=10) - - if response.status_code == HTTP_OK: - board_info = response.json() - return board_info["id"] - return "" - -def get_lists(board_id: str) -> list: - """Get all lists in the specified Trello board. - - Args: - board_id (str): The full ID of the Trello board. - - Returns: - list: A list of list IDs. - - """ - url = f"{BASE_URL}/boards/{board_id}/lists" - query = { - "key": api_key, - "token": token, - } - response = requests.get(url, params=query, timeout=10) - - list_id = [] - if response.status_code == HTTP_OK: - lists = response.json() - list_id = [lst["id"] for lst in lists] - - return list_id - -def create_card(list_id: str, name: str, desc: str) -> None: - """Create a new card in the specified list. - - Args: - list_id (str): The ID of the list where the card will be created. - name (str): The name of the card. - desc (str): The description of the card (optional). - - """ - url = f"{BASE_URL}/cards" - query = { - "key": api_key, - "token": token, - "idList": list_id, - "name": name, - "desc": desc, - } - response = requests.post(url, params=query, timeout=10) - - if response.status_code == HTTP_OK: - logger.info("Successfully created!") - - -if __name__ == "__main__": - # go to your target workspace and board, you will see a short id in your url - # example: https://trello.com/b/{short_board_id}/{board_name} - short_board_id = "your_short_board_id" - full_board_id = get_full_board_info(short_board_id) - # to simply test, I just chose the first list to add a new card - if full_board_id: - list_ids = get_lists(full_board_id) - if list_ids: - create_card(list_ids[0], "new to-do", - "This is a new task created by the bot") \ No newline at end of file diff --git a/src/hello.py b/src/hello.py deleted file mode 100644 index 267de61..0000000 --- a/src/hello.py +++ /dev/null @@ -1,16 +0,0 @@ -"""Dummy file to check ruff, pytest and automatic tests with CircleCI.""" - -def addition(num1:int, num2:int) -> int: - """Add 2 numebrs and return their sum.""" - return num1 + num2 - - -def main() -> int: - """Contain the main execution logic.""" - n1 = 50 - n2 = 49 - return addition(n1, n2) - - -if __name__ == "__main__": - main() diff --git a/src/maigic_nyu/__init__.py b/src/maigic_nyu/__init__.py new file mode 100644 index 0000000..2926e00 --- /dev/null +++ b/src/maigic_nyu/__init__.py @@ -0,0 +1 @@ +"""Make files discoverable.""" diff --git a/src/maigic_nyu/_demo_math.py b/src/maigic_nyu/_demo_math.py new file mode 100644 index 0000000..4bfdd55 --- /dev/null +++ b/src/maigic_nyu/_demo_math.py @@ -0,0 +1,11 @@ +"""Dummy file to check ruff, pytest and automatic tests with CircleCI.""" + + +def addition(num1: int, num2: int) -> int: + """Add 2 numebrs and return their sum.""" + return num1 + num2 + + +def substraction(num1: int, num2: int) -> int: + """Substract num2 from num1 i.e. num1 -num2.""" + return num1 - num2 diff --git a/src/maigic_nyu/api.py b/src/maigic_nyu/api.py new file mode 100644 index 0000000..67ecdcb --- /dev/null +++ b/src/maigic_nyu/api.py @@ -0,0 +1,4 @@ +"""Maigic API, exposed to the user.""" + +from maigic_nyu._demo_math import addition as addition # noqa: PLC0414 +from maigic_nyu._demo_math import substraction as substraction # noqa: PLC0414 diff --git a/tests/test_api.py b/tests/test_api.py new file mode 100644 index 0000000..6b1cf5e --- /dev/null +++ b/tests/test_api.py @@ -0,0 +1,23 @@ +"""File tests hello.py file.""" + +from maigic_nyu.api import addition, substraction + + +def test_addition() -> None: + """Test the addition function.""" + test_7 = 7 + test_0 = 0 + test__2 = -2 + assert addition(3, 4) == test_7 + assert addition(-1, 1) == test_0 + assert addition(-1, -1) == test__2 + + +def test_substraction() -> None: + """Test the addition function.""" + test_7 = 7 + test_0 = 0 + test_negative_2 = -2 + assert substraction(10, 3) == test_7 + assert substraction(-1, -1) == test_0 + assert substraction(-1, 1) == test_negative_2 diff --git a/tests/test_hello.py b/tests/test_hello.py deleted file mode 100644 index 628323a..0000000 --- a/tests/test_hello.py +++ /dev/null @@ -1,19 +0,0 @@ -"""File tests hello.py file.""" - -from src.hello import addition, main - - -def test_addition() -> None: - """Test the addition function.""" - test_7 = 7 - test_0 = 0 - test__2 = -2 - assert addition(3, 4) == test_7 - assert addition(-1, 1) == test_0 - assert addition(-1, -1) == test__2 - - -def test_main() -> None: - """Test the main function's behavior.""" - test_99 = 99 - assert main() == test_99 # 50 + 49 = 99 diff --git a/uv.lock b/uv.lock deleted file mode 100644 index e56983f..0000000 --- a/uv.lock +++ /dev/null @@ -1,141 +0,0 @@ -version = 1 -requires-python = ">=3.11" - -[[package]] -name = "colorama" -version = "0.4.6" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 }, -] - -[[package]] -name = "iniconfig" -version = "2.0.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/d7/4b/cbd8e699e64a6f16ca3a8220661b5f83792b3017d0f79807cb8708d33913/iniconfig-2.0.0.tar.gz", hash = "sha256:2d91e135bf72d31a410b17c16da610a82cb55f6b0477d1a902134b24a455b8b3", size = 4646 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/ef/a6/62565a6e1cf69e10f5727360368e451d4b7f58beeac6173dc9db836a5b46/iniconfig-2.0.0-py3-none-any.whl", hash = "sha256:b6a85871a79d2e3b22d2d1b94ac2824226a63c6b741c88f7ae975f18b6778374", size = 5892 }, -] - -[[package]] -name = "maigic" -version = "0.1.0" -source = { virtual = "." } -dependencies = [ - { name = "numpy" }, - { name = "pytest" }, - { name = "ruff" }, -] - -[package.metadata] -requires-dist = [ - { name = "numpy", specifier = ">=2.1.1" }, - { name = "pytest", specifier = ">=8.3.3" }, - { name = "ruff", specifier = ">=0.6.5" }, -] - -[[package]] -name = "numpy" -version = "2.1.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/59/5f/9003bb3e632f2b58f5e3a3378902dcc73c5518070736c6740fe52454e8e1/numpy-2.1.1.tar.gz", hash = "sha256:d0cf7d55b1051387807405b3898efafa862997b4cba8aa5dbe657be794afeafd", size = 18874860 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/f7/86/2c01070424a42b286ea0271203682c3d3e81e10ce695545b35768307b383/numpy-2.1.1-cp311-cp311-macosx_10_9_x86_64.whl", hash = "sha256:0d07841fd284718feffe7dd17a63a2e6c78679b2d386d3e82f44f0108c905550", size = 21154850 }, - { url = "https://files.pythonhosted.org/packages/ef/4e/d3426d9e620a18bbb979f28e4dc7f9a2c35eb7cf726ffcb33545ebdd3e6a/numpy-2.1.1-cp311-cp311-macosx_11_0_arm64.whl", hash = "sha256:b5613cfeb1adfe791e8e681128f5f49f22f3fcaa942255a6124d58ca59d9528f", size = 13789477 }, - { url = "https://files.pythonhosted.org/packages/c6/6e/fb6b1b2da9f4c757f55b202f10b6af0fe4fee87ace6e830228a12ab8ae5d/numpy-2.1.1-cp311-cp311-macosx_14_0_arm64.whl", hash = "sha256:0b8cc2715a84b7c3b161f9ebbd942740aaed913584cae9cdc7f8ad5ad41943d0", size = 5351769 }, - { url = "https://files.pythonhosted.org/packages/58/9a/07c8a9dc7254f3265ae014e33768d1cfd8eb73ee6cf215f4ec3b497e4255/numpy-2.1.1-cp311-cp311-macosx_14_0_x86_64.whl", hash = "sha256:b49742cdb85f1f81e4dc1b39dcf328244f4d8d1ded95dea725b316bd2cf18c95", size = 6890872 }, - { url = "https://files.pythonhosted.org/packages/08/4e/3b50fa3b1e045793056ed5a1fc6f89dd897ff9cb00900ca6377fe552d442/numpy-2.1.1-cp311-cp311-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8d5f8a8e3bc87334f025194c6193e408903d21ebaeb10952264943a985066ca", size = 13984256 }, - { url = "https://files.pythonhosted.org/packages/d9/37/108d692f7e2544b9ae972c7bfa06c26717871c273ccec86470bc3132b04d/numpy-2.1.1-cp311-cp311-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d51fc141ddbe3f919e91a096ec739f49d686df8af254b2053ba21a910ae518bf", size = 16337778 }, - { url = "https://files.pythonhosted.org/packages/95/2d/df81a1be3be6d3a92fd12dfd6c26a0dc026b276136ec1056562342a484a2/numpy-2.1.1-cp311-cp311-musllinux_1_1_x86_64.whl", hash = "sha256:98ce7fb5b8063cfdd86596b9c762bf2b5e35a2cdd7e967494ab78a1fa7f8b86e", size = 16710448 }, - { url = "https://files.pythonhosted.org/packages/8f/34/4b2e604c5c44bd64b6c85e89d88871b41e60233b3ddf97419b37ae5b0c72/numpy-2.1.1-cp311-cp311-musllinux_1_2_aarch64.whl", hash = "sha256:24c2ad697bd8593887b019817ddd9974a7f429c14a5469d7fad413f28340a6d2", size = 14489002 }, - { url = "https://files.pythonhosted.org/packages/9f/0d/67c04b6bfefd0abbe7f60f7e4f11e3aca15d688faec1d1df089966105a9a/numpy-2.1.1-cp311-cp311-win32.whl", hash = "sha256:397bc5ce62d3fb73f304bec332171535c187e0643e176a6e9421a6e3eacef06d", size = 6533215 }, - { url = "https://files.pythonhosted.org/packages/94/7a/4c00332a3ca79702bbc86228afd0e84e6f91b47222ec8cdf00677dd16481/numpy-2.1.1-cp311-cp311-win_amd64.whl", hash = "sha256:ae8ce252404cdd4de56dcfce8b11eac3c594a9c16c231d081fb705cf23bd4d9e", size = 12870550 }, - { url = "https://files.pythonhosted.org/packages/36/11/c573ef66c004f991989c2c6218229d9003164525549409aec5ec9afc0285/numpy-2.1.1-cp312-cp312-macosx_10_9_x86_64.whl", hash = "sha256:7c803b7934a7f59563db459292e6aa078bb38b7ab1446ca38dd138646a38203e", size = 20884403 }, - { url = "https://files.pythonhosted.org/packages/6b/6c/a9fbef5fd2f9685212af2a9e47485cde9357c3e303e079ccf85127516f2d/numpy-2.1.1-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:6435c48250c12f001920f0751fe50c0348f5f240852cfddc5e2f97e007544cbe", size = 13493375 }, - { url = "https://files.pythonhosted.org/packages/34/f2/1316a6b08ad4c161d793abe81ff7181e9ae2e357a5b06352a383b9f8e800/numpy-2.1.1-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:3269c9eb8745e8d975980b3a7411a98976824e1fdef11f0aacf76147f662b15f", size = 5088823 }, - { url = "https://files.pythonhosted.org/packages/be/15/fabf78a6d4a10c250e87daf1cd901af05e71501380532ac508879cc46a7e/numpy-2.1.1-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:fac6e277a41163d27dfab5f4ec1f7a83fac94e170665a4a50191b545721c6521", size = 6619825 }, - { url = "https://files.pythonhosted.org/packages/9f/8a/76ddef3e621541ddd6984bc24d256a4e3422d036790cbbe449e6cad439ee/numpy-2.1.1-cp312-cp312-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:fcd8f556cdc8cfe35e70efb92463082b7f43dd7e547eb071ffc36abc0ca4699b", size = 13696705 }, - { url = "https://files.pythonhosted.org/packages/cb/22/2b840d297183916a95847c11f82ae11e248fa98113490b2357f774651e1d/numpy-2.1.1-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:d2b9cd92c8f8e7b313b80e93cedc12c0112088541dcedd9197b5dee3738c1201", size = 16041649 }, - { url = "https://files.pythonhosted.org/packages/c7/e8/6f4825d8f576cfd5e4d6515b9eec22bd618868bdafc8a8c08b446dcb65f0/numpy-2.1.1-cp312-cp312-musllinux_1_1_x86_64.whl", hash = "sha256:afd9c680df4de71cd58582b51e88a61feed4abcc7530bcd3d48483f20fc76f2a", size = 16409358 }, - { url = "https://files.pythonhosted.org/packages/bf/f8/5edf1105b0dc24fd66fc3e9e7f3bca3d920cde571caaa4375ec1566073c3/numpy-2.1.1-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:8661c94e3aad18e1ea17a11f60f843a4933ccaf1a25a7c6a9182af70610b2313", size = 14172488 }, - { url = "https://files.pythonhosted.org/packages/f4/c2/dddca3e69a024d2f249a5b68698328163cbdafb7e65fbf6d36373bbabf12/numpy-2.1.1-cp312-cp312-win32.whl", hash = "sha256:950802d17a33c07cba7fd7c3dcfa7d64705509206be1606f196d179e539111ed", size = 6237195 }, - { url = "https://files.pythonhosted.org/packages/b7/98/5640a09daa3abf0caeaefa6e7bf0d10c0aa28a77c84e507d6a716e0e23df/numpy-2.1.1-cp312-cp312-win_amd64.whl", hash = "sha256:3fc5eabfc720db95d68e6646e88f8b399bfedd235994016351b1d9e062c4b270", size = 12568082 }, - { url = "https://files.pythonhosted.org/packages/6b/9e/8bc6f133bc6d359ccc9ec051853aded45504d217685191f31f46d36b7065/numpy-2.1.1-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:046356b19d7ad1890c751b99acad5e82dc4a02232013bd9a9a712fddf8eb60f5", size = 20834810 }, - { url = "https://files.pythonhosted.org/packages/32/1b/429519a2fa28681814c511574017d35f3aab7136d554cc65f4c1526dfbf5/numpy-2.1.1-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:6e5a9cb2be39350ae6c8f79410744e80154df658d5bea06e06e0ac5bb75480d5", size = 13507739 }, - { url = "https://files.pythonhosted.org/packages/25/18/c732d7dd9896d11e4afcd487ac65e62f9fa0495563b7614eb850765361fa/numpy-2.1.1-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:d4c57b68c8ef5e1ebf47238e99bf27657511ec3f071c465f6b1bccbef12d4136", size = 5074465 }, - { url = "https://files.pythonhosted.org/packages/3e/37/838b7ae9262c370ab25312bab365492016f11810ffc03ebebbd54670b669/numpy-2.1.1-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:8ae0fd135e0b157365ac7cc31fff27f07a5572bdfc38f9c2d43b2aff416cc8b0", size = 6606418 }, - { url = "https://files.pythonhosted.org/packages/8b/b9/7ff3bfb71e316a5b43a124c4b7a5881ab12f3c32636014bef1f757f19dbd/numpy-2.1.1-cp313-cp313-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:981707f6b31b59c0c24bcda52e5605f9701cb46da4b86c2e8023656ad3e833cb", size = 13692464 }, - { url = "https://files.pythonhosted.org/packages/42/78/75bcf16e6737cd196ff7ecf0e1fd3f953293a34dff4fd93fb488e8308536/numpy-2.1.1-cp313-cp313-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:2ca4b53e1e0b279142113b8c5eb7d7a877e967c306edc34f3b58e9be12fda8df", size = 16037763 }, - { url = "https://files.pythonhosted.org/packages/23/99/36bf5ffe034d06df307bc783e25cf164775863166dcd878879559fe0379f/numpy-2.1.1-cp313-cp313-musllinux_1_1_x86_64.whl", hash = "sha256:e097507396c0be4e547ff15b13dc3866f45f3680f789c1a1301b07dadd3fbc78", size = 16410374 }, - { url = "https://files.pythonhosted.org/packages/7f/16/04c5dab564887d4cd31a9ed30e51467fa70d52a4425f5a9bd1eed5b3d34c/numpy-2.1.1-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:f7506387e191fe8cdb267f912469a3cccc538ab108471291636a96a54e599556", size = 14169873 }, - { url = "https://files.pythonhosted.org/packages/09/e0/d1b5adbf1731886c4186c59a9fa208585df9452a43a2b60e79af7c649717/numpy-2.1.1-cp313-cp313-win32.whl", hash = "sha256:251105b7c42abe40e3a689881e1793370cc9724ad50d64b30b358bbb3a97553b", size = 6234118 }, - { url = "https://files.pythonhosted.org/packages/d0/9c/2391ee6e9ebe77232ddcab29d92662b545e99d78c3eb3b4e26d59b9ca1ca/numpy-2.1.1-cp313-cp313-win_amd64.whl", hash = "sha256:f212d4f46b67ff604d11fff7cc62d36b3e8714edf68e44e9760e19be38c03eb0", size = 12561742 }, - { url = "https://files.pythonhosted.org/packages/38/0e/c4f754f9e73f9bb520e8bf418c646f2c4f70c5d5f2bc561e90f884593193/numpy-2.1.1-cp313-cp313t-macosx_10_13_x86_64.whl", hash = "sha256:920b0911bb2e4414c50e55bd658baeb78281a47feeb064ab40c2b66ecba85553", size = 20858403 }, - { url = "https://files.pythonhosted.org/packages/32/fc/d69092b9171efa0cb8079577e71ce0cac0e08f917d33f6e99c916ed51d44/numpy-2.1.1-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:bab7c09454460a487e631ffc0c42057e3d8f2a9ddccd1e60c7bb8ed774992480", size = 13519851 }, - { url = "https://files.pythonhosted.org/packages/14/2a/d7cf2cd9f15b23f623075546ea64a2c367cab703338ca22aaaecf7e704df/numpy-2.1.1-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:cea427d1350f3fd0d2818ce7350095c1a2ee33e30961d2f0fef48576ddbbe90f", size = 5115444 }, - { url = "https://files.pythonhosted.org/packages/8e/00/e87b2cb4afcecca3b678deefb8fa53005d7054f3b5c39596e5554e5d98f8/numpy-2.1.1-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:e30356d530528a42eeba51420ae8bf6c6c09559051887196599d96ee5f536468", size = 6628903 }, - { url = "https://files.pythonhosted.org/packages/ab/9d/337ae8721b3beec48c3413d71f2d44b2defbf3c6f7a85184fc18b7b61f4a/numpy-2.1.1-cp313-cp313t-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:e8dfa9e94fc127c40979c3eacbae1e61fda4fe71d84869cc129e2721973231ef", size = 13665945 }, - { url = "https://files.pythonhosted.org/packages/c0/90/ee8668e84c5d5cc080ef3beb622c016adf19ca3aa51afe9dbdcc6a9baf59/numpy-2.1.1-cp313-cp313t-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:910b47a6d0635ec1bd53b88f86120a52bf56dcc27b51f18c7b4a2e2224c29f0f", size = 16023473 }, - { url = "https://files.pythonhosted.org/packages/38/a0/57c24b2131879183051dc698fbb53fd43b77c3fa85b6e6311014f2bc2973/numpy-2.1.1-cp313-cp313t-musllinux_1_1_x86_64.whl", hash = "sha256:13cc11c00000848702322af4de0147ced365c81d66053a67c2e962a485b3717c", size = 16400624 }, - { url = "https://files.pythonhosted.org/packages/bb/4c/14a41eb5c9548c6cee6af0936eabfd985c69230ffa2f2598321431a9aa0a/numpy-2.1.1-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:53e27293b3a2b661c03f79aa51c3987492bd4641ef933e366e0f9f6c9bf257ec", size = 14155072 }, -] - -[[package]] -name = "packaging" -version = "24.1" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/51/65/50db4dda066951078f0a96cf12f4b9ada6e4b811516bf0262c0f4f7064d4/packaging-24.1.tar.gz", hash = "sha256:026ed72c8ed3fcce5bf8950572258698927fd1dbda10a5e981cdf0ac37f4f002", size = 148788 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/08/aa/cc0199a5f0ad350994d660967a8efb233fe0416e4639146c089643407ce6/packaging-24.1-py3-none-any.whl", hash = "sha256:5b8f2217dbdbd2f7f384c41c628544e6d52f2d0f53c6d0c3ea61aa5d1d7ff124", size = 53985 }, -] - -[[package]] -name = "pluggy" -version = "1.5.0" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/96/2d/02d4312c973c6050a18b314a5ad0b3210edb65a906f868e31c111dede4a6/pluggy-1.5.0.tar.gz", hash = "sha256:2cffa88e94fdc978c4c574f15f9e59b7f4201d439195c3715ca9e2486f1d0cf1", size = 67955 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/88/5f/e351af9a41f866ac3f1fac4ca0613908d9a41741cfcf2228f4ad853b697d/pluggy-1.5.0-py3-none-any.whl", hash = "sha256:44e1ad92c8ca002de6377e165f3e0f1be63266ab4d554740532335b9d75ea669", size = 20556 }, -] - -[[package]] -name = "pytest" -version = "8.3.3" -source = { registry = "https://pypi.org/simple" } -dependencies = [ - { name = "colorama", marker = "sys_platform == 'win32'" }, - { name = "iniconfig" }, - { name = "packaging" }, - { name = "pluggy" }, -] -sdist = { url = "https://files.pythonhosted.org/packages/8b/6c/62bbd536103af674e227c41a8f3dcd022d591f6eed5facb5a0f31ee33bbc/pytest-8.3.3.tar.gz", hash = "sha256:70b98107bd648308a7952b06e6ca9a50bc660be218d53c257cc1fc94fda10181", size = 1442487 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/6b/77/7440a06a8ead44c7757a64362dd22df5760f9b12dc5f11b6188cd2fc27a0/pytest-8.3.3-py3-none-any.whl", hash = "sha256:a6853c7375b2663155079443d2e45de913a911a11d669df02a50814944db57b2", size = 342341 }, -] - -[[package]] -name = "ruff" -version = "0.6.5" -source = { registry = "https://pypi.org/simple" } -sdist = { url = "https://files.pythonhosted.org/packages/96/3f/29b2d3d90f811f6fb5b90242309f4668cd8c2482aab86ffc23099000545b/ruff-0.6.5.tar.gz", hash = "sha256:4d32d87fab433c0cf285c3683dd4dae63be05fd7a1d65b3f5bf7cdd05a6b96fb", size = 2476127 } -wheels = [ - { url = "https://files.pythonhosted.org/packages/64/05/cc62df44b5a0271b29f11d687aa89e85943e0d26e5bb773dbc1456d9885d/ruff-0.6.5-py3-none-linux_armv6l.whl", hash = "sha256:7e4e308f16e07c95fc7753fc1aaac690a323b2bb9f4ec5e844a97bb7fbebd748", size = 9770988 }, - { url = "https://files.pythonhosted.org/packages/09/3d/89dac56ab7053d5b7cba723c9cae1a29b7a2978174c67e2441525ee00343/ruff-0.6.5-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:932cd69eefe4daf8c7d92bd6689f7e8182571cb934ea720af218929da7bd7d69", size = 9423303 }, - { url = "https://files.pythonhosted.org/packages/70/76/dc04654d26beace866a3c9e0c87112304e3d6406e1ee8ca0d9bebbd82d91/ruff-0.6.5-py3-none-macosx_11_0_arm64.whl", hash = "sha256:3a8d42d11fff8d3143ff4da41742a98f8f233bf8890e9fe23077826818f8d680", size = 9134078 }, - { url = "https://files.pythonhosted.org/packages/da/52/6a492cffcd2c6e243043937ab52811b6ebb10cb5b77a68cc98e7676ceaef/ruff-0.6.5-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a50af6e828ee692fb10ff2dfe53f05caecf077f4210fae9677e06a808275754f", size = 10105094 }, - { url = "https://files.pythonhosted.org/packages/59/7c/fd76a583ae59a276537d71921d616a83ec7774027d0812049afb6af8a07f/ruff-0.6.5-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:794ada3400a0d0b89e3015f1a7e01f4c97320ac665b7bc3ade24b50b54cb2972", size = 9542751 }, - { url = "https://files.pythonhosted.org/packages/56/5b/4e8928fa11412b16ecf7d7755fe45db6dfa7abce32841f6aec33bae3a7da/ruff-0.6.5-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:381413ec47f71ce1d1c614f7779d88886f406f1fd53d289c77e4e533dc6ea200", size = 10358844 }, - { url = "https://files.pythonhosted.org/packages/bd/a8/315ea8f71b111c8fb2b681c88a3e7a707d74308eb1435dc6ee3e6637a286/ruff-0.6.5-py3-none-manylinux_2_17_ppc64.manylinux2014_ppc64.whl", hash = "sha256:52e75a82bbc9b42e63c08d22ad0ac525117e72aee9729a069d7c4f235fc4d276", size = 11075199 }, - { url = "https://files.pythonhosted.org/packages/d9/1c/3a3728d42db52bfe418d8c913b453531766be1383719573f2458e8b59990/ruff-0.6.5-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:09c72a833fd3551135ceddcba5ebdb68ff89225d30758027280968c9acdc7810", size = 10661186 }, - { url = "https://files.pythonhosted.org/packages/d4/0c/ae25e213461aab274822081923d747f02929d71843c42b8f56018a7ec636/ruff-0.6.5-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:800c50371bdcb99b3c1551d5691e14d16d6f07063a518770254227f7f6e8c178", size = 11747444 }, - { url = "https://files.pythonhosted.org/packages/c4/e3/9d0ff218c7663ab9d53abe02911bec03d32b8ced7f78c1c49c2af84903a2/ruff-0.6.5-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:8e25ddd9cd63ba1f3bd51c1f09903904a6adf8429df34f17d728a8fa11174253", size = 10266302 }, - { url = "https://files.pythonhosted.org/packages/ac/03/f158cc24120bf277b0cd7906ba509a2db74531003663500a0d1781cd7448/ruff-0.6.5-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:7291e64d7129f24d1b0c947ec3ec4c0076e958d1475c61202497c6aced35dd19", size = 10104976 }, - { url = "https://files.pythonhosted.org/packages/91/d0/0bacdffc234e588ec05834186ad11ec8281a6ca598d0106892497bbcfa44/ruff-0.6.5-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:9ad7dfbd138d09d9a7e6931e6a7e797651ce29becd688be8a0d4d5f8177b4b0c", size = 9625374 }, - { url = "https://files.pythonhosted.org/packages/1a/ad/721003cde8abd9f50bff74acbcb21852531036451d48a1abddba4dd84025/ruff-0.6.5-py3-none-musllinux_1_2_i686.whl", hash = "sha256:005256d977021790cc52aa23d78f06bb5090dc0bfbd42de46d49c201533982ae", size = 9959661 }, - { url = "https://files.pythonhosted.org/packages/37/84/8d70a3eacaacb65b4bb1461fc1a59e37ff165152b7e507692109117c877f/ruff-0.6.5-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:482c1e6bfeb615eafc5899127b805d28e387bd87db38b2c0c41d271f5e58d8cc", size = 10327408 }, - { url = "https://files.pythonhosted.org/packages/54/7e/6b0a9ab30428a9e3d9607f6dd2e4fb743594d42bd1b6ba7b7b239acda921/ruff-0.6.5-py3-none-win32.whl", hash = "sha256:cf4d3fa53644137f6a4a27a2b397381d16454a1566ae5335855c187fbf67e4f5", size = 8012512 }, - { url = "https://files.pythonhosted.org/packages/d8/88/176f50162a219e3039f21e9e4323869fc62bf8d3afb4147a390d6c744bd8/ruff-0.6.5-py3-none-win_amd64.whl", hash = "sha256:3e42a57b58e3612051a636bc1ac4e6b838679530235520e8f095f7c44f706ff9", size = 8804438 }, - { url = "https://files.pythonhosted.org/packages/67/a0/1b488bbe35a7ff8296fdea1ec1a9c2676cecc7e42bda63860f9397d59140/ruff-0.6.5-py3-none-win_arm64.whl", hash = "sha256:51935067740773afdf97493ba9b8231279e9beef0f2a8079188c4776c25688e0", size = 8179780 }, -]