From 2df0accce3195d8c5787727ff902dd37e3e25db6 Mon Sep 17 00:00:00 2001 From: Kevin Tindall Date: Mon, 14 Feb 2022 18:30:54 -0600 Subject: [PATCH] Moving to new organization --- .flake8 | 15 - .github/workflows/tox-test.yml | 64 -- .github/workflows/validation.yml | 63 - .gitignore | 108 -- .python-version | 4 - CONTRIBUTING.md | 7 - LICENSE | 202 ---- README.md | 104 +- mcstatus/__init__.py | 1 - mcstatus/bedrock_status.py | 99 -- mcstatus/pinger.py | 267 ----- mcstatus/protocol/__init__.py | 0 mcstatus/protocol/connection.py | 337 ------ mcstatus/querier.py | 166 --- mcstatus/scripts/__init__.py | 0 mcstatus/scripts/mcstatus.py | 132 --- mcstatus/server.py | 256 ----- mcstatus/tests/__init__.py | 0 mcstatus/tests/protocol/test_connection.py | 280 ----- mcstatus/tests/test_async_pinger.py | 91 -- mcstatus/tests/test_async_querier.py | 51 - mcstatus/tests/test_async_support.py | 26 - mcstatus/tests/test_bedrock_status.py | 21 - mcstatus/tests/test_pinger.py | 386 ------- mcstatus/tests/test_querier.py | 125 -- mcstatus/tests/test_retry_decorator.py | 66 -- mcstatus/tests/test_server.py | 215 ---- mcstatus/tests/test_session_id.py | 23 - mcstatus/tests/test_timeout.py | 34 - mcstatus/utils.py | 48 - poetry.lock | 1201 -------------------- pyproject.toml | 73 -- pyrightconfig.json | 11 - release.sh | 14 - tox.ini | 41 - 35 files changed, 1 insertion(+), 4530 deletions(-) delete mode 100644 .flake8 delete mode 100644 .github/workflows/tox-test.yml delete mode 100644 .github/workflows/validation.yml delete mode 100644 .gitignore delete mode 100644 .python-version delete mode 100644 CONTRIBUTING.md delete mode 100644 LICENSE delete mode 100644 mcstatus/__init__.py delete mode 100644 mcstatus/bedrock_status.py delete mode 100644 mcstatus/pinger.py delete mode 100644 mcstatus/protocol/__init__.py delete mode 100644 mcstatus/protocol/connection.py delete mode 100644 mcstatus/querier.py delete mode 100644 mcstatus/scripts/__init__.py delete mode 100644 mcstatus/scripts/mcstatus.py delete mode 100644 mcstatus/server.py delete mode 100644 mcstatus/tests/__init__.py delete mode 100644 mcstatus/tests/protocol/test_connection.py delete mode 100644 mcstatus/tests/test_async_pinger.py delete mode 100644 mcstatus/tests/test_async_querier.py delete mode 100644 mcstatus/tests/test_async_support.py delete mode 100644 mcstatus/tests/test_bedrock_status.py delete mode 100644 mcstatus/tests/test_pinger.py delete mode 100644 mcstatus/tests/test_querier.py delete mode 100644 mcstatus/tests/test_retry_decorator.py delete mode 100644 mcstatus/tests/test_server.py delete mode 100644 mcstatus/tests/test_session_id.py delete mode 100644 mcstatus/tests/test_timeout.py delete mode 100644 mcstatus/utils.py delete mode 100644 poetry.lock delete mode 100644 pyproject.toml delete mode 100644 pyrightconfig.json delete mode 100755 release.sh delete mode 100644 tox.ini diff --git a/.flake8 b/.flake8 deleted file mode 100644 index 05c3758..0000000 --- a/.flake8 +++ /dev/null @@ -1,15 +0,0 @@ -[flake8] -max-line-length=127 -extend-ignore=E203 -application-import-names=mcstatus -ban-relative-imports=true -import-order-style=pycharm -exclude=.venv,.git,.cache -ignore= - ANN001, # TEMPORARY: parameter annotation (we still have a lot of these) - ANN201, # TEMPORARY: return type annotation (we still have a lot of these) - ANN002, # *args annotation - ANN003, # **kwargs annotation - ANN101, # self param annotation - ANN102, # cls param annotation - ANN204, # return type annotation for special methods diff --git a/.github/workflows/tox-test.yml b/.github/workflows/tox-test.yml deleted file mode 100644 index f165319..0000000 --- a/.github/workflows/tox-test.yml +++ /dev/null @@ -1,64 +0,0 @@ -name: Tox test - -on: [pull_request, push] - -env: - # Make sure pip caches dependencies and installs as user - PIP_NO_CACHE_DIR: false - PIP_USER: 1 - - # Make sure poetry won't use virtual environments - POETRY_VIRTUALENVS_CREATE: false - - # Specify paths here, so we know exactly where things are for caching - PYTHONUSERBASE: ${{ github.workspace }}/.cache/py-user-base - POETRY_CACHE_DIR: ${{ github.workspace }}/.cache/py-user-base - TOXDIR: ${{ github.workspace }}/.tox - -jobs: - tox-test: - runs-on: ${{ matrix.platform }} - - strategy: - matrix: - platform: [ubuntu-latest, macos-latest, windows-latest] - python-version: ["3.7", "3.8", "3.9", "3.10"] - - steps: - - name: Checkout repository - uses: actions/checkout@v2 - - - name: Python setup - id: python - uses: actions/setup-python@v2 - with: - python-version: ${{ matrix.python-version }} - - # Cache python dependencies so that unless we change them, - # we won't need to reinstall them with each workflow run. - # The key is a composite of multiple values, which when changed - # the cache won't be restored in order to make updating possible - - name: Python dependency caching - uses: actions/cache@v2 - id: python_cache - with: - path: | - ${{ env.PYTHONUSERBASE }} - ${{ env.TOXDIR }} - key: "python-0-${{ runner.os }}-${{ env.PYTHONUSERBASE }}-\ - ${{ env.TOXDIR }}-${{ steps.python.outputs.python-version }}-\ - ${{ hashFiles('./pyproject.toml', './poetry.lock') }}" - - # In case the dependencies weren't restored, install them - - name: Install dependencies using poetry - if: steps.python_cache.outputs.cache-hit != 'true' - run: | - pip install poetry - pip install tox - pip install tox-poetry - pip install tox-gh-actions - - - name: Test with tox - run: python -m tox - env: - PIP_USER: 0 # We want tox to use it's environments, not user installs diff --git a/.github/workflows/validation.yml b/.github/workflows/validation.yml deleted file mode 100644 index 3635e67..0000000 --- a/.github/workflows/validation.yml +++ /dev/null @@ -1,63 +0,0 @@ -name: Validation - -on: [pull_request, push] - -env: - # Make sure pip caches dependencies and installs as user - PIP_NO_CACHE_DIR: false - PIP_USER: 1 - - # Make sure poetry won't use virtual environments - POETRY_VIRTUALENVS_CREATE: false - - # Specify paths here, so we know what to cache - POETRY_CACHE_DIR: ${{ github.workspace }}/.cache/py-user-base - PYTHONUSERBASE: ${{ github.workspace }}/.cache/py-user-base - -jobs: - lint: - runs-on: ubuntu-latest - - steps: - - name: Add custom PYTHONUSERBASE to PATH - run: echo '${{ env.PYTHONUSERBASE }}/bin/' >> $GITHUB_PATH - - - name: Checkout repository - uses: actions/checkout@v2 - - - name: Python setup - id: python - uses: actions/setup-python@v2 - with: - python-version: '3.9.5' - - # Cache python dependencies so that unless we change them, - # we won't need to reinstall them with each workflow run. - # The key is a composite of multiple values, which when changed - # the cache won't be restored in order to make updating possible - - name: Python dependency caching - uses: actions/cache@v2 - id: python_cache - with: - path: ${{ env.PYTHONUSERBASE }} - key: "python-0-${{ runner.os }}-${{ env.PYTHONUSERBASE }}-\ - ${{ steps.python.outputs.python-version }}-\ - ${{ hashFiles('./pyproject.toml', './poetry.lock') }}" - - # In case the dependencies weren't restored, install them - - name: Install dependencies using poetry - if: steps.python_cache.outputs.cache-hit != 'true' - run: | - pip install poetry - poetry install - - # Run the actual linting steps here: - - - name: Run black formatter check - run: black --check . - - - name: Run pyright type checker - run: pyright -v $PYTHONUSERBASE - - - name: Run flake8 linter - run: flake8 . diff --git a/.gitignore b/.gitignore deleted file mode 100644 index bba5761..0000000 --- a/.gitignore +++ /dev/null @@ -1,108 +0,0 @@ -# Created by http://www.gitignore.io - -### Python ### -# Byte-compiled / optimized / DLL files -__pycache__/ -*.py[cod] - -# C extensions -*.so - -# Distribution / packaging -.Python -env/ -venv/ -build/ -develop-eggs/ -dist/ -eggs/ -lib/ -lib64/ -parts/ -sdist/ -var/ -*.egg-info/ -.installed.cfg -*.egg - -# 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/ -.coverage* -.cache -nosetests.xml -coverage.xml - -# Translations -*.mo -*.pot - -# Django stuff: -*.log - -# Sphinx documentation -docs/_build/ - -# PyBuilder -target/ - - -### PyCharm ### -# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm - -## Directory-based project format -.idea/ -/*.iml -# if you remove the above rule, at least ignore user-specific stuff: -# .idea/workspace.xml -# .idea/tasks.xml -# .idea/dictionaries -# and these sensitive or high-churn files: -# .idea/dataSources.ids -# .idea/dataSources.xml -# .idea/sqlDataSources.xml -# .idea/dynamic.xml -# and, if using gradle:: -# .idea/gradle.xml -# .idea/libraries - -## File-based project format -*.ipr -*.iws - -## Additional for IntelliJ -out/ - -# generated by mpeltonen/sbt-idea plugin -.idea_modules/ - -# generated by JIRA plugin -atlassian-ide-plugin.xml - -# generated by Crashlytics plugin (for Android Studio and Intellij) -com_crashlytics_export_strings.xml - - -### SublimeText ### -# workspace files are user-specific -*.sublime-workspace - -# project files should be checked into the repository, unless a significant -# proportion of contributors will probably not be using SublimeText -# *.sublime-project - -#sftp configuration file -sftp-config.json - -### Visual Studio Code ### -.vscode diff --git a/.python-version b/.python-version deleted file mode 100644 index 0807d05..0000000 --- a/.python-version +++ /dev/null @@ -1,4 +0,0 @@ -3.9.6 -3.8.10 -3.7.9 -3.6.8 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md deleted file mode 100644 index bb2c378..0000000 --- a/CONTRIBUTING.md +++ /dev/null @@ -1,7 +0,0 @@ -Setup: -``` -pipx install poetry -pipx inject poetry poetry-dynamic-versioning -pipx install tox -pipx inject tox tox-poetry -``` diff --git a/LICENSE b/LICENSE deleted file mode 100644 index d645695..0000000 --- a/LICENSE +++ /dev/null @@ -1,202 +0,0 @@ - - Apache License - Version 2.0, January 2004 - http://www.apache.org/licenses/ - - TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION - - 1. Definitions. - - "License" shall mean the terms and conditions for use, reproduction, - and distribution as defined by Sections 1 through 9 of this document. - - "Licensor" shall mean the copyright owner or entity authorized by - the copyright owner that is granting the License. - - "Legal Entity" shall mean the union of the acting entity and all - other entities that control, are controlled by, or are under common - control with that entity. For the purposes of this definition, - "control" means (i) the power, direct or indirect, to cause the - direction or management of such entity, whether by contract or - otherwise, or (ii) ownership of fifty percent (50%) or more of the - outstanding shares, or (iii) beneficial ownership of such entity. - - "You" (or "Your") shall mean an individual or Legal Entity - exercising permissions granted by this License. - - "Source" form shall mean the preferred form for making modifications, - including but not limited to software source code, documentation - source, and configuration files. - - "Object" form shall mean any form resulting from mechanical - transformation or translation of a Source form, including but - not limited to compiled object code, generated documentation, - and conversions to other media types. - - "Work" shall mean the work of authorship, whether in Source or - Object form, made available under the License, as indicated by a - copyright notice that is included in or attached to the work - (an example is provided in the Appendix below). - - "Derivative Works" shall mean any work, whether in Source or Object - form, that is based on (or derived from) the Work and for which the - editorial revisions, annotations, elaborations, or other modifications - represent, as a whole, an original work of authorship. For the purposes - of this License, Derivative Works shall not include works that remain - separable from, or merely link (or bind by name) to the interfaces of, - the Work and Derivative Works thereof. - - "Contribution" shall mean any work of authorship, including - the original version of the Work and any modifications or additions - to that Work or Derivative Works thereof, that is intentionally - submitted to Licensor for inclusion in the Work by the copyright owner - or by an individual or Legal Entity authorized to submit on behalf of - the copyright owner. For the purposes of this definition, "submitted" - means any form of electronic, verbal, or written communication sent - to the Licensor or its representatives, including but not limited to - communication on electronic mailing lists, source code control systems, - and issue tracking systems that are managed by, or on behalf of, the - Licensor for the purpose of discussing and improving the Work, but - excluding communication that is conspicuously marked or otherwise - designated in writing by the copyright owner as "Not a Contribution." - - "Contributor" shall mean Licensor and any individual or Legal Entity - on behalf of whom a Contribution has been received by Licensor and - subsequently incorporated within the Work. - - 2. Grant of Copyright License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - copyright license to reproduce, prepare Derivative Works of, - publicly display, publicly perform, sublicense, and distribute the - Work and such Derivative Works in Source or Object form. - - 3. Grant of Patent License. Subject to the terms and conditions of - this License, each Contributor hereby grants to You a perpetual, - worldwide, non-exclusive, no-charge, royalty-free, irrevocable - (except as stated in this section) patent license to make, have made, - use, offer to sell, sell, import, and otherwise transfer the Work, - where such license applies only to those patent claims licensable - by such Contributor that are necessarily infringed by their - Contribution(s) alone or by combination of their Contribution(s) - with the Work to which such Contribution(s) was submitted. If You - institute patent litigation against any entity (including a - cross-claim or counterclaim in a lawsuit) alleging that the Work - or a Contribution incorporated within the Work constitutes direct - or contributory patent infringement, then any patent licenses - granted to You under this License for that Work shall terminate - as of the date such litigation is filed. - - 4. Redistribution. You may reproduce and distribute copies of the - Work or Derivative Works thereof in any medium, with or without - modifications, and in Source or Object form, provided that You - meet the following conditions: - - (a) You must give any other recipients of the Work or - Derivative Works a copy of this License; and - - (b) You must cause any modified files to carry prominent notices - stating that You changed the files; and - - (c) You must retain, in the Source form of any Derivative Works - that You distribute, all copyright, patent, trademark, and - attribution notices from the Source form of the Work, - excluding those notices that do not pertain to any part of - the Derivative Works; and - - (d) If the Work includes a "NOTICE" text file as part of its - distribution, then any Derivative Works that You distribute must - include a readable copy of the attribution notices contained - within such NOTICE file, excluding those notices that do not - pertain to any part of the Derivative Works, in at least one - of the following places: within a NOTICE text file distributed - as part of the Derivative Works; within the Source form or - documentation, if provided along with the Derivative Works; or, - within a display generated by the Derivative Works, if and - wherever such third-party notices normally appear. The contents - of the NOTICE file are for informational purposes only and - do not modify the License. You may add Your own attribution - notices within Derivative Works that You distribute, alongside - or as an addendum to the NOTICE text from the Work, provided - that such additional attribution notices cannot be construed - as modifying the License. - - You may add Your own copyright statement to Your modifications and - may provide additional or different license terms and conditions - for use, reproduction, or distribution of Your modifications, or - for any such Derivative Works as a whole, provided Your use, - reproduction, and distribution of the Work otherwise complies with - the conditions stated in this License. - - 5. Submission of Contributions. Unless You explicitly state otherwise, - any Contribution intentionally submitted for inclusion in the Work - by You to the Licensor shall be under the terms and conditions of - this License, without any additional terms or conditions. - Notwithstanding the above, nothing herein shall supersede or modify - the terms of any separate license agreement you may have executed - with Licensor regarding such Contributions. - - 6. Trademarks. This License does not grant permission to use the trade - names, trademarks, service marks, or product names of the Licensor, - except as required for reasonable and customary use in describing the - origin of the Work and reproducing the content of the NOTICE file. - - 7. Disclaimer of Warranty. Unless required by applicable law or - agreed to in writing, Licensor provides the Work (and each - Contributor provides its Contributions) on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or - implied, including, without limitation, any warranties or conditions - of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A - PARTICULAR PURPOSE. You are solely responsible for determining the - appropriateness of using or redistributing the Work and assume any - risks associated with Your exercise of permissions under this License. - - 8. Limitation of Liability. In no event and under no legal theory, - whether in tort (including negligence), contract, or otherwise, - unless required by applicable law (such as deliberate and grossly - negligent acts) or agreed to in writing, shall any Contributor be - liable to You for damages, including any direct, indirect, special, - incidental, or consequential damages of any character arising as a - result of this License or out of the use or inability to use the - Work (including but not limited to damages for loss of goodwill, - work stoppage, computer failure or malfunction, or any and all - other commercial damages or losses), even if such Contributor - has been advised of the possibility of such damages. - - 9. Accepting Warranty or Additional Liability. While redistributing - the Work or Derivative Works thereof, You may choose to offer, - and charge a fee for, acceptance of support, warranty, indemnity, - or other liability obligations and/or rights consistent with this - License. However, in accepting such obligations, You may act only - on Your own behalf and on Your sole responsibility, not on behalf - of any other Contributor, and only if You agree to indemnify, - defend, and hold each Contributor harmless for any liability - incurred by, or claims asserted against, such Contributor by reason - of your accepting any such warranty or additional liability. - - END OF TERMS AND CONDITIONS - - APPENDIX: How to apply the Apache License to your work. - - To apply the Apache License to your work, attach the following - boilerplate notice, with the fields enclosed by brackets "[]" - replaced with your own identifying information. (Don't include - the brackets!) The text should be enclosed in the appropriate - comment syntax for the file format. We also recommend that a - file or class name and description of purpose be included on the - same "printed page" as the copyright notice for easier - identification within third-party archives. - - Copyright [yyyy] [name of copyright owner] - - Licensed under the Apache License, Version 2.0 (the "License"); - you may not use this file except in compliance with the License. - You may obtain a copy of the License at - - http://www.apache.org/licenses/LICENSE-2.0 - - Unless required by applicable law or agreed to in writing, software - distributed under the License is distributed on an "AS IS" BASIS, - WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - See the License for the specific language governing permissions and - limitations under the License. diff --git a/README.md b/README.md index bc5a88b..6b953af 100644 --- a/README.md +++ b/README.md @@ -1,103 +1 @@ -![travis build status](https://img.shields.io/travis/Dinnerbone/mcstatus/master.svg) -[![current PyPI version](https://img.shields.io/pypi/v/mcstatus.svg)](https://pypi.org/project/mcstatus/) -![supported python versions](https://img.shields.io/pypi/pyversions/mcstatus.svg) -[![discord chat](https://img.shields.io/discord/936788458939224094.svg?logo=Discord)](https://discord.gg/C2wX7zduxC) - -mcstatus -======== - -`mcstatus` provides an easy way to query Minecraft servers for any information they can expose. -It provides three modes of access (`query`, `status` and `ping`), the differences of which are listed below in usage. - -Usage ------ - -Java Edition -```python -from mcstatus import MinecraftServer - -# If you know the host and port, you may skip this and use MinecraftServer("example.org", 1234) -server = MinecraftServer.lookup("example.org:1234") - -# 'status' is supported by all Minecraft servers that are version 1.7 or higher. -status = server.status() -print(f"The server has {status.players.online} players and replied in {status.latency} ms") - -# 'ping' is supported by all Minecraft servers that are version 1.7 or higher. -# It is included in a 'status' call, but is exposed separate if you do not require the additional info. -latency = server.ping() -print(f"The server replied in {latency} ms") - -# 'query' has to be enabled in a servers' server.properties file. -# It may give more information than a ping, such as a full player list or mod information. -query = server.query() -print(f"The server has the following players online: {', '.join(query.players.names)}") -``` - -Bedrock Edition -```python -from mcstatus import MinecraftBedrockServer - -# If you know the host and port, you may skip this and use MinecraftBedrockServer("example.org", 19132) -server = MinecraftBedrockServer.lookup("example.org:19132") - -# 'status' is the only feature that is supported by Bedrock at this time. -# In this case status includes players_online, latency, motd, map, gamemode, and players_max. (ex: status.gamemode) -status = server.status() -print(f"The server has {status.players_online} players online and replied in {status.latency} ms") -``` - -Command Line Interface -``` -$ mcstatus -Usage: mcstatus [OPTIONS] ADDRESS COMMAND [ARGS]... - - mcstatus provides an easy way to query Minecraft servers for any - information they can expose. It provides three modes of access: query, - status, and ping. - - Examples: - - $ mcstatus example.org ping - 21.120ms - - $ mcstatus example.org:1234 ping - 159.903ms - - $ mcstatus example.org status - version: v1.8.8 (protocol 47) - description: "A Minecraft Server" - players: 1/20 ['Dinnerbone (61699b2e-d327-4a01-9f1e-0ea8c3f06bc6)'] - - $ mcstatus example.org query - host: 93.148.216.34:25565 - software: v1.8.8 vanilla - plugins: [] - motd: "A Minecraft Server" - players: 1/20 ['Dinnerbone (61699b2e-d327-4a01-9f1e-0ea8c3f06bc6)'] - -Options: - -h, --help Show this message and exit. - -Commands: - json combination of several other commands with json formatting - ping prints server latency - query detailed server information - status basic server information -``` - -Installation ------------- - -mcstatus is available on pypi, and can be installed trivially with: - -```bash -python3 -m pip install mcstatus -``` - -Alternatively, just clone this repo! - -License -------- - -mcstatus is licensed under Apache 2.0. +Moving to https://github.com/py-mine/mcstatus diff --git a/mcstatus/__init__.py b/mcstatus/__init__.py deleted file mode 100644 index b2b7358..0000000 --- a/mcstatus/__init__.py +++ /dev/null @@ -1 +0,0 @@ -from mcstatus.server import MinecraftBedrockServer, MinecraftServer # noqa: F401 diff --git a/mcstatus/bedrock_status.py b/mcstatus/bedrock_status.py deleted file mode 100644 index 73c0165..0000000 --- a/mcstatus/bedrock_status.py +++ /dev/null @@ -1,99 +0,0 @@ -from __future__ import annotations - -import asyncio -import socket -import struct -from time import perf_counter - -import asyncio_dgram - - -class BedrockServerStatus: - request_status_data = b"\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\xff\xff\x00\xfe\xfe\xfe\xfe\xfd\xfd\xfd\xfd\x124Vx" - - def __init__(self, host: str, port: int = 19132, timeout: float = 3): - self.host = host - self.port = port - self.timeout = timeout - - @staticmethod - def parse_response(data: bytes, latency: float) -> "BedrockStatusResponse": - data = data[1:] - name_length = struct.unpack(">H", data[32:34])[0] - decoded_data = data[34 : 34 + name_length].decode().split(";") - - try: - map_ = decoded_data[7] - except IndexError: - map_ = None - try: - gamemode = decoded_data[8] - except IndexError: - gamemode = None - - return BedrockStatusResponse( - protocol=decoded_data[2], - brand=decoded_data[0], - version=decoded_data[3], - latency=latency, - players_online=decoded_data[4], - players_max=decoded_data[5], - motd=decoded_data[1], - map_=map_, - gamemode=gamemode, - ) - - def read_status(self) -> BedrockStatusResponse: - start = perf_counter() - - s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) - s.settimeout(self.timeout) - - s.sendto(self.request_status_data, (self.host, self.port)) - data, _ = s.recvfrom(2048) - - return self.parse_response(data, (perf_counter() - start)) - - async def read_status_async(self) -> BedrockStatusResponse: - start = perf_counter() - stream = None - - try: - conn = asyncio_dgram.connect((self.host, self.port)) - stream = await asyncio.wait_for(conn, timeout=self.timeout) - - await asyncio.wait_for(stream.send(self.request_status_data), timeout=self.timeout) - data, _ = await asyncio.wait_for(stream.recv(), timeout=self.timeout) - finally: - if stream is not None: - stream.close() - - return self.parse_response(data, (perf_counter() - start)) - - -class BedrockStatusResponse: - class Version: - def __init__(self, protocol, brand, version): - self.protocol = protocol - self.brand = brand - self.version = version - - def __init__( - self, - protocol, - brand, - version, - latency, - players_online, - players_max, - motd, - map_, - gamemode, - ): - self.version = self.Version(protocol, brand, version) - self.latency = latency - self.players_online = players_online - self.players_max = players_max - self.motd = motd - self.map = map_ - self.gamemode = gamemode diff --git a/mcstatus/pinger.py b/mcstatus/pinger.py deleted file mode 100644 index 167f870..0000000 --- a/mcstatus/pinger.py +++ /dev/null @@ -1,267 +0,0 @@ -from __future__ import annotations - -import datetime -import json -import random -from typing import List, Optional, Union - -from mcstatus.protocol.connection import Connection, TCPAsyncSocketConnection, TCPSocketConnection - -STYLE_MAP = { - "bold": "l", - "italic": "o", - "underlined": "n", - "obfuscated": "k", - "color": { - "dark_red": "4", - "red": "c", - "gold": "6", - "yellow": "e", - "dark_green": "2", - "green": "a", - "aqua": "b", - "dark_aqua": "3", - "dark_blue": "1", - "blue": "9", - "light_purple": "d", - "dark_purple": "5", - "white": "f", - "gray": "7", - "dark_gray": "8", - "black": "0", - }, -} - - -class ServerPinger: - def __init__( - self, - connection: TCPSocketConnection, - host: str = "", - port: int = 0, - version: int = 47, - ping_token=None, - ): - if ping_token is None: - ping_token = random.randint(0, (1 << 63) - 1) - self.version = version - self.connection = connection - self.host = host - self.port = port - self.ping_token = ping_token - - def handshake(self) -> None: - packet = Connection() - packet.write_varint(0) - packet.write_varint(self.version) - packet.write_utf(self.host) - packet.write_ushort(self.port) - packet.write_varint(1) # Intention to query status - - self.connection.write_buffer(packet) - - def read_status(self) -> "PingResponse": - request = Connection() - request.write_varint(0) # Request status - self.connection.write_buffer(request) - - response = self.connection.read_buffer() - if response.read_varint() != 0: - raise IOError("Received invalid status response packet.") - try: - raw = json.loads(response.read_utf()) - except ValueError: - raise IOError("Received invalid JSON") - try: - return PingResponse(raw) - except ValueError as e: - raise IOError(f"Received invalid status response: {e}") - - def test_ping(self) -> float: - request = Connection() - request.write_varint(1) # Test ping - request.write_long(self.ping_token) - sent = datetime.datetime.now() - self.connection.write_buffer(request) - - response = self.connection.read_buffer() - received = datetime.datetime.now() - if response.read_varint() != 1: - raise IOError("Received invalid ping response packet.") - received_token = response.read_long() - if received_token != self.ping_token: - raise IOError( - f"Received mangled ping response packet (expected token {self.ping_token}, received {received_token})" - ) - - delta = received - sent - return delta.total_seconds() * 1000 - - -class AsyncServerPinger(ServerPinger): - def __init__( - self, connection: TCPAsyncSocketConnection, host: str = "", port: int = 0, version: int = 47, ping_token=None - ): - # We do this to inform python about self.connection type (it's async) - super().__init__(connection, host=host, port=port, version=version, ping_token=ping_token) # type: ignore[arg-type] - self.connection: TCPAsyncSocketConnection - - async def read_status(self) -> "PingResponse": - request = Connection() - request.write_varint(0) # Request status - self.connection.write_buffer(request) - - response = await self.connection.read_buffer() - if response.read_varint() != 0: - raise IOError("Received invalid status response packet.") - try: - raw = json.loads(response.read_utf()) - except ValueError: - raise IOError("Received invalid JSON") - try: - return PingResponse(raw) - except ValueError as e: - raise IOError(f"Received invalid status response: {e}") - - async def test_ping(self) -> float: - request = Connection() - request.write_varint(1) # Test ping - request.write_long(self.ping_token) - sent = datetime.datetime.now() - self.connection.write_buffer(request) - - response = await self.connection.read_buffer() - received = datetime.datetime.now() - if response.read_varint() != 1: - raise IOError("Received invalid ping response packet.") - received_token = response.read_long() - if received_token != self.ping_token: - raise IOError( - f"Received mangled ping response packet (expected token {self.ping_token}, received {received_token})" - ) - - delta = received - sent - return delta.total_seconds() * 1000 - - -class PingResponse: - # THIS IS SO UNPYTHONIC - # it's staying just because the tests depend on this structure - class Players: - class Player: - name: str - id: str - - def __init__(self, raw): - if not isinstance(raw, dict): - raise ValueError(f"Invalid player object (expected dict, found {type(raw)}") - - if "name" not in raw: - raise ValueError("Invalid player object (no 'name' value)") - if not isinstance(raw["name"], str): - raise ValueError(f"Invalid player object (expected 'name' to be str, was {type(raw['name'])}") - self.name = raw["name"] - - if "id" not in raw: - raise ValueError("Invalid player object (no 'id' value)") - if not isinstance(raw["id"], str): - raise ValueError(f"Invalid player object (expected 'id' to be str, was {type(raw['id'])}") - self.id = raw["id"] - - online: int - max: int - sample: Optional[List["PingResponse.Players.Player"]] - - def __init__(self, raw): - if not isinstance(raw, dict): - raise ValueError(f"Invalid players object (expected dict, found {type(raw)}") - - if "online" not in raw: - raise ValueError("Invalid players object (no 'online' value)") - if not isinstance(raw["online"], int): - raise ValueError(f"Invalid players object (expected 'online' to be int, was {type(raw['online'])})") - self.online = raw["online"] - - if "max" not in raw: - raise ValueError("Invalid players object (no 'max' value)") - if not isinstance(raw["max"], int): - raise ValueError(f"Invalid players object (expected 'max' to be int, was {type(raw['max'])}") - self.max = raw["max"] - - if "sample" in raw: - if not isinstance(raw["sample"], list): - raise ValueError(f"Invalid players object (expected 'sample' to be list, was {type(raw['max'])})") - self.sample = [PingResponse.Players.Player(p) for p in raw["sample"]] - else: - self.sample = None - - class Version: - name: str - protocol: int - - def __init__(self, raw): - if not isinstance(raw, dict): - raise ValueError(f"Invalid version object (expected dict, found {type(raw)})") - - if "name" not in raw: - raise ValueError("Invalid version object (no 'name' value)") - if not isinstance(raw["name"], str): - raise ValueError(f"Invalid version object (expected 'name' to be str, was {type(raw['name'])})") - self.name = raw["name"] - - if "protocol" not in raw: - raise ValueError("Invalid version object (no 'protocol' value)") - if not isinstance(raw["protocol"], int): - raise ValueError(f"Invalid version object (expected 'protocol' to be int, was {type(raw['protocol'])})") - self.protocol = raw["protocol"] - - players: Players - version: Version - description: str - favicon: Optional[str] - latency: float = 0 - - def __init__(self, raw): - self.raw = raw - - if "players" not in raw: - raise ValueError("Invalid status object (no 'players' value)") - self.players = PingResponse.Players(raw["players"]) - - if "version" not in raw: - raise ValueError("Invalid status object (no 'version' value)") - self.version = PingResponse.Version(raw["version"]) - - if "description" not in raw: - raise ValueError("Invalid status object (no 'description' value)") - self.description = self._parse_description(raw["description"]) - - self.favicon = raw.get("favicon") - - @staticmethod - def _parse_description(raw_description: Union[dict, list, str]) -> str: - if isinstance(raw_description, str): - return raw_description - - if isinstance(raw_description, dict): - entries = raw_description.get("extra", []) - end = raw_description["text"] - else: - entries = raw_description - end = "" - - description = "" - - for entry in entries: - for style_key, style_val in STYLE_MAP.items(): - if entry.get(style_key): - try: - if isinstance(style_val, dict): - style_val = style_val[entry[style_key]] - - description += f"ยง{style_val}" - except KeyError: - pass # ignoring these key errors strips out html color codes - description += entry.get("text", "") - - return description + end diff --git a/mcstatus/protocol/__init__.py b/mcstatus/protocol/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/mcstatus/protocol/connection.py b/mcstatus/protocol/connection.py deleted file mode 100644 index e37473f..0000000 --- a/mcstatus/protocol/connection.py +++ /dev/null @@ -1,337 +0,0 @@ -from __future__ import annotations - -import asyncio -import socket -import struct -from abc import ABC, abstractmethod -from ctypes import c_int32 as signed_int32 -from ctypes import c_uint32 as unsigned_int32 -from ipaddress import ip_address -from typing import Iterable, Optional, SupportsBytes, TYPE_CHECKING, Tuple, Union - -import asyncio_dgram - -if TYPE_CHECKING: - from typing_extensions import SupportsIndex # Python 3.7 doesn't support this yet. - - BytesConvertable = Union[SupportsIndex, Iterable[SupportsIndex]] - - -def ip_type(address: Union[int, str]) -> Optional[int]: - try: - return ip_address(address).version - except ValueError: - return None - - -class Connection: - def __init__(self): - self.sent = bytearray() - self.received = bytearray() - - def read(self, length: int) -> bytearray: - result = self.received[:length] - self.received = self.received[length:] - return result - - def write(self, data: Union["Connection", str, bytearray, bytes]) -> None: - if isinstance(data, Connection): - data = data.flush() - if isinstance(data, str): - data = bytearray(data, "utf-8") - self.sent.extend(data) - - def receive(self, data: Union[BytesConvertable, bytearray]) -> None: - if not isinstance(data, bytearray): - data = bytearray(data) - self.received.extend(data) - - def remaining(self) -> int: - return len(self.received) - - def flush(self) -> bytearray: - result = self.sent - self.sent = bytearray() - return result - - def _unpack(self, format: str, data: Union[BytesConvertable, SupportsBytes]) -> int: - return struct.unpack(">" + format, bytes(data))[0] - - def _pack(self, format: str, data: int) -> bytes: - return struct.pack(">" + format, data) - - def read_varint(self) -> int: - result = 0 - for i in range(5): - part = self.read(1)[0] - result |= (part & 0x7F) << 7 * i - if not part & 0x80: - return signed_int32(result).value - raise IOError("Server sent a varint that was too big!") - - def write_varint(self, value: int) -> None: - if value < -(2**31) or 2**31 - 1 < value: - raise ValueError("Minecraft varints must be in the range of [-2**31, 2**31 - 1].") - remaining = unsigned_int32(value).value - for _ in range(5): - if remaining & ~0x7F == 0: - self.write(struct.pack("!B", remaining)) - return - self.write(struct.pack("!B", remaining & 0x7F | 0x80)) - remaining >>= 7 - - def read_utf(self) -> str: - length = self.read_varint() - return self.read(length).decode("utf8") - - def write_utf(self, value: str) -> None: - self.write_varint(len(value)) - self.write(bytearray(value, "utf8")) - - def read_ascii(self) -> str: - result = bytearray() - while len(result) == 0 or result[-1] != 0: - result.extend(self.read(1)) - return result[:-1].decode("ISO-8859-1") - - def write_ascii(self, value: str) -> None: - self.write(bytearray(value, "ISO-8859-1")) - self.write(bytearray.fromhex("00")) - - def read_short(self) -> int: - return self._unpack("h", self.read(2)) - - def write_short(self, value: int) -> None: - self.write(self._pack("h", value)) - - def read_ushort(self) -> int: - return self._unpack("H", self.read(2)) - - def write_ushort(self, value: int) -> None: - self.write(self._pack("H", value)) - - def read_int(self) -> int: - return self._unpack("i", self.read(4)) - - def write_int(self, value: int) -> None: - self.write(self._pack("i", value)) - - def read_uint(self) -> int: - return self._unpack("I", self.read(4)) - - def write_uint(self, value: int) -> None: - self.write(self._pack("I", value)) - - def read_long(self) -> int: - return self._unpack("q", self.read(8)) - - def write_long(self, value: int) -> None: - self.write(self._pack("q", value)) - - def read_ulong(self) -> int: - return self._unpack("Q", self.read(8)) - - def write_ulong(self, value: int) -> None: - self.write(self._pack("Q", value)) - - def read_buffer(self) -> "Connection": - length = self.read_varint() - result = Connection() - result.receive(self.read(length)) - return result - - def write_buffer(self, buffer: "Connection") -> None: - data = buffer.flush() - self.write_varint(len(data)) - self.write(data) - - -class AsyncReadConnection(Connection, ABC): - @abstractmethod - async def read(self, length: int) -> bytearray: - ... - - async def read_varint(self) -> int: - result = 0 - for i in range(5): - part = (await self.read(1))[0] - result |= (part & 0x7F) << (7 * i) - if not part & 0x80: - return signed_int32(result).value - raise IOError("Server sent a varint that was too big!") - - async def read_utf(self) -> str: - length = await self.read_varint() - return (await self.read(length)).decode("utf8") - - async def read_ascii(self) -> str: - result = bytearray() - while len(result) == 0 or result[-1] != 0: - result.extend(await self.read(1)) - return result[:-1].decode("ISO-8859-1") - - async def read_short(self) -> int: - return self._unpack("h", await self.read(2)) - - async def read_ushort(self) -> int: - return self._unpack("H", await self.read(2)) - - async def read_int(self) -> int: - return self._unpack("i", await self.read(4)) - - async def read_uint(self) -> int: - return self._unpack("I", await self.read(4)) - - async def read_long(self) -> int: - return self._unpack("q", await self.read(8)) - - async def read_ulong(self) -> int: - return self._unpack("Q", await self.read(8)) - - async def read_buffer(self) -> Connection: - length = await self.read_varint() - result = Connection() - result.receive(await self.read(length)) - return result - - -class TCPSocketConnection(Connection): - def __init__(self, addr: Tuple[str, int], timeout: float = 3): - Connection.__init__(self) - self.socket = socket.create_connection(addr, timeout=timeout) - self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1) - - def flush(self) -> bytearray: - raise NotImplementedError("TCPSocketConnection does not support flush()") - - def receive(self, data: Union[BytesConvertable, SupportsBytes]) -> None: - raise NotImplementedError("TCPSocketConnection does not support receive()") - - def remaining(self) -> int: - raise NotImplementedError("TCPSocketConnection does not support remaining()") - - def read(self, length: int) -> bytearray: - result = bytearray() - while len(result) < length: - new = self.socket.recv(length - len(result)) - if len(new) == 0: - raise IOError("Server did not respond with any information!") - result.extend(new) - return result - - def write(self, data: Union[bytes, bytearray]) -> None: - self.socket.send(data) - - def __del__(self): - try: - self.socket.close() - except Exception: # TODO: Check what this actually excepts - pass - - -class UDPSocketConnection(Connection): - def __init__(self, addr: Tuple[str, int], timeout: float = 3): - Connection.__init__(self) - self.addr = addr - self.socket = socket.socket( - socket.AF_INET if ip_type(addr[0]) == 4 else socket.AF_INET6, - socket.SOCK_DGRAM, - ) - self.socket.settimeout(timeout) - - def flush(self) -> bytearray: - raise NotImplementedError("UDPSocketConnection does not support flush()") - - def receive(self, data: Union[BytesConvertable, SupportsBytes]) -> None: - raise NotImplementedError("UDPSocketConnection does not support receive()") - - def remaining(self) -> int: - return 65535 - - def read(self, length: int) -> bytearray: - result = bytearray() - while len(result) == 0: - result.extend(self.socket.recvfrom(self.remaining())[0]) - return result - - def write(self, data: Union[Connection, bytes, bytearray]) -> None: - if isinstance(data, Connection): - data = bytearray(data.flush()) - self.socket.sendto(data, self.addr) - - def __del__(self): - try: - self.socket.close() - except Exception: # TODO: Check what this actually excepts - pass - - -class TCPAsyncSocketConnection(AsyncReadConnection): - # These will only be None until connect is called, ignore the None type assignment - reader: asyncio.StreamReader = None # type: ignore[assignment] - writer: asyncio.StreamWriter = None # type: ignore[assignment] - timeout: float = None # type: ignore[assignment] - - def __init__(self): - super().__init__() - - async def connect(self, addr: Tuple[str, int], timeout: float = 3): - self.timeout = timeout - conn = asyncio.open_connection(addr[0], addr[1]) - self.reader, self.writer = await asyncio.wait_for(conn, timeout=self.timeout) - - async def read(self, length: int) -> bytearray: - result = bytearray() - while len(result) < length: - new = await asyncio.wait_for(self.reader.read(length - len(result)), timeout=self.timeout) - if len(new) == 0: - raise IOError("Server did not respond with any information!") - result.extend(new) - return result - - def write(self, data: Union[bytes, bytearray]) -> None: - self.writer.write(data) - - def __del__(self): - try: - self.writer.close() - except Exception: # TODO: Check what this actually expects - pass - - -class UDPAsyncSocketConnection(AsyncReadConnection): - # These will only be None until connect is called, ignore the None type assignment - stream: asyncio_dgram.aio.DatagramClient = None # type: ignore[assignment] - timeout: float = None # type: ignore[assignment] - - def __init__(self): - super().__init__() - - async def connect(self, addr: Tuple[str, int], timeout: float = 3): - self.timeout = timeout - conn = asyncio_dgram.connect((addr[0], addr[1])) - self.stream = await asyncio.wait_for(conn, timeout=self.timeout) - - def flush(self) -> bytearray: - raise NotImplementedError("UDPSocketConnection does not support flush()") - - def receive(self, data: Union[SupportsBytes, BytesConvertable]) -> None: - raise NotImplementedError("UDPSocketConnection does not support receive()") - - def remaining(self) -> int: - return 65535 - - async def read(self, length: int) -> bytes: - data, remote_addr = await asyncio.wait_for(self.stream.recv(), timeout=self.timeout) - return data - - async def write(self, data: Union[Connection, bytes, bytearray]) -> None: - if isinstance(data, Connection): - data = bytearray(data.flush()) - await self.stream.send(data) - - def __del__(self): - try: - self.stream.close() - except Exception: # TODO: Check what this actually excepts - pass diff --git a/mcstatus/querier.py b/mcstatus/querier.py deleted file mode 100644 index 30c0760..0000000 --- a/mcstatus/querier.py +++ /dev/null @@ -1,166 +0,0 @@ -from __future__ import annotations - -import random -import re -import struct -from typing import List, TYPE_CHECKING - -from mcstatus.protocol.connection import Connection, UDPAsyncSocketConnection, UDPSocketConnection - -if TYPE_CHECKING: - from typing_extensions import Self - - -class ServerQuerier: - MAGIC_PREFIX = bytearray.fromhex("FEFD") - PADDING = bytearray.fromhex("00000000") - PACKET_TYPE_CHALLENGE = 9 - PACKET_TYPE_QUERY = 0 - - def __init__(self, connection: UDPSocketConnection): - self.connection = connection - self.challenge = 0 - - @staticmethod - def _generate_session_id() -> int: - # minecraft only supports lower 4 bits - return random.randint(0, 2**31) & 0x0F0F0F0F - - def _create_packet(self) -> Connection: - packet = Connection() - packet.write(self.MAGIC_PREFIX) - packet.write(struct.pack("!B", self.PACKET_TYPE_QUERY)) - packet.write_uint(self._generate_session_id()) - packet.write_int(self.challenge) - packet.write(self.PADDING) - return packet - - def _create_handshake_packet(self) -> Connection: - packet = Connection() - packet.write(self.MAGIC_PREFIX) - packet.write(struct.pack("!B", self.PACKET_TYPE_CHALLENGE)) - packet.write_uint(self._generate_session_id()) - return packet - - def _read_packet(self) -> Connection: - packet = Connection() - packet.receive(self.connection.read(self.connection.remaining())) - packet.read(1 + 4) - return packet - - def handshake(self) -> None: - self.connection.write(self._create_handshake_packet()) - - packet = self._read_packet() - self.challenge = int(packet.read_ascii()) - - def read_query(self) -> QueryResponse: - request = self._create_packet() - self.connection.write(request) - - response = self._read_packet() - return QueryResponse.from_connection(response) - - -class AsyncServerQuerier(ServerQuerier): - def __init__(self, connection: UDPAsyncSocketConnection): - # We do this to inform python about self.connection type (it's async) - super().__init__(connection) # type: ignore[arg-type] - self.connection: UDPAsyncSocketConnection - - async def _read_packet(self) -> Connection: - packet = Connection() - packet.receive(await self.connection.read(self.connection.remaining())) - packet.read(1 + 4) - return packet - - async def handshake(self) -> None: - await self.connection.write(self._create_handshake_packet()) - - packet = await self._read_packet() - self.challenge = int(packet.read_ascii()) - - async def read_query(self) -> QueryResponse: - request = self._create_packet() - await self.connection.write(request) - - response = await self._read_packet() - return QueryResponse.from_connection(response) - - -class QueryResponse: - # THIS IS SO UNPYTHONIC - # it's staying just because the tests depend on this structure - class Players: - online: int - max: int - names: List[str] - - def __init__(self, online, max, names): - self.online = int(online) - self.max = int(max) - self.names = names - - class Software: - version: str - brand: str - plugins: List[str] - - def __init__(self, version, plugins): - self.version = version - self.brand = "vanilla" - self.plugins = [] - - if plugins: - parts = plugins.split(":", 1) - self.brand = parts[0].strip() - - if len(parts) == 2: - self.plugins = [s.strip() for s in parts[1].split(";")] - - motd: str - map: str - players: Players - software: Software - - def __init__(self, raw, players): - try: - self.raw = raw - self.motd = raw["hostname"] - self.map = raw["map"] - self.players = QueryResponse.Players(raw["numplayers"], raw["maxplayers"], players) - self.software = QueryResponse.Software(raw["version"], raw["plugins"]) - except KeyError: - raise ValueError("The provided data is not valid") - - @classmethod - def from_connection(cls, response: Connection) -> Self: - response.read(len("splitnum") + 1 + 1 + 1) - data = {} - players = [] - - while True: - key = response.read_ascii() - if key == "hostname": # hostname is actually motd in the query protocol - match = re.search(b"(.*)\x00gametype", response.received, flags=re.DOTALL) - motd = match.group(1) if match else "" - # Since the query protocol does not properly support unicode, the motd is still not resolved - # correctly; however, this will avoid other parameter parsing errors. - data[key] = response.read(len(motd)).decode("ISO-8859-1") - response.read(1) # ignore null byte - elif len(key) == 0: - response.read(1) - break - else: - value = response.read_ascii() - data[key] = value - - response.read(len("player_") + 1 + 1) - - while True: - motd = response.read_ascii() - if len(motd) == 0: - break - players.append(motd) - - return cls(data, players) diff --git a/mcstatus/scripts/__init__.py b/mcstatus/scripts/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/mcstatus/scripts/mcstatus.py b/mcstatus/scripts/mcstatus.py deleted file mode 100644 index f6199f0..0000000 --- a/mcstatus/scripts/mcstatus.py +++ /dev/null @@ -1,132 +0,0 @@ -from __future__ import annotations - -import socket -from json import dumps as json_dumps - -import click - -from mcstatus import MinecraftServer - -server: MinecraftServer = None # type: ignore[assignment] # This will be set with cli function - - -@click.group(context_settings=dict(help_option_names=["-h", "--help"])) -@click.argument("address") -def cli(address): - """ - mcstatus provides an easy way to query Minecraft servers for - any information they can expose. It provides three modes of - access: query, status, and ping. - - Examples: - - \b - $ mcstatus example.org ping - 21.120ms - - \b - $ mcstatus example.org:1234 ping - 159.903ms - - \b - $ mcstatus example.org status - version: v1.8.8 (protocol 47) - description: "A Minecraft Server" - players: 1/20 ['Dinnerbone (61699b2e-d327-4a01-9f1e-0ea8c3f06bc6)'] - - \b - $ mcstatus example.org query - host: 93.148.216.34:25565 - software: v1.8.8 vanilla - plugins: [] - motd: "A Minecraft Server" - players: 1/20 ['Dinnerbone (61699b2e-d327-4a01-9f1e-0ea8c3f06bc6)'] - """ - global server - server = MinecraftServer.lookup(address) - - -@cli.command(short_help="prints server latency") -def ping(): - """ - Ping server for latency. - """ - click.echo(f"{server.ping()}ms") - - -@cli.command(short_help="basic server information") -def status(): - """ - Prints server status. Supported by all Minecraft - servers that are version 1.7 or higher. - """ - response = server.status() - if response.players.sample is not None: - player_sample = str([f"{player.name} ({player.id})" for player in response.players.sample]) - else: - player_sample = "No players online" - - click.echo(f"version: v{response.version.name} (protocol {response.version.protocol})") - click.echo(f'description: "{response.description}"') - click.echo(f"players: {response.players.online}/{response.players.max} {player_sample}") - - -@cli.command(short_help="all available server information in json") -def json(): - """ - Prints server status and query in json. Supported by all Minecraft - servers that are version 1.7 or higher. - """ - data = {} - data["online"] = False - # Build data with responses and quit on exception - try: - ping_res = server.ping() - data["online"] = True - data["ping"] = ping_res - - status_res = server.status(tries=1) - data["version"] = status_res.version.name - data["protocol"] = status_res.version.protocol - data["motd"] = status_res.description - data["player_count"] = status_res.players.online - data["player_max"] = status_res.players.max - data["players"] = [] - if status_res.players.sample is not None: - data["players"] = [{"name": player.name, "id": player.id} for player in status_res.players.sample] - - query_res = server.query(tries=1) # type: ignore[call-arg] # tries is supported with retry decorator - data["host_ip"] = query_res.raw["hostip"] - data["host_port"] = query_res.raw["hostport"] - data["map"] = query_res.map - data["plugins"] = query_res.software.plugins - except Exception: # TODO: Check what this actually excepts - pass - click.echo(json_dumps(data)) - - -@cli.command(short_help="detailed server information") -def query(): - """ - Prints detailed server information. Must be enabled in - servers' server.properties file. - """ - try: - response = server.query() - except socket.timeout: - print( - "The server did not respond to the query protocol." - "\nPlease ensure that the server has enable-query turned on," - " and that the necessary port (same as server-port unless query-port is set) is open in any firewall(s)." - "\nSee https://wiki.vg/Query for further information." - ) - raise click.Abort() - click.echo(f"host: {response.raw['hostip']}:{response.raw['hostport']}") - click.echo(f"software: v{response.software.version} {response.software.brand}") - click.echo(f"plugins: {response.software.plugins}") - click.echo(f'motd: "{response.motd}"') - click.echo(f"players: {response.players.online}/{response.players.max} {response.players.names}") - - -if __name__ == "__main__": - cli() # type: ignore[call-arg] diff --git a/mcstatus/server.py b/mcstatus/server.py deleted file mode 100644 index 456b926..0000000 --- a/mcstatus/server.py +++ /dev/null @@ -1,256 +0,0 @@ -from __future__ import annotations - -from typing import Optional, TYPE_CHECKING, Tuple -from urllib.parse import urlparse - -import dns.resolver -from dns.exception import DNSException - -from mcstatus.bedrock_status import BedrockServerStatus, BedrockStatusResponse -from mcstatus.pinger import AsyncServerPinger, PingResponse, ServerPinger -from mcstatus.protocol.connection import ( - TCPAsyncSocketConnection, - TCPSocketConnection, - UDPAsyncSocketConnection, - UDPSocketConnection, -) -from mcstatus.querier import AsyncServerQuerier, QueryResponse, ServerQuerier -from mcstatus.utils import retry - -if TYPE_CHECKING: - from typing_extensions import Self - - -__all__ = ["MinecraftServer", "MinecraftBedrockServer"] - - -def parse_address(address: str) -> Tuple[str, Optional[int]]: - tmp = urlparse("//" + address) - if not tmp.hostname: - raise ValueError(f"Invalid address '{address}'") - return (tmp.hostname, tmp.port) - - -def ensure_valid(host: object, port: object): - if not isinstance(host, str): - raise TypeError(f"Host must be a string address, got {type(host)} ({host!r})") - if not isinstance(port, int): - raise TypeError(f"Port must be an integer port number, got {type(port)} ({port})") - if port > 65535 or port < 0: - raise ValueError(f"Port must be within the allowed range (0-2^16), got {port}") - - -class MinecraftServer: - """Base class for a Minecraft Java Edition server. - - :param str host: The host/address/ip of the Minecraft server. - :param int port: The port that the server is on. - :param float timeout: The timeout in seconds before failing to connect. - :attr host: - :attr port: - """ - - def __init__(self, host: str, port: int = 25565, timeout: float = 3): - ensure_valid(host, port) - self.host = host - self.port = port - self.timeout = timeout - - @classmethod - def lookup(cls, address: str, timeout: float = 3) -> Self: - """Parses the given address and checks DNS records for an SRV record that points to the Minecraft server. - - :param str address: The address of the Minecraft server, like `example.com:25565`. - :param float timeout: The timeout in seconds before failing to connect. - :return: A `MinecraftServer` instance. - :rtype: MinecraftServer - """ - - host, port = parse_address(address) - if port is None: - port = 25565 - try: - answers = dns.resolver.resolve("_minecraft._tcp." + host, "SRV") - if len(answers): - answer = answers[0] - host = str(answer.target).rstrip(".") - port = int(answer.port) - except Exception: - pass - - return cls(host, port, timeout) - - def ping(self, **kwargs) -> float: - """Checks the latency between a Minecraft Java Edition server and the client (you). - - :param type **kwargs: Passed to a `ServerPinger` instance. - :return: The latency between the Minecraft Server and you. - :rtype: float - """ - - connection = TCPSocketConnection((self.host, self.port), self.timeout) - return self._retry_ping(connection, **kwargs) - - @retry(tries=3) - def _retry_ping(self, connection: TCPSocketConnection, **kwargs) -> float: - pinger = ServerPinger(connection, host=self.host, port=self.port, **kwargs) - pinger.handshake() - return pinger.test_ping() - - async def async_ping(self, **kwargs) -> float: - """Asynchronously checks the latency between a Minecraft Java Edition server and the client (you). - - :param type **kwargs: Passed to a `AsyncServerPinger` instance. - :return: The latency between the Minecraft Server and you. - :rtype: float - """ - - connection = TCPAsyncSocketConnection() - await connection.connect((self.host, self.port), self.timeout) - return await self._retry_async_ping(connection, **kwargs) - - @retry(tries=3) - async def _retry_async_ping(self, connection: TCPAsyncSocketConnection, **kwargs) -> float: - pinger = AsyncServerPinger(connection, host=self.host, port=self.port, **kwargs) - pinger.handshake() - ping = await pinger.test_ping() - return ping - - def status(self, **kwargs) -> PingResponse: - """Checks the status of a Minecraft Java Edition server via the ping protocol. - - :param type **kwargs: Passed to a `ServerPinger` instance. - :return: Status information in a `PingResponse` instance. - :rtype: PingResponse - """ - - connection = TCPSocketConnection((self.host, self.port), self.timeout) - return self._retry_status(connection, **kwargs) - - @retry(tries=3) - def _retry_status(self, connection: TCPSocketConnection, **kwargs) -> PingResponse: - pinger = ServerPinger(connection, host=self.host, port=self.port, **kwargs) - pinger.handshake() - result = pinger.read_status() - result.latency = pinger.test_ping() - return result - - async def async_status(self, **kwargs) -> PingResponse: - """Asynchronously checks the status of a Minecraft Java Edition server via the ping protocol. - - :param type **kwargs: Passed to a `AsyncServerPinger` instance. - :return: Status information in a `PingResponse` instance. - :rtype: PingResponse - """ - - connection = TCPAsyncSocketConnection() - await connection.connect((self.host, self.port), self.timeout) - return await self._retry_async_status(connection, **kwargs) - - @retry(tries=3) - async def _retry_async_status(self, connection: TCPAsyncSocketConnection, **kwargs) -> PingResponse: - pinger = AsyncServerPinger(connection, host=self.host, port=self.port, **kwargs) - pinger.handshake() - result = await pinger.read_status() - result.latency = await pinger.test_ping() - return result - - def query(self) -> QueryResponse: - """Checks the status of a Minecraft Java Edition server via the query protocol. - - :return: Query status information in a `QueryResponse` instance. - :rtype: QueryResponse - """ - host = self.host - try: - answers = dns.resolver.resolve(host, "A") - if len(answers): - answer = answers[0] - host = str(answer).rstrip(".") - except DNSException: - pass - - return self._retry_query(host) - - @retry(tries=3) - def _retry_query(self, host: str) -> QueryResponse: - connection = UDPSocketConnection((host, self.port), self.timeout) - querier = ServerQuerier(connection) - querier.handshake() - return querier.read_query() - - async def async_query(self) -> QueryResponse: - """Asynchronously checks the status of a Minecraft Java Edition server via the query protocol. - - :return: Query status information in a `QueryResponse` instance. - :rtype: QueryResponse - """ - host = self.host - try: - answers = dns.resolver.resolve(host, "A") - if len(answers): - answer = answers[0] - host = str(answer).rstrip(".") - except DNSException: - pass - - return await self._retry_async_query(host) - - @retry(tries=3) - async def _retry_async_query(self, host) -> QueryResponse: - connection = UDPAsyncSocketConnection() - await connection.connect((host, self.port), self.timeout) - querier = AsyncServerQuerier(connection) - await querier.handshake() - return await querier.read_query() - - -class MinecraftBedrockServer: - """Base class for a Minecraft Bedrock Edition server. - - :param str host: The host/address/ip of the Minecraft server. - :param int port: The port that the server is on. - :param float timeout: The timeout in seconds before failing to connect. - :attr host: - :attr port: - """ - - def __init__(self, host: str, port: int = 19132, timeout: float = 3): - ensure_valid(host, port) - self.host = host - self.port = port - self.timeout = timeout - - @classmethod - def lookup(cls, address: str) -> Self: - """Parses a given address and returns a MinecraftBedrockServer instance. - - :param str address: The address of the Minecraft server, like `example.com:19132` - :return: A `MinecraftBedrockServer` instance. - :rtype: MinecraftBedrockServer - """ - host, port = parse_address(address) - # If the address didn't contain port, fall back to constructor's default - if port is None: - return cls(host) - return cls(host, port) - - @retry(tries=3) - def status(self, **kwargs) -> BedrockStatusResponse: - """Checks the status of a Minecraft Bedrock Edition server. - - :param type **kwargs: Passed to a `BedrockServerStatus` instance. - :return: Status information in a `BedrockStatusResponse` instance. - :rtype: BedrockStatusResponse - """ - return BedrockServerStatus(self.host, self.port, self.timeout, **kwargs).read_status() - - @retry(tries=3) - async def async_status(self, **kwargs) -> BedrockStatusResponse: - """Asynchronously checks the status of a Minecraft Bedrock Edition server. - - :param type **kwargs: Passed to a `BedrockServerStatus` instance. - :return: Status information in a `BedrockStatusResponse` instance. - :rtype: BedrockStatusResponse - """ - return await BedrockServerStatus(self.host, self.port, self.timeout, **kwargs).read_status_async() diff --git a/mcstatus/tests/__init__.py b/mcstatus/tests/__init__.py deleted file mode 100644 index e69de29..0000000 diff --git a/mcstatus/tests/protocol/test_connection.py b/mcstatus/tests/protocol/test_connection.py deleted file mode 100644 index 82d6244..0000000 --- a/mcstatus/tests/protocol/test_connection.py +++ /dev/null @@ -1,280 +0,0 @@ -from unittest.mock import Mock, patch - -import pytest - -from mcstatus.protocol.connection import ( - Connection, - TCPSocketConnection, - UDPSocketConnection, -) - - -class TestConnection: - connection: Connection - - def setup_method(self): - self.connection = Connection() - - def test_flush(self): - self.connection.sent = bytearray.fromhex("7FAABB") - - assert self.connection.flush() == bytearray.fromhex("7FAABB") - assert self.connection.sent == bytearray() - - def test_receive(self): - self.connection.receive(bytearray.fromhex("7F")) - self.connection.receive(bytearray.fromhex("AABB")) - - assert self.connection.received == bytearray.fromhex("7FAABB") - - def test_remaining(self): - self.connection.receive(bytearray.fromhex("7F")) - self.connection.receive(bytearray.fromhex("AABB")) - - assert self.connection.remaining() == 3 - - def test_send(self): - self.connection.write(bytearray.fromhex("7F")) - self.connection.write(bytearray.fromhex("AABB")) - - assert self.connection.flush() == bytearray.fromhex("7FAABB") - - def test_read(self): - self.connection.receive(bytearray.fromhex("7FAABB")) - - assert self.connection.read(2) == bytearray.fromhex("7FAA") - assert self.connection.read(1) == bytearray.fromhex("BB") - - def _assert_varint_read_write(self, hexstr, value) -> None: - self.connection.receive(bytearray.fromhex(hexstr)) - assert self.connection.read_varint() == value - - self.connection.write_varint(value) - assert self.connection.flush() == bytearray.fromhex(hexstr) - - def test_varint_cases(self): - self._assert_varint_read_write("00", 0) - self._assert_varint_read_write("01", 1) - self._assert_varint_read_write("0F", 15) - self._assert_varint_read_write("FFFFFFFF07", 2147483647) - - self._assert_varint_read_write("FFFFFFFF0F", -1) - self._assert_varint_read_write("8080808008", -2147483648) - - def test_read_invalid_varint(self): - self.connection.receive(bytearray.fromhex("FFFFFFFF80")) - - with pytest.raises(IOError): - self.connection.read_varint() - - def test_write_invalid_varint(self): - with pytest.raises(ValueError): - self.connection.write_varint(2147483648) - with pytest.raises(ValueError): - self.connection.write_varint(-2147483649) - - def test_read_utf(self): - self.connection.receive(bytearray.fromhex("0D48656C6C6F2C20776F726C6421")) - - assert self.connection.read_utf() == "Hello, world!" - - def test_write_utf(self): - self.connection.write_utf("Hello, world!") - - assert self.connection.flush() == bytearray.fromhex("0D48656C6C6F2C20776F726C6421") - - def test_read_empty_utf(self): - self.connection.write_utf("") - - assert self.connection.flush() == bytearray.fromhex("00") - - def test_read_ascii(self): - self.connection.receive(bytearray.fromhex("48656C6C6F2C20776F726C642100")) - - assert self.connection.read_ascii() == "Hello, world!" - - def test_write_ascii(self): - self.connection.write_ascii("Hello, world!") - - assert self.connection.flush() == bytearray.fromhex("48656C6C6F2C20776F726C642100") - - def test_read_empty_ascii(self): - self.connection.write_ascii("") - - assert self.connection.flush() == bytearray.fromhex("00") - - def test_read_short_negative(self): - self.connection.receive(bytearray.fromhex("8000")) - - assert self.connection.read_short() == -32768 - - def test_write_short_negative(self): - self.connection.write_short(-32768) - - assert self.connection.flush() == bytearray.fromhex("8000") - - def test_read_short_positive(self): - self.connection.receive(bytearray.fromhex("7FFF")) - - assert self.connection.read_short() == 32767 - - def test_write_short_positive(self): - self.connection.write_short(32767) - - assert self.connection.flush() == bytearray.fromhex("7FFF") - - def test_read_ushort_positive(self): - self.connection.receive(bytearray.fromhex("8000")) - - assert self.connection.read_ushort() == 32768 - - def test_write_ushort_positive(self): - self.connection.write_ushort(32768) - - assert self.connection.flush() == bytearray.fromhex("8000") - - def test_read_int_negative(self): - self.connection.receive(bytearray.fromhex("80000000")) - - assert self.connection.read_int() == -2147483648 - - def test_write_int_negative(self): - self.connection.write_int(-2147483648) - - assert self.connection.flush() == bytearray.fromhex("80000000") - - def test_read_int_positive(self): - self.connection.receive(bytearray.fromhex("7FFFFFFF")) - - assert self.connection.read_int() == 2147483647 - - def test_write_int_positive(self): - self.connection.write_int(2147483647) - - assert self.connection.flush() == bytearray.fromhex("7FFFFFFF") - - def test_read_uint_positive(self): - self.connection.receive(bytearray.fromhex("80000000")) - - assert self.connection.read_uint() == 2147483648 - - def test_write_uint_positive(self): - self.connection.write_uint(2147483648) - - assert self.connection.flush() == bytearray.fromhex("80000000") - - def test_read_long_negative(self): - self.connection.receive(bytearray.fromhex("8000000000000000")) - - assert self.connection.read_long() == -9223372036854775808 - - def test_write_long_negative(self): - self.connection.write_long(-9223372036854775808) - - assert self.connection.flush() == bytearray.fromhex("8000000000000000") - - def test_read_long_positive(self): - self.connection.receive(bytearray.fromhex("7FFFFFFFFFFFFFFF")) - - assert self.connection.read_long() == 9223372036854775807 - - def test_write_long_positive(self): - self.connection.write_long(9223372036854775807) - - assert self.connection.flush() == bytearray.fromhex("7FFFFFFFFFFFFFFF") - - def test_read_ulong_positive(self): - self.connection.receive(bytearray.fromhex("8000000000000000")) - - assert self.connection.read_ulong() == 9223372036854775808 - - def test_write_ulong_positive(self): - self.connection.write_ulong(9223372036854775808) - - assert self.connection.flush() == bytearray.fromhex("8000000000000000") - - def test_read_buffer(self): - self.connection.receive(bytearray.fromhex("027FAA")) - buffer = self.connection.read_buffer() - - assert buffer.received == bytearray.fromhex("7FAA") - assert self.connection.flush() == bytearray() - - def test_write_buffer(self): - buffer = Connection() - buffer.write(bytearray.fromhex("7FAA")) - self.connection.write_buffer(buffer) - - assert self.connection.flush() == bytearray.fromhex("027FAA") - - -class TCPSocketConnectionTest: - def setup_method(self): - socket = Mock() - socket.recv = Mock() - socket.send = Mock() - with patch("socket.create_connection") as create_connection: - create_connection.return_value = socket - self.connection = TCPSocketConnection(("localhost", 1234)) - - def test_flush(self): - with pytest.raises(TypeError): - self.connection.flush() - - def test_receive(self): - with pytest.raises(TypeError): - self.connection.receive("") # type: ignore # This is desired to produce TypeError - - def test_remaining(self): - with pytest.raises(TypeError): - self.connection.remaining() - - def test_read(self): - self.connection.socket.recv.return_value = bytearray.fromhex("7FAA") - - assert self.connection.read(2) == bytearray.fromhex("7FAA") - - def test_read_empty(self): - self.connection.socket.recv.return_value = bytearray.fromhex("") - - with pytest.raises(IOError): - self.connection.read(2) - - def test_write(self): - self.connection.write(bytearray.fromhex("7FAA")) - - self.connection.socket.send.assert_called_once_with(bytearray.fromhex("7FAA")) # type: ignore[attr-defined] - - -class UDPSocketConnectionTest: - def setup_method(self): - socket = Mock() - socket.recvfrom = Mock() - socket.sendto = Mock() - with patch("socket.socket") as create_socket: - create_socket.return_value = socket - self.connection = UDPSocketConnection(("localhost", 1234)) - - def test_flush(self): - with pytest.raises(TypeError): - self.connection.flush() - - def test_receive(self): - with pytest.raises(TypeError): - self.connection.receive("") # type: ignore # This is desired to produce TypeError - - def test_remaining(self): - assert self.connection.remaining() == 65535 - - def test_read(self): - self.connection.socket.recvfrom.return_value = [bytearray.fromhex("7FAA")] - - assert self.connection.read(2) == bytearray.fromhex("7FAA") - - def test_write(self): - self.connection.write(bytearray.fromhex("7FAA")) - - self.connection.socket.sendto.assert_called_once_with( # type: ignore[attr-defined] - bytearray.fromhex("7FAA"), - ("localhost", 1234), - ) diff --git a/mcstatus/tests/test_async_pinger.py b/mcstatus/tests/test_async_pinger.py deleted file mode 100644 index fb130c4..0000000 --- a/mcstatus/tests/test_async_pinger.py +++ /dev/null @@ -1,91 +0,0 @@ -import asyncio - -import pytest - -from mcstatus.pinger import AsyncServerPinger -from mcstatus.protocol.connection import Connection - - -def async_decorator(f): - def wrapper(*args, **kwargs): - loop = asyncio.get_event_loop() - return loop.run_until_complete(f(*args, **kwargs)) - - return wrapper - - -class FakeAsyncConnection(Connection): - async def read_buffer(self): - return super().read_buffer() - - -class TestAsyncServerPinger: - def setup_method(self): - self.pinger = AsyncServerPinger( - FakeAsyncConnection(), host="localhost", port=25565, version=44 # type: ignore[arg-type] - ) - - def test_handshake(self): - self.pinger.handshake() - - assert self.pinger.connection.flush() == bytearray.fromhex("0F002C096C6F63616C686F737463DD01") - - def test_read_status(self): - self.pinger.connection.receive( - bytearray.fromhex( - "7200707B226465736372697074696F6E223A2241204D696E65637261667420536572766572222C22706C6179657273223A7B2" - "26D6178223A32302C226F6E6C696E65223A307D2C2276657273696F6E223A7B226E616D65223A22312E382D70726531222C22" - "70726F746F636F6C223A34347D7D" - ) - ) - status = async_decorator(self.pinger.read_status)() - - assert status.raw == { - "description": "A Minecraft Server", - "players": {"max": 20, "online": 0}, - "version": {"name": "1.8-pre1", "protocol": 44}, - } - assert self.pinger.connection.flush() == bytearray.fromhex("0100") - - def test_read_status_invalid_json(self): - self.pinger.connection.receive(bytearray.fromhex("0300017B")) - with pytest.raises(IOError): - async_decorator(self.pinger.test_ping)() - - def test_read_status_invalid_reply(self): - self.pinger.connection.receive( - bytearray.fromhex( - "4F004D7B22706C6179657273223A7B226D6178223A32302C226F6E6C696E65223A307D2C2276657273696F6E223A7B226E616" - "D65223A22312E382D70726531222C2270726F746F636F6C223A34347D7D" - ) - ) - - with pytest.raises(IOError): - async_decorator(self.pinger.test_ping)() - - def test_read_status_invalid_status(self): - self.pinger.connection.receive(bytearray.fromhex("0105")) - - with pytest.raises(IOError): - async_decorator(self.pinger.test_ping)() - - def test_test_ping(self): - self.pinger.connection.receive(bytearray.fromhex("09010000000000DD7D1C")) - self.pinger.ping_token = 14515484 - - assert async_decorator(self.pinger.test_ping)() >= 0 - assert self.pinger.connection.flush() == bytearray.fromhex("09010000000000DD7D1C") - - def test_test_ping_invalid(self): - self.pinger.connection.receive(bytearray.fromhex("011F")) - self.pinger.ping_token = 14515484 - - with pytest.raises(IOError): - async_decorator(self.pinger.test_ping)() - - def test_test_ping_wrong_token(self): - self.pinger.connection.receive(bytearray.fromhex("09010000000000DD7D1C")) - self.pinger.ping_token = 12345 - - with pytest.raises(IOError): - async_decorator(self.pinger.test_ping)() diff --git a/mcstatus/tests/test_async_querier.py b/mcstatus/tests/test_async_querier.py deleted file mode 100644 index e557cdd..0000000 --- a/mcstatus/tests/test_async_querier.py +++ /dev/null @@ -1,51 +0,0 @@ -from mcstatus.protocol.connection import Connection -from mcstatus.querier import AsyncServerQuerier -from mcstatus.tests.test_async_pinger import async_decorator - - -class FakeUDPAsyncConnection(Connection): - async def read(self, length): - return super().read(length) - - async def write(self, data): - return super().write(data) - - -class TestMinecraftAsyncQuerier: - def setup_method(self): - self.querier = AsyncServerQuerier(FakeUDPAsyncConnection()) # type: ignore[arg-type] - - def test_handshake(self): - self.querier.connection.receive(bytearray.fromhex("090000000035373033353037373800")) - async_decorator(self.querier.handshake)() - conn_bytes = self.querier.connection.flush() - assert conn_bytes[:3] == bytearray.fromhex("FEFD09") - assert self.querier.challenge == 570350778 - - def test_query(self): - self.querier.connection.receive( - bytearray.fromhex( - "00000000000000000000000000000000686f73746e616d650041204d696e656372616674205365727665720067616d6574797" - "06500534d500067616d655f6964004d494e4543524146540076657273696f6e00312e3800706c7567696e7300006d61700077" - "6f726c64006e756d706c61796572730033006d6178706c617965727300323000686f7374706f727400323535363500686f737" - "46970003139322e3136382e35362e31000001706c617965725f000044696e6e6572626f6e6500446a696e6e69626f6e650053" - "746576650000" - ) - ) - response = async_decorator(self.querier.read_query)() - conn_bytes = self.querier.connection.flush() - assert conn_bytes[:3] == bytearray.fromhex("FEFD00") - assert conn_bytes[7:] == bytearray.fromhex("0000000000000000") - assert response.raw == { - "hostname": "A Minecraft Server", - "gametype": "SMP", - "game_id": "MINECRAFT", - "version": "1.8", - "plugins": "", - "map": "world", - "numplayers": "3", - "maxplayers": "20", - "hostport": "25565", - "hostip": "192.168.56.1", - } - assert response.players.names == ["Dinnerbone", "Djinnibone", "Steve"] diff --git a/mcstatus/tests/test_async_support.py b/mcstatus/tests/test_async_support.py deleted file mode 100644 index 8747ab8..0000000 --- a/mcstatus/tests/test_async_support.py +++ /dev/null @@ -1,26 +0,0 @@ -from inspect import iscoroutinefunction - -from mcstatus.protocol.connection import ( - TCPAsyncSocketConnection, - UDPAsyncSocketConnection, -) - - -def test_is_completely_asynchronous(): - conn = TCPAsyncSocketConnection() - assertions = 0 - for attribute in dir(conn): - if attribute.startswith("read_"): - assert iscoroutinefunction(getattr(conn, attribute)) - assertions += 1 - assert assertions > 0, "None of the read_* attributes were async" - - -def test_query_is_completely_asynchronous(): - conn = UDPAsyncSocketConnection() - assertions = 0 - for attribute in dir(conn): - if attribute.startswith("read_"): - assert iscoroutinefunction(getattr(conn, attribute)) - assertions += 1 - assert assertions > 0, "None of the read_* attributes were async" diff --git a/mcstatus/tests/test_bedrock_status.py b/mcstatus/tests/test_bedrock_status.py deleted file mode 100644 index 6d076a2..0000000 --- a/mcstatus/tests/test_bedrock_status.py +++ /dev/null @@ -1,21 +0,0 @@ -from mcstatus.bedrock_status import BedrockServerStatus, BedrockStatusResponse - - -def test_bedrock_response_contains_expected_fields(): - data = ( - b"\x1c\x00\x00\x00\x00\x00\x00\x00\x004GT\x00\xb8\x83D\xde\x00\xff\xff\x00\xfe\xfe\xfe\xfe\xfd\xfd\xfd\xfd" - b"\x124Vx\x00wMCPE;\xc2\xa7r\xc2\xa74G\xc2\xa7r\xc2\xa76a\xc2\xa7r\xc2\xa7ey\xc2\xa7r\xc2\xa72B\xc2\xa7r\xc2" - b"\xa71o\xc2\xa7r\xc2\xa79w\xc2\xa7r\xc2\xa7ds\xc2\xa7r\xc2\xa74e\xc2\xa7r\xc2\xa76r;422;;1;69;376707197539105" - b"3022;;Default;1;19132;-1;" - ) - parsed = BedrockServerStatus.parse_response(data, 1) - assert isinstance(parsed, BedrockStatusResponse) - assert "gamemode" in parsed.__dict__ - assert "latency" in parsed.__dict__ - assert "map" in parsed.__dict__ - assert "motd" in parsed.__dict__ - assert "players_max" in parsed.__dict__ - assert "players_online" in parsed.__dict__ - assert "version" in parsed.__dict__ - assert "brand" in parsed.version.__dict__ - assert "protocol" in parsed.version.__dict__ diff --git a/mcstatus/tests/test_pinger.py b/mcstatus/tests/test_pinger.py deleted file mode 100644 index 14a0b37..0000000 --- a/mcstatus/tests/test_pinger.py +++ /dev/null @@ -1,386 +0,0 @@ -import pytest - -from mcstatus.pinger import PingResponse, ServerPinger -from mcstatus.protocol.connection import Connection - - -class TestServerPinger: - def setup_method(self): - self.pinger = ServerPinger(Connection(), host="localhost", port=25565, version=44) # type: ignore[arg-type] - - def test_handshake(self): - self.pinger.handshake() - - assert self.pinger.connection.flush() == bytearray.fromhex("0F002C096C6F63616C686F737463DD01") - - def test_read_status(self): - self.pinger.connection.receive( - bytearray.fromhex( - "7200707B226465736372697074696F6E223A2241204D696E65637261667420536572766572222C22706C6179657273223A7B2" - "26D6178223A32302C226F6E6C696E65223A307D2C2276657273696F6E223A7B226E616D65223A22312E382D70726531222C22" - "70726F746F636F6C223A34347D7D" - ) - ) - status = self.pinger.read_status() - - assert status.raw == { - "description": "A Minecraft Server", - "players": {"max": 20, "online": 0}, - "version": {"name": "1.8-pre1", "protocol": 44}, - } - assert self.pinger.connection.flush() == bytearray.fromhex("0100") - - def test_read_status_invalid_json(self): - self.pinger.connection.receive(bytearray.fromhex("0300017B")) - with pytest.raises(IOError): - self.pinger.read_status() - - def test_read_status_invalid_reply(self): - self.pinger.connection.receive( - bytearray.fromhex( - "4F004D7B22706C6179657273223A7B226D6178223A32302C226F6E6C696E65223A307D2C2276657273696F6E223A7B226E616" - "D65223A22312E382D70726531222C2270726F746F636F6C223A34347D7D" - ) - ) - - with pytest.raises(IOError): - self.pinger.read_status() - - def test_read_status_invalid_status(self): - self.pinger.connection.receive(bytearray.fromhex("0105")) - - with pytest.raises(IOError): - self.pinger.read_status() - - def test_test_ping(self): - self.pinger.connection.receive(bytearray.fromhex("09010000000000DD7D1C")) - self.pinger.ping_token = 14515484 - - assert self.pinger.test_ping() >= 0 - assert self.pinger.connection.flush() == bytearray.fromhex("09010000000000DD7D1C") - - def test_test_ping_invalid(self): - self.pinger.connection.receive(bytearray.fromhex("011F")) - self.pinger.ping_token = 14515484 - - with pytest.raises(IOError): - self.pinger.test_ping() - - def test_test_ping_wrong_token(self): - self.pinger.connection.receive(bytearray.fromhex("09010000000000DD7D1C")) - self.pinger.ping_token = 12345 - - with pytest.raises(IOError): - self.pinger.test_ping() - - -class TestPingResponse: - def test_raw(self): - response = PingResponse( - { - "description": "A Minecraft Server", - "players": {"max": 20, "online": 0}, - "version": {"name": "1.8-pre1", "protocol": 44}, - } - ) - - assert response.raw == { - "description": "A Minecraft Server", - "players": {"max": 20, "online": 0}, - "version": {"name": "1.8-pre1", "protocol": 44}, - } - - def test_description(self): - response = PingResponse( - { - "description": "A Minecraft Server", - "players": {"max": 20, "online": 0}, - "version": {"name": "1.8-pre1", "protocol": 44}, - } - ) - - assert response.description == "A Minecraft Server" - - def test_description_missing(self): - with pytest.raises(ValueError): - PingResponse( - { - "players": {"max": 20, "online": 0}, - "version": {"name": "1.8-pre1", "protocol": 44}, - } - ) - - def test_parse_description_strips_html_color_codes(self): - out = PingResponse._parse_description( - { - "extra": [ - {"text": " "}, - {"strikethrough": True, "color": "#b3eeff", "text": "="}, - {"strikethrough": True, "color": "#b9ecff", "text": "="}, - {"strikethrough": True, "color": "#c0eaff", "text": "="}, - {"strikethrough": True, "color": "#c7e8ff", "text": "="}, - {"strikethrough": True, "color": "#cee6ff", "text": "="}, - {"strikethrough": True, "color": "#d5e4ff", "text": "="}, - {"strikethrough": True, "color": "#dce2ff", "text": "="}, - {"strikethrough": True, "color": "#e3e0ff", "text": "="}, - {"strikethrough": True, "color": "#eadeff", "text": "="}, - {"strikethrough": True, "color": "#f1dcff", "text": "="}, - {"strikethrough": True, "color": "#f8daff", "text": "="}, - {"strikethrough": True, "color": "#ffd9ff", "text": "="}, - {"strikethrough": True, "color": "#f4dcff", "text": "="}, - {"strikethrough": True, "color": "#f9daff", "text": "="}, - {"strikethrough": True, "color": "#ffd9ff", "text": "="}, - {"color": "white", "text": " "}, - {"bold": True, "color": "#66ff99", "text": "C"}, - {"bold": True, "color": "#75f5a2", "text": "r"}, - {"bold": True, "color": "#84ebab", "text": "e"}, - {"bold": True, "color": "#93e2b4", "text": "a"}, - {"bold": True, "color": "#a3d8bd", "text": "t"}, - {"bold": True, "color": "#b2cfc6", "text": "i"}, - {"bold": True, "color": "#c1c5cf", "text": "v"}, - {"bold": True, "color": "#d1bbd8", "text": "e"}, - {"bold": True, "color": "#e0b2e1", "text": "F"}, - {"bold": True, "color": "#efa8ea", "text": "u"}, - {"bold": True, "color": "#ff9ff4", "text": "n "}, - {"strikethrough": True, "color": "#b3eeff", "text": "="}, - {"strikethrough": True, "color": "#b9ecff", "text": "="}, - {"strikethrough": True, "color": "#c0eaff", "text": "="}, - {"strikethrough": True, "color": "#c7e8ff", "text": "="}, - {"strikethrough": True, "color": "#cee6ff", "text": "="}, - {"strikethrough": True, "color": "#d5e4ff", "text": "="}, - {"strikethrough": True, "color": "#dce2ff", "text": "="}, - {"strikethrough": True, "color": "#e3e0ff", "text": "="}, - {"strikethrough": True, "color": "#eadeff", "text": "="}, - {"strikethrough": True, "color": "#f1dcff", "text": "="}, - {"strikethrough": True, "color": "#f8daff", "text": "="}, - {"strikethrough": True, "color": "#ffd9ff", "text": "="}, - {"strikethrough": True, "color": "#f4dcff", "text": "="}, - {"strikethrough": True, "color": "#f9daff", "text": "="}, - {"strikethrough": True, "color": "#ffd9ff", "text": "="}, - {"color": "white", "text": " \n "}, - {"bold": True, "color": "#E5E5E5", "text": "The server has been updated to "}, - {"bold": True, "color": "#97ABFF", "text": "1.17.1"}, - ], - "text": "", - } - ) - assert out == ( - " ===============ยงf ยงlCยงlrยงleยงlaยงltยงliยงlvยงleยงlFยงluยงln ===============ยงf \n" - " ยงlThe server has been updated to ยงl1.17.1" - ) - - def test_parse_description(self): - out = PingResponse._parse_description("test ยง2description") - assert out == "test ยง2description" - - out = PingResponse._parse_description({"text": "ยง8ยงlfoo"}) - assert out == "ยง8ยงlfoo" - - out = PingResponse._parse_description( - { - "extra": [{"bold": True, "italic": True, "color": "gray", "text": "foo"}, {"color": "gold", "text": "bar"}], - "text": ".", - } - ) - # We don't care in which order the style prefixes are, allow any - assert out in { - "ยงlยงoยง7fooยง6bar.", - "ยงlยง7ยงofooยง6bar.", - "ยงoยงlยง7fooยง6bar.", - "ยงoยง7ยงlfooยง6bar.", - "ยง7ยงoยงlfooยง6bar.", - "ยง7ยงlยงofooยง6bar.", - } - - out = PingResponse._parse_description( - [{"bold": True, "italic": True, "color": "gray", "text": "foo"}, {"color": "gold", "text": "bar"}] - ) - assert out in { - "ยงlยงoยง7fooยง6bar", - "ยงlยง7ยงofooยง6bar", - "ยงoยงlยง7fooยง6bar", - "ยงoยง7ยงlfooยง6bar", - "ยง7ยงoยงlfooยง6bar", - "ยง7ยงlยงofooยง6bar", - } - - def test_version(self): - response = PingResponse( - { - "description": "A Minecraft Server", - "players": {"max": 20, "online": 0}, - "version": {"name": "1.8-pre1", "protocol": 44}, - } - ) - - assert response.version is not None - assert response.version.name == "1.8-pre1" - assert response.version.protocol == 44 - - def test_version_missing(self): - with pytest.raises(ValueError): - PingResponse( - { - "description": "A Minecraft Server", - "players": {"max": 20, "online": 0}, - } - ) - - def test_version_invalid(self): - with pytest.raises(ValueError): - PingResponse( - { - "description": "A Minecraft Server", - "players": {"max": 20, "online": 0}, - "version": "foo", - } - ) - - def test_players(self): - response = PingResponse( - { - "description": "A Minecraft Server", - "players": {"max": 20, "online": 5}, - "version": {"name": "1.8-pre1", "protocol": 44}, - } - ) - - assert response.players is not None - assert response.players.max == 20 - assert response.players.online == 5 - - def test_players_missing(self): - with pytest.raises(ValueError): - PingResponse( - { - "description": "A Minecraft Server", - "version": {"name": "1.8-pre1", "protocol": 44}, - } - ) - - def test_favicon(self): - response = PingResponse( - { - "description": "A Minecraft Server", - "players": {"max": 20, "online": 0}, - "version": {"name": "1.8-pre1", "protocol": 44}, - "favicon": "data:image/png;base64,foo", - } - ) - - assert response.favicon == "data:image/png;base64,foo" - - def test_favicon_missing(self): - response = PingResponse( - { - "description": "A Minecraft Server", - "players": {"max": 20, "online": 0}, - "version": {"name": "1.8-pre1", "protocol": 44}, - } - ) - - assert response.favicon is None - - -class TestPingResponsePlayers: - def test_invalid(self): - with pytest.raises(ValueError): - PingResponse.Players("foo") - - def test_max_missing(self): - with pytest.raises(ValueError): - PingResponse.Players({"online": 5}) - - def test_max_invalid(self): - with pytest.raises(ValueError): - PingResponse.Players({"max": "foo", "online": 5}) - - def test_online_missing(self): - with pytest.raises(ValueError): - PingResponse.Players({"max": 20}) - - def test_online_invalid(self): - with pytest.raises(ValueError): - PingResponse.Players({"max": 20, "online": "foo"}) - - def test_valid(self): - players = PingResponse.Players({"max": 20, "online": 5}) - - assert players.max == 20 - assert players.online == 5 - - def test_sample(self): - players = PingResponse.Players( - { - "max": 20, - "online": 1, - "sample": [{"name": "Dinnerbone", "id": "61699b2e-d327-4a01-9f1e-0ea8c3f06bc6"}], - } - ) - - assert players.sample is not None - assert players.sample[0].name == "Dinnerbone" - - def test_sample_invalid(self): - with pytest.raises(ValueError): - PingResponse.Players({"max": 20, "online": 1, "sample": "foo"}) - - def test_sample_missing(self): - players = PingResponse.Players({"max": 20, "online": 1}) - assert players.sample is None - - -class TestPingResponsePlayersPlayer: - def test_invalid(self): - with pytest.raises(ValueError): - PingResponse.Players.Player("foo") - - def test_name_missing(self): - with pytest.raises(ValueError): - PingResponse.Players.Player({"id": "61699b2e-d327-4a01-9f1e-0ea8c3f06bc6"}) - - def test_name_invalid(self): - with pytest.raises(ValueError): - PingResponse.Players.Player({"name": {}, "id": "61699b2e-d327-4a01-9f1e-0ea8c3f06bc6"}) - - def test_id_missing(self): - with pytest.raises(ValueError): - PingResponse.Players.Player({"name": "Dinnerbone"}) - - def test_id_invalid(self): - with pytest.raises(ValueError): - PingResponse.Players.Player({"name": "Dinnerbone", "id": {}}) - - def test_valid(self): - player = PingResponse.Players.Player({"name": "Dinnerbone", "id": "61699b2e-d327-4a01-9f1e-0ea8c3f06bc6"}) - - assert player.name == "Dinnerbone" - assert player.id == "61699b2e-d327-4a01-9f1e-0ea8c3f06bc6" - - -class TestPingResponseVersion: - def test_invalid(self): - with pytest.raises(ValueError): - PingResponse.Version("foo") - - def test_protocol_missing(self): - with pytest.raises(ValueError): - PingResponse.Version({"name": "foo"}) - - def test_protocol_invalid(self): - with pytest.raises(ValueError): - PingResponse.Version({"name": "foo", "protocol": "bar"}) - - def test_name_missing(self): - with pytest.raises(ValueError): - PingResponse.Version({"protocol": 5}) - - def test_name_invalid(self): - with pytest.raises(ValueError): - PingResponse.Version({"name": {}, "protocol": 5}) - - def test_valid(self): - players = PingResponse.Version({"name": "foo", "protocol": 5}) - - assert players.name == "foo" - assert players.protocol == 5 diff --git a/mcstatus/tests/test_querier.py b/mcstatus/tests/test_querier.py deleted file mode 100644 index f1ac354..0000000 --- a/mcstatus/tests/test_querier.py +++ /dev/null @@ -1,125 +0,0 @@ -from mcstatus.protocol.connection import Connection -from mcstatus.querier import QueryResponse, ServerQuerier - - -class TestMinecraftQuerier: - def setup_method(self): - self.querier = ServerQuerier(Connection()) # type: ignore[arg-type] - - def test_handshake(self): - self.querier.connection.receive(bytearray.fromhex("090000000035373033353037373800")) - self.querier.handshake() - - conn_bytes = self.querier.connection.flush() - assert conn_bytes[:3] == bytearray.fromhex("FEFD09") - assert self.querier.challenge == 570350778 - - def test_query(self): - self.querier.connection.receive( - bytearray.fromhex( - "00000000000000000000000000000000686f73746e616d650041204d696e656372616674205365727665720067616d6574797" - "06500534d500067616d655f6964004d494e4543524146540076657273696f6e00312e3800706c7567696e7300006d61700077" - "6f726c64006e756d706c61796572730033006d6178706c617965727300323000686f7374706f727400323535363500686f737" - "46970003139322e3136382e35362e31000001706c617965725f000044696e6e6572626f6e6500446a696e6e69626f6e650053" - "746576650000" - ) - ) - response = self.querier.read_query() - conn_bytes = self.querier.connection.flush() - assert conn_bytes[:3] == bytearray.fromhex("FEFD00") - assert conn_bytes[7:] == bytearray.fromhex("0000000000000000") - assert response.raw == { - "hostname": "A Minecraft Server", - "gametype": "SMP", - "game_id": "MINECRAFT", - "version": "1.8", - "plugins": "", - "map": "world", - "numplayers": "3", - "maxplayers": "20", - "hostport": "25565", - "hostip": "192.168.56.1", - } - assert response.players.names == ["Dinnerbone", "Djinnibone", "Steve"] - - def test_query_handles_unicode_motd_with_nulls(self): - self.querier.connection.receive( - bytearray( - b"\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00hostname\x00\x00*K\xd5\x00gametype\x00SMP" - b"\x00game_id\x00MINECRAFT\x00version\x001.16.5\x00plugins\x00Paper on 1.16.5-R0.1-SNAPSHOT\x00map\x00world" - b"\x00numplayers\x000\x00maxplayers\x0020\x00hostport\x0025565\x00hostip\x00127.0.1.1\x00\x00\x01player_\x00" - b"\x00\x00" - ) - ) - response = self.querier.read_query() - self.querier.connection.flush() - - assert response.raw["game_id"] == "MINECRAFT" - assert response.motd == "\x00*Kร•" - - def test_query_handles_unicode_motd_with_2a00_at_the_start(self): - self.querier.connection.receive( - bytearray.fromhex( - "00000000000000000000000000000000686f73746e616d6500006f746865720067616d657479706500534d500067616d655f6964004d" - "494e4543524146540076657273696f6e00312e31382e3100706c7567696e7300006d617000776f726c64006e756d706c617965727300" - "30006d6178706c617965727300323000686f7374706f727400323535363500686f73746970003137322e31372e302e32000001706c61" - "7965725f000000" - ) - ) - response = self.querier.read_query() - self.querier.connection.flush() - - assert response.raw["game_id"] == "MINECRAFT" - assert response.motd == "\x00other" # "\u2a00other" is actually what is expected, - # but the query protocol for vanilla has a bug when it comes to unicode handling. - # The status protocol correctly shows "โจ€other". - - -class TestQueryResponse: - def setup_method(self): - self.raw = { - "hostname": "A Minecraft Server", - "gametype": "SMP", - "game_id": "MINECRAFT", - "version": "1.8", - "plugins": "", - "map": "world", - "numplayers": "3", - "maxplayers": "20", - "hostport": "25565", - "hostip": "192.168.56.1", - } - self.players = ["Dinnerbone", "Djinnibone", "Steve"] - - def test_valid(self): - response = QueryResponse(self.raw, self.players) - assert response.motd == "A Minecraft Server" - assert response.map == "world" - assert response.players.online == 3 - assert response.players.max == 20 - assert response.players.names == ["Dinnerbone", "Djinnibone", "Steve"] - assert response.software.brand == "vanilla" - assert response.software.version == "1.8" - assert response.software.plugins == [] - - def test_valid2(self): - players = QueryResponse.Players(5, 20, ["Dinnerbone", "Djinnibone", "Steve"]) - assert players.online == 5 - assert players.max == 20 - assert players.names == ["Dinnerbone", "Djinnibone", "Steve"] - - def test_vanilla(self): - software = QueryResponse.Software("1.8", "") - assert software.brand == "vanilla" - assert software.version == "1.8" - assert software.plugins == [] - - def test_modded(self): - software = QueryResponse.Software("1.8", "A modded server: Foo 1.0; Bar 2.0; Baz 3.0") - assert software.brand == "A modded server" - assert software.plugins == ["Foo 1.0", "Bar 2.0", "Baz 3.0"] - - def test_modded_no_plugins(self): - software = QueryResponse.Software("1.8", "A modded server") - assert software.brand == "A modded server" - assert software.plugins == [] diff --git a/mcstatus/tests/test_retry_decorator.py b/mcstatus/tests/test_retry_decorator.py deleted file mode 100644 index 200b911..0000000 --- a/mcstatus/tests/test_retry_decorator.py +++ /dev/null @@ -1,66 +0,0 @@ -import pytest - -from mcstatus.tests.test_async_pinger import async_decorator -from mcstatus.utils import retry - - -def test_sync_success(): - x = -1 - - @retry(tries=2) - def func(): - nonlocal x - x += 1 - return 5 / x - - y = func() - assert x == 1 - assert y == 5 - - -def test_sync_fail(): - x = -1 - - @retry(tries=2) - def func(): - nonlocal x - x += 1 - if x == 0: - raise OSError("First error") - elif x == 1: - raise RuntimeError("Second error") - - # We should get the last exception on failure (not OSError) - with pytest.raises(RuntimeError): - func() - - -def test_async_success(): - x = -1 - - @retry(tries=2) - async def func(): - nonlocal x - x += 1 - return 5 / x - - y = async_decorator(func)() - assert x == 1 - assert y == 5 - - -def test_async_fail(): - x = -1 - - @retry(tries=2) - async def func(): - nonlocal x - x += 1 - if x == 0: - raise OSError("First error") - elif x == 1: - raise RuntimeError("Second error") - - # We should get the last exception on failure (not OSError) - with pytest.raises(RuntimeError): - async_decorator(func)() diff --git a/mcstatus/tests/test_server.py b/mcstatus/tests/test_server.py deleted file mode 100644 index 7c20213..0000000 --- a/mcstatus/tests/test_server.py +++ /dev/null @@ -1,215 +0,0 @@ -import asyncio -import sys -from unittest.mock import Mock, patch - -import pytest - -from mcstatus.protocol.connection import Connection -from mcstatus.server import MinecraftServer - - -class MockProtocolFactory(asyncio.Protocol): - transport: asyncio.Transport - - def __init__(self, data_expected_to_receive, data_to_respond_with): - self.data_expected_to_receive = data_expected_to_receive - self.data_to_respond_with = data_to_respond_with - - def connection_made(self, transport): - print("connection_made") - self.transport = transport - - def connection_lost(self, exc): - print("connection_lost") - self.transport.close() - - def data_received(self, data): - assert self.data_expected_to_receive in data - self.transport.write(self.data_to_respond_with) - - def eof_received(self): - print("eof_received") - - def pause_writing(self): - print("pause_writing") - - def resume_writing(self): - print("resume_writing") - - -@pytest.fixture() -async def create_mock_packet_server(event_loop): - servers = [] - - async def create_server(port, data_expected_to_receive, data_to_respond_with): - server = await event_loop.create_server( - lambda: MockProtocolFactory(data_expected_to_receive, data_to_respond_with), - host="localhost", - port=port, - ) - servers.append(server) - return server - - yield create_server - - for server in servers: - server.close() - await server.wait_closed() - - -class TestAsyncMinecraftServer: - @pytest.mark.skipif( - sys.platform.startswith("win"), - reason="async bug on Windows https://github.com/Dinnerbone/mcstatus/issues/140", - ) - @pytest.mark.asyncio - async def test_async_ping(self, unused_tcp_port, create_mock_packet_server): - await create_mock_packet_server( - port=unused_tcp_port, - data_expected_to_receive=bytearray.fromhex("09010000000001C54246"), - data_to_respond_with=bytearray.fromhex("0F002F096C6F63616C686F737463DD0109010000000001C54246"), - ) - minecraft_server = MinecraftServer("localhost", port=unused_tcp_port) - - latency = await minecraft_server.async_ping(ping_token=29704774, version=47) - assert latency >= 0 - - -class TestMinecraftServer: - def setup_method(self): - self.socket = Connection() - self.server = MinecraftServer("localhost", port=25565) - - def test_ping(self): - self.socket.receive(bytearray.fromhex("09010000000001C54246")) - - with patch("mcstatus.server.TCPSocketConnection") as connection: - connection.return_value = self.socket - latency = self.server.ping(ping_token=29704774, version=47) - - assert self.socket.flush() == bytearray.fromhex("0F002F096C6F63616C686F737463DD0109010000000001C54246") - assert self.socket.remaining() == 0, "Data is pending to be read, but should be empty" - assert latency >= 0 - - def test_ping_retry(self): - with patch("mcstatus.server.TCPSocketConnection") as connection: - connection.return_value = None - with patch("mcstatus.server.ServerPinger") as pinger: - pinger.side_effect = [Exception, Exception, Exception] - with pytest.raises(Exception): - self.server.ping() - assert pinger.call_count == 3 - - def test_status(self): - self.socket.receive( - bytearray.fromhex( - "6D006B7B226465736372697074696F6E223A2241204D696E65637261667420536572766572222C22706C6179657273223A7B2" - "26D6178223A32302C226F6E6C696E65223A307D2C2276657273696F6E223A7B226E616D65223A22312E38222C2270726F746F" - "636F6C223A34377D7D09010000000001C54246" - ) - ) - - with patch("mcstatus.server.TCPSocketConnection") as connection: - connection.return_value = self.socket - info = self.server.status(ping_token=29704774, version=47) - - assert self.socket.flush() == bytearray.fromhex("0F002F096C6F63616C686F737463DD01010009010000000001C54246") - assert self.socket.remaining() == 0, "Data is pending to be read, but should be empty" - assert info.raw == { - "description": "A Minecraft Server", - "players": {"max": 20, "online": 0}, - "version": {"name": "1.8", "protocol": 47}, - } - assert info.latency >= 0 - - def test_status_retry(self): - with patch("mcstatus.server.TCPSocketConnection") as connection: - connection.return_value = None - with patch("mcstatus.server.ServerPinger") as pinger: - pinger.side_effect = [Exception, Exception, Exception] - with pytest.raises(Exception): - self.server.status() - assert pinger.call_count == 3 - - def test_query(self): - self.socket.receive(bytearray.fromhex("090000000035373033353037373800")) - self.socket.receive( - bytearray.fromhex( - "00000000000000000000000000000000686f73746e616d650041204d696e656372616674205365727665720067616d6574797" - "06500534d500067616d655f6964004d494e4543524146540076657273696f6e00312e3800706c7567696e7300006d61700077" - "6f726c64006e756d706c61796572730033006d6178706c617965727300323000686f7374706f727400323535363500686f737" - "46970003139322e3136382e35362e31000001706c617965725f000044696e6e6572626f6e6500446a696e6e69626f6e650053" - "746576650000" - ) - ) - - self.socket.remaining = Mock() - self.socket.remaining.side_effect = [15, 208] - - with patch("mcstatus.server.UDPSocketConnection") as connection: - connection.return_value = self.socket - info = self.server.query() - - conn_bytes = self.socket.flush() - assert conn_bytes[:3] == bytearray.fromhex("FEFD09") - assert info.raw == { - "hostname": "A Minecraft Server", - "gametype": "SMP", - "game_id": "MINECRAFT", - "version": "1.8", - "plugins": "", - "map": "world", - "numplayers": "3", - "maxplayers": "20", - "hostport": "25565", - "hostip": "192.168.56.1", - } - - def test_query_retry(self): - with patch("mcstatus.server.UDPSocketConnection") as connection: - connection.return_value = None - with patch("mcstatus.server.ServerQuerier") as querier: - querier.side_effect = [Exception, Exception, Exception] - with pytest.raises(Exception): - self.server.query() - assert querier.call_count == 3 - - def test_by_address_no_srv(self): - with patch("dns.resolver.resolve") as resolve: - resolve.return_value = [] - self.server = MinecraftServer.lookup("example.org") - resolve.assert_called_once_with("_minecraft._tcp.example.org", "SRV") - assert self.server.host == "example.org" - assert self.server.port == 25565 - - def test_by_address_invalid_srv(self): - with patch("dns.resolver.resolve") as resolve: - resolve.side_effect = [Exception] - self.server = MinecraftServer.lookup("example.org") - resolve.assert_called_once_with("_minecraft._tcp.example.org", "SRV") - assert self.server.host == "example.org" - assert self.server.port == 25565 - - def test_by_address_with_srv(self): - with patch("dns.resolver.resolve") as resolve: - answer = Mock() - answer.target = "different.example.org." - answer.port = 12345 - resolve.return_value = [answer] - self.server = MinecraftServer.lookup("example.org") - resolve.assert_called_once_with("_minecraft._tcp.example.org", "SRV") - assert self.server.host == "different.example.org" - assert self.server.port == 12345 - - def test_by_address_with_port(self): - self.server = MinecraftServer.lookup("example.org:12345") - assert self.server.host == "example.org" - assert self.server.port == 12345 - - def test_by_address_with_multiple_ports(self): - with pytest.raises(ValueError): - MinecraftServer.lookup("example.org:12345:6789") - - def test_by_address_with_invalid_port(self): - with pytest.raises(ValueError): - MinecraftServer.lookup("example.org:port") diff --git a/mcstatus/tests/test_session_id.py b/mcstatus/tests/test_session_id.py deleted file mode 100644 index d18971c..0000000 --- a/mcstatus/tests/test_session_id.py +++ /dev/null @@ -1,23 +0,0 @@ -from unittest.mock import Mock - -from mcstatus.protocol.connection import Connection -from mcstatus.querier import ServerQuerier - - -class TestHandshake: - def setup_method(self): - self.querier = ServerQuerier(Connection()) # type: ignore[arg-type] - - def test_session_id(self): - def session_id(): - return 0x01010101 - - self.querier.connection.receive(bytearray.fromhex("090000000035373033353037373800")) - self.querier._generate_session_id = Mock() - self.querier._generate_session_id = session_id - self.querier.handshake() - - conn_bytes = self.querier.connection.flush() - assert conn_bytes[:3] == bytearray.fromhex("FEFD09") - assert conn_bytes[3:] == session_id().to_bytes(4, byteorder="big") - assert self.querier.challenge == 570350778 diff --git a/mcstatus/tests/test_timeout.py b/mcstatus/tests/test_timeout.py deleted file mode 100644 index 721f8e6..0000000 --- a/mcstatus/tests/test_timeout.py +++ /dev/null @@ -1,34 +0,0 @@ -import asyncio -from unittest.mock import patch - -import pytest - -from mcstatus.protocol.connection import TCPAsyncSocketConnection - - -class FakeAsyncStream(asyncio.StreamReader): - async def read(self, n: int) -> bytes: - await asyncio.sleep(delay=2) - return bytes([0] * n) - - -async def fake_asyncio_asyncio_open_connection(hostname: str, port: int): - return FakeAsyncStream(), None - - -class TestAsyncSocketConnection: - def setup_method(self): - self.tcp_async_socket = TCPAsyncSocketConnection() - - def test_tcp_socket_read(self): - try: - from asyncio.exceptions import TimeoutError - except ImportError: - from asyncio import TimeoutError - - loop = asyncio.get_event_loop() - with patch("asyncio.open_connection", fake_asyncio_asyncio_open_connection): - loop.run_until_complete(self.tcp_async_socket.connect(("dummy_address", 1234), timeout=0.01)) - - with pytest.raises(TimeoutError): - loop.run_until_complete(self.tcp_async_socket.read(10)) diff --git a/mcstatus/utils.py b/mcstatus/utils.py deleted file mode 100644 index 05ce050..0000000 --- a/mcstatus/utils.py +++ /dev/null @@ -1,48 +0,0 @@ -from __future__ import annotations - -import asyncio -from functools import wraps -from typing import Callable, Tuple, Type - - -def retry(tries: int, exceptions: Tuple[Type[BaseException]] = (Exception,)) -> Callable: - """ - Decorator that re-runs given function tries times if error occurs. - - The amount of tries will either be the value given to the decorator, - or if tries is present in keyword arguments on function call, this - specified value will take precedense. - - If the function fails even after all of the retries, raise the last - exception that the function raised (even if the previous failures caused - a different exception, this will only raise the last one!). - """ - - def decorate(func: Callable) -> Callable: - @wraps(func) - async def async_wrapper(*args, tries: int = tries, **kwargs): - last_exc: BaseException - for _ in range(tries): - try: - return await func(*args, **kwargs) - except exceptions as exc: - last_exc = exc - else: - raise last_exc # type: ignore # (This won't actually be unbound) - - @wraps(func) - def sync_wrapper(*args, tries: int = tries, **kwargs): - last_exc: BaseException - for _ in range(tries): - try: - return func(*args, **kwargs) - except exceptions as exc: - last_exc = exc - else: - raise last_exc # type: ignore # (This won't actually be unbound) - - if asyncio.iscoroutinefunction(func): - return async_wrapper - return sync_wrapper - - return decorate diff --git a/poetry.lock b/poetry.lock deleted file mode 100644 index 5eaa24f..0000000 --- a/poetry.lock +++ /dev/null @@ -1,1201 +0,0 @@ -[[package]] -name = "asyncio-dgram" -version = "1.2.0" -description = "Higher level Datagram support for Asyncio" -category = "main" -optional = false -python-versions = ">=3.5" - -[package.extras] -test = ["black (>=20.8b1)", "flake8 (>=3.8.3)", "pytest (>=5.4.3)", "pytest-asyncio (>=0.14.0)"] - -[[package]] -name = "atomicwrites" -version = "1.4.0" -description = "Atomic file writes." -category = "dev" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" - -[[package]] -name = "attrs" -version = "21.4.0" -description = "Classes Without Boilerplate" -category = "dev" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" - -[package.extras] -dev = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "furo", "sphinx", "sphinx-notfound-page", "pre-commit", "cloudpickle"] -docs = ["furo", "sphinx", "zope.interface", "sphinx-notfound-page"] -tests = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "zope.interface", "cloudpickle"] -tests_no_zope = ["coverage[toml] (>=5.0.2)", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "mypy", "pytest-mypy-plugins", "cloudpickle"] - -[[package]] -name = "black" -version = "22.1.0" -description = "The uncompromising code formatter." -category = "dev" -optional = false -python-versions = ">=3.6.2" - -[package.dependencies] -click = ">=8.0.0" -mypy-extensions = ">=0.4.3" -pathspec = ">=0.9.0" -platformdirs = ">=2" -tomli = ">=1.1.0" -typed-ast = {version = ">=1.4.2", markers = "python_version < \"3.8\" and implementation_name == \"cpython\""} -typing-extensions = {version = ">=3.10.0.0", markers = "python_version < \"3.10\""} - -[package.extras] -colorama = ["colorama (>=0.4.3)"] -d = ["aiohttp (>=3.7.4)"] -jupyter = ["ipython (>=7.8.0)", "tokenize-rt (>=3.2.0)"] -uvloop = ["uvloop (>=0.15.2)"] - -[[package]] -name = "bleach" -version = "4.1.0" -description = "An easy safelist-based HTML-sanitizing tool." -category = "dev" -optional = false -python-versions = ">=3.6" - -[package.dependencies] -packaging = "*" -six = ">=1.9.0" -webencodings = "*" - -[[package]] -name = "certifi" -version = "2021.10.8" -description = "Python package for providing Mozilla's CA Bundle." -category = "dev" -optional = false -python-versions = "*" - -[[package]] -name = "cffi" -version = "1.15.0" -description = "Foreign Function Interface for Python calling C code." -category = "dev" -optional = false -python-versions = "*" - -[package.dependencies] -pycparser = "*" - -[[package]] -name = "charset-normalizer" -version = "2.0.11" -description = "The Real First Universal Charset Detector. Open, modern and actively maintained alternative to Chardet." -category = "dev" -optional = false -python-versions = ">=3.5.0" - -[package.extras] -unicode_backport = ["unicodedata2"] - -[[package]] -name = "click" -version = "8.0.3" -description = "Composable command line interface toolkit" -category = "main" -optional = false -python-versions = ">=3.6" - -[package.dependencies] -colorama = {version = "*", markers = "platform_system == \"Windows\""} -importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} - -[[package]] -name = "colorama" -version = "0.4.4" -description = "Cross-platform colored terminal text." -category = "main" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" - -[[package]] -name = "coverage" -version = "6.3" -description = "Code coverage measurement for Python" -category = "dev" -optional = false -python-versions = ">=3.7" - -[package.dependencies] -tomli = {version = "*", optional = true, markers = "extra == \"toml\""} - -[package.extras] -toml = ["tomli"] - -[[package]] -name = "cryptography" -version = "36.0.1" -description = "cryptography is a package which provides cryptographic recipes and primitives to Python developers." -category = "dev" -optional = false -python-versions = ">=3.6" - -[package.dependencies] -cffi = ">=1.12" - -[package.extras] -docs = ["sphinx (>=1.6.5,!=1.8.0,!=3.1.0,!=3.1.1)", "sphinx-rtd-theme"] -docstest = ["pyenchant (>=1.6.11)", "twine (>=1.12.0)", "sphinxcontrib-spelling (>=4.0.1)"] -pep8test = ["black", "flake8", "flake8-import-order", "pep8-naming"] -sdist = ["setuptools_rust (>=0.11.4)"] -ssh = ["bcrypt (>=3.1.5)"] -test = ["pytest (>=6.2.0)", "pytest-cov", "pytest-subtests", "pytest-xdist", "pretend", "iso8601", "pytz", "hypothesis (>=1.11.4,!=3.79.2)"] - -[[package]] -name = "distlib" -version = "0.3.4" -description = "Distribution utilities" -category = "dev" -optional = false -python-versions = "*" - -[[package]] -name = "dnspython" -version = "2.1.0" -description = "DNS toolkit" -category = "main" -optional = false -python-versions = ">=3.6" - -[package.extras] -dnssec = ["cryptography (>=2.6)"] -doh = ["requests", "requests-toolbelt"] -idna = ["idna (>=2.1)"] -curio = ["curio (>=1.2)", "sniffio (>=1.1)"] -trio = ["trio (>=0.14.0)", "sniffio (>=1.1)"] - -[[package]] -name = "docutils" -version = "0.18.1" -description = "Docutils -- Python Documentation Utilities" -category = "dev" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" - -[[package]] -name = "filelock" -version = "3.4.2" -description = "A platform independent file lock." -category = "dev" -optional = false -python-versions = ">=3.7" - -[package.extras] -docs = ["furo (>=2021.8.17b43)", "sphinx (>=4.1)", "sphinx-autodoc-typehints (>=1.12)"] -testing = ["covdefaults (>=1.2.0)", "coverage (>=4)", "pytest (>=4)", "pytest-cov", "pytest-timeout (>=1.4.2)"] - -[[package]] -name = "flake8" -version = "4.0.1" -description = "the modular source code checker: pep8 pyflakes and co" -category = "dev" -optional = false -python-versions = ">=3.6" - -[package.dependencies] -importlib-metadata = {version = "<4.3", markers = "python_version < \"3.8\""} -mccabe = ">=0.6.0,<0.7.0" -pycodestyle = ">=2.8.0,<2.9.0" -pyflakes = ">=2.4.0,<2.5.0" - -[[package]] -name = "flake8-annotations" -version = "2.7.0" -description = "Flake8 Type Annotation Checks" -category = "dev" -optional = false -python-versions = ">=3.6.2,<4.0.0" - -[package.dependencies] -flake8 = ">=3.7,<5.0" -typed-ast = {version = ">=1.4,<2.0", markers = "python_version < \"3.8\""} - -[[package]] -name = "flake8-bugbear" -version = "22.1.11" -description = "A plugin for flake8 finding likely bugs and design problems in your program. Contains warnings that don't belong in pyflakes and pycodestyle." -category = "dev" -optional = false -python-versions = ">=3.6" - -[package.dependencies] -attrs = ">=19.2.0" -flake8 = ">=3.0.0" - -[package.extras] -dev = ["coverage", "hypothesis", "hypothesmith (>=0.2)", "pre-commit"] - -[[package]] -name = "flake8-import-order" -version = "0.18.1" -description = "Flake8 and pylama plugin that checks the ordering of import statements." -category = "dev" -optional = false -python-versions = "*" - -[package.dependencies] -pycodestyle = "*" - -[[package]] -name = "flake8-polyfill" -version = "1.0.2" -description = "Polyfill package for Flake8 plugins" -category = "dev" -optional = false -python-versions = "*" - -[package.dependencies] -flake8 = "*" - -[[package]] -name = "flake8-tidy-imports" -version = "4.6.0" -description = "A flake8 plugin that helps you write tidier imports." -category = "dev" -optional = false -python-versions = ">=3.7" - -[package.dependencies] -flake8 = ">=3.8.0" -importlib-metadata = {version = "*", markers = "python_version < \"3.8\""} - -[[package]] -name = "idna" -version = "3.3" -description = "Internationalized Domain Names in Applications (IDNA)" -category = "dev" -optional = false -python-versions = ">=3.5" - -[[package]] -name = "importlib-metadata" -version = "4.2.0" -description = "Read metadata from Python packages" -category = "main" -optional = false -python-versions = ">=3.6" - -[package.dependencies] -typing-extensions = {version = ">=3.6.4", markers = "python_version < \"3.8\""} -zipp = ">=0.5" - -[package.extras] -docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] -testing = ["pytest (>=4.6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "packaging", "pep517", "pyfakefs", "flufl.flake8", "pytest-black (>=0.3.7)", "pytest-mypy", "importlib-resources (>=1.3)"] - -[[package]] -name = "iniconfig" -version = "1.1.1" -description = "iniconfig: brain-dead simple config-ini parsing" -category = "dev" -optional = false -python-versions = "*" - -[[package]] -name = "jeepney" -version = "0.7.1" -description = "Low-level, pure Python DBus protocol wrapper." -category = "dev" -optional = false -python-versions = ">=3.6" - -[package.extras] -test = ["pytest", "pytest-trio", "pytest-asyncio", "testpath", "trio", "async-timeout"] -trio = ["trio", "async-generator"] - -[[package]] -name = "keyring" -version = "23.5.0" -description = "Store and access your passwords safely." -category = "dev" -optional = false -python-versions = ">=3.7" - -[package.dependencies] -importlib-metadata = ">=3.6" -jeepney = {version = ">=0.4.2", markers = "sys_platform == \"linux\""} -pywin32-ctypes = {version = "<0.1.0 || >0.1.0,<0.1.1 || >0.1.1", markers = "sys_platform == \"win32\""} -SecretStorage = {version = ">=3.2", markers = "sys_platform == \"linux\""} - -[package.extras] -docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)", "jaraco.tidelift (>=1.4)"] -testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "pytest-black (>=0.3.7)", "pytest-mypy"] - -[[package]] -name = "mccabe" -version = "0.6.1" -description = "McCabe checker, plugin for flake8" -category = "dev" -optional = false -python-versions = "*" - -[[package]] -name = "mypy-extensions" -version = "0.4.3" -description = "Experimental type system extensions for programs checked with the mypy typechecker." -category = "dev" -optional = false -python-versions = "*" - -[[package]] -name = "nodeenv" -version = "1.6.0" -description = "Node.js virtual environment builder" -category = "dev" -optional = false -python-versions = "*" - -[[package]] -name = "packaging" -version = "21.3" -description = "Core utilities for Python packages" -category = "dev" -optional = false -python-versions = ">=3.6" - -[package.dependencies] -pyparsing = ">=2.0.2,<3.0.5 || >3.0.5" - -[[package]] -name = "pathspec" -version = "0.9.0" -description = "Utility library for gitignore style pattern matching of file paths." -category = "dev" -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" - -[[package]] -name = "pep8-naming" -version = "0.12.1" -description = "Check PEP-8 naming conventions, plugin for flake8" -category = "dev" -optional = false -python-versions = "*" - -[package.dependencies] -flake8 = ">=3.9.1" -flake8-polyfill = ">=1.0.2,<2" - -[[package]] -name = "pkginfo" -version = "1.8.2" -description = "Query metadatdata from sdists / bdists / installed packages." -category = "dev" -optional = false -python-versions = "*" - -[package.extras] -testing = ["coverage", "nose"] - -[[package]] -name = "platformdirs" -version = "2.4.1" -description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." -category = "dev" -optional = false -python-versions = ">=3.7" - -[package.extras] -docs = ["Sphinx (>=4)", "furo (>=2021.7.5b38)", "proselint (>=0.10.2)", "sphinx-autodoc-typehints (>=1.12)"] -test = ["appdirs (==1.4.4)", "pytest (>=6)", "pytest-cov (>=2.7)", "pytest-mock (>=3.6)"] - -[[package]] -name = "pluggy" -version = "1.0.0" -description = "plugin and hook calling mechanisms for python" -category = "dev" -optional = false -python-versions = ">=3.6" - -[package.dependencies] -importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} - -[package.extras] -dev = ["pre-commit", "tox"] -testing = ["pytest", "pytest-benchmark"] - -[[package]] -name = "py" -version = "1.11.0" -description = "library with cross-python path, ini-parsing, io, code, log facilities" -category = "dev" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" - -[[package]] -name = "pycodestyle" -version = "2.8.0" -description = "Python style guide checker" -category = "dev" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" - -[[package]] -name = "pycparser" -version = "2.21" -description = "C parser in Python" -category = "dev" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" - -[[package]] -name = "pyflakes" -version = "2.4.0" -description = "passive checker of Python programs" -category = "dev" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" - -[[package]] -name = "pygments" -version = "2.11.2" -description = "Pygments is a syntax highlighting package written in Python." -category = "dev" -optional = false -python-versions = ">=3.5" - -[[package]] -name = "pyparsing" -version = "3.0.7" -description = "Python parsing module" -category = "dev" -optional = false -python-versions = ">=3.6" - -[package.extras] -diagrams = ["jinja2", "railroad-diagrams"] - -[[package]] -name = "pyright" -version = "0.0.13" -description = "Command line wrapper for pyright" -category = "dev" -optional = false -python-versions = ">=3" - -[package.dependencies] -nodeenv = ">=1.6.0" -typing-extensions = {version = ">=3.7", markers = "python_version < \"3.8\""} - -[package.extras] -all = ["twine (>=3.4.1)"] -dev = ["twine (>=3.4.1)"] - -[[package]] -name = "pytest" -version = "6.2.5" -description = "pytest: simple powerful testing with Python" -category = "dev" -optional = false -python-versions = ">=3.6" - -[package.dependencies] -atomicwrites = {version = ">=1.0", markers = "sys_platform == \"win32\""} -attrs = ">=19.2.0" -colorama = {version = "*", markers = "sys_platform == \"win32\""} -importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} -iniconfig = "*" -packaging = "*" -pluggy = ">=0.12,<2.0" -py = ">=1.8.2" -toml = "*" - -[package.extras] -testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"] - -[[package]] -name = "pytest-asyncio" -version = "0.16.0" -description = "Pytest support for asyncio." -category = "dev" -optional = false -python-versions = ">= 3.6" - -[package.dependencies] -pytest = ">=5.4.0" - -[package.extras] -testing = ["coverage", "hypothesis (>=5.7.1)"] - -[[package]] -name = "pytest-cov" -version = "3.0.0" -description = "Pytest plugin for measuring coverage." -category = "dev" -optional = false -python-versions = ">=3.6" - -[package.dependencies] -coverage = {version = ">=5.2.1", extras = ["toml"]} -pytest = ">=4.6" - -[package.extras] -testing = ["fields", "hunter", "process-tests", "six", "pytest-xdist", "virtualenv"] - -[[package]] -name = "pywin32-ctypes" -version = "0.2.0" -description = "" -category = "dev" -optional = false -python-versions = "*" - -[[package]] -name = "readme-renderer" -version = "32.0" -description = "readme_renderer is a library for rendering \"readme\" descriptions for Warehouse" -category = "dev" -optional = false -python-versions = ">=3.6" - -[package.dependencies] -bleach = ">=2.1.0" -docutils = ">=0.13.1" -Pygments = ">=2.5.1" - -[package.extras] -md = ["cmarkgfm (>=0.5.0,<0.7.0)"] - -[[package]] -name = "requests" -version = "2.27.1" -description = "Python HTTP for Humans." -category = "dev" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*" - -[package.dependencies] -certifi = ">=2017.4.17" -charset-normalizer = {version = ">=2.0.0,<2.1.0", markers = "python_version >= \"3\""} -idna = {version = ">=2.5,<4", markers = "python_version >= \"3\""} -urllib3 = ">=1.21.1,<1.27" - -[package.extras] -socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"] -use_chardet_on_py3 = ["chardet (>=3.0.2,<5)"] - -[[package]] -name = "requests-toolbelt" -version = "0.9.1" -description = "A utility belt for advanced users of python-requests" -category = "dev" -optional = false -python-versions = "*" - -[package.dependencies] -requests = ">=2.0.1,<3.0.0" - -[[package]] -name = "rfc3986" -version = "2.0.0" -description = "Validating URI References per RFC 3986" -category = "dev" -optional = false -python-versions = ">=3.7" - -[package.extras] -idna2008 = ["idna"] - -[[package]] -name = "secretstorage" -version = "3.3.1" -description = "Python bindings to FreeDesktop.org Secret Service API" -category = "dev" -optional = false -python-versions = ">=3.6" - -[package.dependencies] -cryptography = ">=2.0" -jeepney = ">=0.6" - -[[package]] -name = "six" -version = "1.16.0" -description = "Python 2 and 3 compatibility utilities" -category = "dev" -optional = false -python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" - -[[package]] -name = "toml" -version = "0.10.2" -description = "Python Library for Tom's Obvious, Minimal Language" -category = "dev" -optional = false -python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" - -[[package]] -name = "tomli" -version = "2.0.0" -description = "A lil' TOML parser" -category = "dev" -optional = false -python-versions = ">=3.7" - -[[package]] -name = "tox" -version = "3.24.5" -description = "tox is a generic virtualenv management and test command line tool" -category = "dev" -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" - -[package.dependencies] -colorama = {version = ">=0.4.1", markers = "platform_system == \"Windows\""} -filelock = ">=3.0.0" -importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} -packaging = ">=14" -pluggy = ">=0.12.0" -py = ">=1.4.17" -six = ">=1.14.0" -toml = ">=0.9.4" -virtualenv = ">=16.0.0,<20.0.0 || >20.0.0,<20.0.1 || >20.0.1,<20.0.2 || >20.0.2,<20.0.3 || >20.0.3,<20.0.4 || >20.0.4,<20.0.5 || >20.0.5,<20.0.6 || >20.0.6,<20.0.7 || >20.0.7" - -[package.extras] -docs = ["pygments-github-lexers (>=0.0.5)", "sphinx (>=2.0.0)", "sphinxcontrib-autoprogram (>=0.1.5)", "towncrier (>=18.5.0)"] -testing = ["flaky (>=3.4.0)", "freezegun (>=0.3.11)", "pytest (>=4.0.0)", "pytest-cov (>=2.5.1)", "pytest-mock (>=1.10.0)", "pytest-randomly (>=1.0.0)", "psutil (>=5.6.1)", "pathlib2 (>=2.3.3)"] - -[[package]] -name = "tox-poetry" -version = "0.4.1" -description = "Tox poetry plugin" -category = "dev" -optional = false -python-versions = "*" - -[package.dependencies] -pluggy = "*" -toml = "*" -tox = {version = ">=3.7.0", markers = "python_version >= \"3\""} - -[package.extras] -test = ["coverage", "pytest", "pycodestyle", "pylint"] - -[[package]] -name = "tqdm" -version = "4.62.3" -description = "Fast, Extensible Progress Meter" -category = "dev" -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,>=2.7" - -[package.dependencies] -colorama = {version = "*", markers = "platform_system == \"Windows\""} - -[package.extras] -dev = ["py-make (>=0.1.0)", "twine", "wheel"] -notebook = ["ipywidgets (>=6)"] -telegram = ["requests"] - -[[package]] -name = "twine" -version = "3.7.1" -description = "Collection of utilities for publishing packages on PyPI" -category = "dev" -optional = false -python-versions = ">=3.6" - -[package.dependencies] -colorama = ">=0.4.3" -importlib-metadata = ">=3.6" -keyring = ">=15.1" -pkginfo = ">=1.8.1" -readme-renderer = ">=21.0" -requests = ">=2.20" -requests-toolbelt = ">=0.8.0,<0.9.0 || >0.9.0" -rfc3986 = ">=1.4.0" -tqdm = ">=4.14" - -[[package]] -name = "typed-ast" -version = "1.5.2" -description = "a fork of Python 2 and 3 ast modules with type comment support" -category = "dev" -optional = false -python-versions = ">=3.6" - -[[package]] -name = "typing-extensions" -version = "4.0.1" -description = "Backported and Experimental Type Hints for Python 3.6+" -category = "main" -optional = false -python-versions = ">=3.6" - -[[package]] -name = "urllib3" -version = "1.22" -description = "HTTP library with thread-safe connection pooling, file post, and more." -category = "dev" -optional = false -python-versions = "*" - -[package.extras] -secure = ["pyOpenSSL (>=0.14)", "cryptography (>=1.3.4)", "idna (>=2.0.0)", "certifi", "ipaddress"] -socks = ["PySocks (>=1.5.6,!=1.5.7,<2.0)"] - -[[package]] -name = "virtualenv" -version = "20.13.0" -description = "Virtual Python Environment builder" -category = "dev" -optional = false -python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" - -[package.dependencies] -distlib = ">=0.3.1,<1" -filelock = ">=3.2,<4" -importlib-metadata = {version = ">=0.12", markers = "python_version < \"3.8\""} -platformdirs = ">=2,<3" -six = ">=1.9.0,<2" - -[package.extras] -docs = ["proselint (>=0.10.2)", "sphinx (>=3)", "sphinx-argparse (>=0.2.5)", "sphinx-rtd-theme (>=0.4.3)", "towncrier (>=21.3)"] -testing = ["coverage (>=4)", "coverage-enable-subprocess (>=1)", "flaky (>=3)", "pytest (>=4)", "pytest-env (>=0.6.2)", "pytest-freezegun (>=0.4.1)", "pytest-mock (>=2)", "pytest-randomly (>=1)", "pytest-timeout (>=1)", "packaging (>=20.0)"] - -[[package]] -name = "webencodings" -version = "0.5.1" -description = "Character encoding aliases for legacy web content" -category = "dev" -optional = false -python-versions = "*" - -[[package]] -name = "zipp" -version = "3.7.0" -description = "Backport of pathlib-compatible object wrapper for zip files" -category = "main" -optional = false -python-versions = ">=3.7" - -[package.extras] -docs = ["sphinx", "jaraco.packaging (>=8.2)", "rst.linker (>=1.9)"] -testing = ["pytest (>=6)", "pytest-checkdocs (>=2.4)", "pytest-flake8", "pytest-cov", "pytest-enabler (>=1.0.1)", "jaraco.itertools", "func-timeout", "pytest-black (>=0.3.7)", "pytest-mypy"] - -[metadata] -lock-version = "1.1" -python-versions = ">=3.7,<4" -content-hash = "1e191c4753a18e38a6a8ba8f3218191db66fa8cfc6a9aa2b14c8577641216dc8" - -[metadata.files] -asyncio-dgram = [ - {file = "asyncio-dgram-1.2.0.tar.gz", hash = "sha256:c5464927f3ebc9a32aa6bbb3676fc1d645ae8bc9b4597feac8ab527b6fc33f38"}, - {file = "asyncio_dgram-1.2.0-py3-none-any.whl", hash = "sha256:d969f2c033542dbddab2fd8c0add51b62c3e01b686a21f84b8d3011cdfd4ce89"}, -] -atomicwrites = [ - {file = "atomicwrites-1.4.0-py2.py3-none-any.whl", hash = "sha256:6d1784dea7c0c8d4a5172b6c620f40b6e4cbfdf96d783691f2e1302a7b88e197"}, - {file = "atomicwrites-1.4.0.tar.gz", hash = "sha256:ae70396ad1a434f9c7046fd2dd196fc04b12f9e91ffb859164193be8b6168a7a"}, -] -attrs = [ - {file = "attrs-21.4.0-py2.py3-none-any.whl", hash = "sha256:2d27e3784d7a565d36ab851fe94887c5eccd6a463168875832a1be79c82828b4"}, - {file = "attrs-21.4.0.tar.gz", hash = "sha256:626ba8234211db98e869df76230a137c4c40a12d72445c45d5f5b716f076e2fd"}, -] -black = [ - {file = "black-22.1.0-cp310-cp310-macosx_10_9_universal2.whl", hash = "sha256:1297c63b9e1b96a3d0da2d85d11cd9bf8664251fd69ddac068b98dc4f34f73b6"}, - {file = "black-22.1.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:2ff96450d3ad9ea499fc4c60e425a1439c2120cbbc1ab959ff20f7c76ec7e866"}, - {file = "black-22.1.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:0e21e1f1efa65a50e3960edd068b6ae6d64ad6235bd8bfea116a03b21836af71"}, - {file = "black-22.1.0-cp310-cp310-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:e2f69158a7d120fd641d1fa9a921d898e20d52e44a74a6fbbcc570a62a6bc8ab"}, - {file = "black-22.1.0-cp310-cp310-win_amd64.whl", hash = "sha256:228b5ae2c8e3d6227e4bde5920d2fc66cc3400fde7bcc74f480cb07ef0b570d5"}, - {file = "black-22.1.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:b1a5ed73ab4c482208d20434f700d514f66ffe2840f63a6252ecc43a9bc77e8a"}, - {file = "black-22.1.0-cp36-cp36m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:35944b7100af4a985abfcaa860b06af15590deb1f392f06c8683b4381e8eeaf0"}, - {file = "black-22.1.0-cp36-cp36m-win_amd64.whl", hash = "sha256:7835fee5238fc0a0baf6c9268fb816b5f5cd9b8793423a75e8cd663c48d073ba"}, - {file = "black-22.1.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:dae63f2dbf82882fa3b2a3c49c32bffe144970a573cd68d247af6560fc493ae1"}, - {file = "black-22.1.0-cp37-cp37m-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5fa1db02410b1924b6749c245ab38d30621564e658297484952f3d8a39fce7e8"}, - {file = "black-22.1.0-cp37-cp37m-win_amd64.whl", hash = "sha256:c8226f50b8c34a14608b848dc23a46e5d08397d009446353dad45e04af0c8e28"}, - {file = "black-22.1.0-cp38-cp38-macosx_10_9_universal2.whl", hash = "sha256:2d6f331c02f0f40aa51a22e479c8209d37fcd520c77721c034517d44eecf5912"}, - {file = "black-22.1.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:742ce9af3086e5bd07e58c8feb09dbb2b047b7f566eb5f5bc63fd455814979f3"}, - {file = "black-22.1.0-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:fdb8754b453fb15fad3f72cd9cad3e16776f0964d67cf30ebcbf10327a3777a3"}, - {file = "black-22.1.0-cp38-cp38-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:f5660feab44c2e3cb24b2419b998846cbb01c23c7fe645fee45087efa3da2d61"}, - {file = "black-22.1.0-cp38-cp38-win_amd64.whl", hash = "sha256:6f2f01381f91c1efb1451998bd65a129b3ed6f64f79663a55fe0e9b74a5f81fd"}, - {file = "black-22.1.0-cp39-cp39-macosx_10_9_universal2.whl", hash = "sha256:efbadd9b52c060a8fc3b9658744091cb33c31f830b3f074422ed27bad2b18e8f"}, - {file = "black-22.1.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:8871fcb4b447206904932b54b567923e5be802b9b19b744fdff092bd2f3118d0"}, - {file = "black-22.1.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ccad888050f5393f0d6029deea2a33e5ae371fd182a697313bdbd835d3edaf9c"}, - {file = "black-22.1.0-cp39-cp39-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:07e5c049442d7ca1a2fc273c79d1aecbbf1bc858f62e8184abe1ad175c4f7cc2"}, - {file = "black-22.1.0-cp39-cp39-win_amd64.whl", hash = "sha256:373922fc66676133ddc3e754e4509196a8c392fec3f5ca4486673e685a421321"}, - {file = "black-22.1.0-py3-none-any.whl", hash = "sha256:3524739d76b6b3ed1132422bf9d82123cd1705086723bc3e235ca39fd21c667d"}, - {file = "black-22.1.0.tar.gz", hash = "sha256:a7c0192d35635f6fc1174be575cb7915e92e5dd629ee79fdaf0dcfa41a80afb5"}, -] -bleach = [ - {file = "bleach-4.1.0-py2.py3-none-any.whl", hash = "sha256:4d2651ab93271d1129ac9cbc679f524565cc8a1b791909c4a51eac4446a15994"}, - {file = "bleach-4.1.0.tar.gz", hash = "sha256:0900d8b37eba61a802ee40ac0061f8c2b5dee29c1927dd1d233e075ebf5a71da"}, -] -certifi = [ - {file = "certifi-2021.10.8-py2.py3-none-any.whl", hash = "sha256:d62a0163eb4c2344ac042ab2bdf75399a71a2d8c7d47eac2e2ee91b9d6339569"}, - {file = "certifi-2021.10.8.tar.gz", hash = "sha256:78884e7c1d4b00ce3cea67b44566851c4343c120abd683433ce934a68ea58872"}, -] -cffi = [ - {file = "cffi-1.15.0-cp27-cp27m-macosx_10_9_x86_64.whl", hash = "sha256:c2502a1a03b6312837279c8c1bd3ebedf6c12c4228ddbad40912d671ccc8a962"}, - {file = "cffi-1.15.0-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:23cfe892bd5dd8941608f93348c0737e369e51c100d03718f108bf1add7bd6d0"}, - {file = "cffi-1.15.0-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:41d45de54cd277a7878919867c0f08b0cf817605e4eb94093e7516505d3c8d14"}, - {file = "cffi-1.15.0-cp27-cp27m-win32.whl", hash = "sha256:4a306fa632e8f0928956a41fa8e1d6243c71e7eb59ffbd165fc0b41e316b2474"}, - {file = "cffi-1.15.0-cp27-cp27m-win_amd64.whl", hash = "sha256:e7022a66d9b55e93e1a845d8c9eba2a1bebd4966cd8bfc25d9cd07d515b33fa6"}, - {file = "cffi-1.15.0-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:14cd121ea63ecdae71efa69c15c5543a4b5fbcd0bbe2aad864baca0063cecf27"}, - {file = "cffi-1.15.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:d4d692a89c5cf08a8557fdeb329b82e7bf609aadfaed6c0d79f5a449a3c7c023"}, - {file = "cffi-1.15.0-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:0104fb5ae2391d46a4cb082abdd5c69ea4eab79d8d44eaaf79f1b1fd806ee4c2"}, - {file = "cffi-1.15.0-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:91ec59c33514b7c7559a6acda53bbfe1b283949c34fe7440bcf917f96ac0723e"}, - {file = "cffi-1.15.0-cp310-cp310-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:f5c7150ad32ba43a07c4479f40241756145a1f03b43480e058cfd862bf5041c7"}, - {file = "cffi-1.15.0-cp310-cp310-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:00c878c90cb53ccfaae6b8bc18ad05d2036553e6d9d1d9dbcf323bbe83854ca3"}, - {file = "cffi-1.15.0-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:abb9a20a72ac4e0fdb50dae135ba5e77880518e742077ced47eb1499e29a443c"}, - {file = "cffi-1.15.0-cp310-cp310-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:a5263e363c27b653a90078143adb3d076c1a748ec9ecc78ea2fb916f9b861962"}, - {file = "cffi-1.15.0-cp310-cp310-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:f54a64f8b0c8ff0b64d18aa76675262e1700f3995182267998c31ae974fbc382"}, - {file = "cffi-1.15.0-cp310-cp310-win32.whl", hash = "sha256:c21c9e3896c23007803a875460fb786118f0cdd4434359577ea25eb556e34c55"}, - {file = "cffi-1.15.0-cp310-cp310-win_amd64.whl", hash = "sha256:5e069f72d497312b24fcc02073d70cb989045d1c91cbd53979366077959933e0"}, - {file = "cffi-1.15.0-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:64d4ec9f448dfe041705426000cc13e34e6e5bb13736e9fd62e34a0b0c41566e"}, - {file = "cffi-1.15.0-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:2756c88cbb94231c7a147402476be2c4df2f6078099a6f4a480d239a8817ae39"}, - {file = "cffi-1.15.0-cp36-cp36m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3b96a311ac60a3f6be21d2572e46ce67f09abcf4d09344c49274eb9e0bf345fc"}, - {file = "cffi-1.15.0-cp36-cp36m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:75e4024375654472cc27e91cbe9eaa08567f7fbdf822638be2814ce059f58032"}, - {file = "cffi-1.15.0-cp36-cp36m-manylinux_2_5_i686.manylinux1_i686.whl", hash = "sha256:59888172256cac5629e60e72e86598027aca6bf01fa2465bdb676d37636573e8"}, - {file = "cffi-1.15.0-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:27c219baf94952ae9d50ec19651a687b826792055353d07648a5695413e0c605"}, - {file = "cffi-1.15.0-cp36-cp36m-win32.whl", hash = "sha256:4958391dbd6249d7ad855b9ca88fae690783a6be9e86df65865058ed81fc860e"}, - {file = "cffi-1.15.0-cp36-cp36m-win_amd64.whl", hash = "sha256:f6f824dc3bce0edab5f427efcfb1d63ee75b6fcb7282900ccaf925be84efb0fc"}, - {file = "cffi-1.15.0-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:06c48159c1abed75c2e721b1715c379fa3200c7784271b3c46df01383b593636"}, - {file = "cffi-1.15.0-cp37-cp37m-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:c2051981a968d7de9dd2d7b87bcb9c939c74a34626a6e2f8181455dd49ed69e4"}, - {file = "cffi-1.15.0-cp37-cp37m-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:fd8a250edc26254fe5b33be00402e6d287f562b6a5b2152dec302fa15bb3e997"}, - {file = "cffi-1.15.0-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:91d77d2a782be4274da750752bb1650a97bfd8f291022b379bb8e01c66b4e96b"}, - {file = "cffi-1.15.0-cp37-cp37m-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:45db3a33139e9c8f7c09234b5784a5e33d31fd6907800b316decad50af323ff2"}, - {file = "cffi-1.15.0-cp37-cp37m-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:263cc3d821c4ab2213cbe8cd8b355a7f72a8324577dc865ef98487c1aeee2bc7"}, - {file = "cffi-1.15.0-cp37-cp37m-win32.whl", hash = "sha256:17771976e82e9f94976180f76468546834d22a7cc404b17c22df2a2c81db0c66"}, - {file = "cffi-1.15.0-cp37-cp37m-win_amd64.whl", hash = "sha256:3415c89f9204ee60cd09b235810be700e993e343a408693e80ce7f6a40108029"}, - {file = "cffi-1.15.0-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:4238e6dab5d6a8ba812de994bbb0a79bddbdf80994e4ce802b6f6f3142fcc880"}, - {file = "cffi-1.15.0-cp38-cp38-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:0808014eb713677ec1292301ea4c81ad277b6cdf2fdd90fd540af98c0b101d20"}, - {file = "cffi-1.15.0-cp38-cp38-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:57e9ac9ccc3101fac9d6014fba037473e4358ef4e89f8e181f8951a2c0162024"}, - {file = "cffi-1.15.0-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:8b6c2ea03845c9f501ed1313e78de148cd3f6cad741a75d43a29b43da27f2e1e"}, - {file = "cffi-1.15.0-cp38-cp38-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:10dffb601ccfb65262a27233ac273d552ddc4d8ae1bf93b21c94b8511bffe728"}, - {file = "cffi-1.15.0-cp38-cp38-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:786902fb9ba7433aae840e0ed609f45c7bcd4e225ebb9c753aa39725bb3e6ad6"}, - {file = "cffi-1.15.0-cp38-cp38-win32.whl", hash = "sha256:da5db4e883f1ce37f55c667e5c0de439df76ac4cb55964655906306918e7363c"}, - {file = "cffi-1.15.0-cp38-cp38-win_amd64.whl", hash = "sha256:181dee03b1170ff1969489acf1c26533710231c58f95534e3edac87fff06c443"}, - {file = "cffi-1.15.0-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:45e8636704eacc432a206ac7345a5d3d2c62d95a507ec70d62f23cd91770482a"}, - {file = "cffi-1.15.0-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:31fb708d9d7c3f49a60f04cf5b119aeefe5644daba1cd2a0fe389b674fd1de37"}, - {file = "cffi-1.15.0-cp39-cp39-manylinux_2_12_i686.manylinux2010_i686.whl", hash = "sha256:6dc2737a3674b3e344847c8686cf29e500584ccad76204efea14f451d4cc669a"}, - {file = "cffi-1.15.0-cp39-cp39-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:74fdfdbfdc48d3f47148976f49fab3251e550a8720bebc99bf1483f5bfb5db3e"}, - {file = "cffi-1.15.0-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:ffaa5c925128e29efbde7301d8ecaf35c8c60ffbcd6a1ffd3a552177c8e5e796"}, - {file = "cffi-1.15.0-cp39-cp39-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:3f7d084648d77af029acb79a0ff49a0ad7e9d09057a9bf46596dac9514dc07df"}, - {file = "cffi-1.15.0-cp39-cp39-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:ef1f279350da2c586a69d32fc8733092fd32cc8ac95139a00377841f59a3f8d8"}, - {file = "cffi-1.15.0-cp39-cp39-win32.whl", hash = "sha256:2a23af14f408d53d5e6cd4e3d9a24ff9e05906ad574822a10563efcef137979a"}, - {file = "cffi-1.15.0-cp39-cp39-win_amd64.whl", hash = "sha256:3773c4d81e6e818df2efbc7dd77325ca0dcb688116050fb2b3011218eda36139"}, - {file = "cffi-1.15.0.tar.gz", hash = "sha256:920f0d66a896c2d99f0adbb391f990a84091179542c205fa53ce5787aff87954"}, -] -charset-normalizer = [ - {file = "charset-normalizer-2.0.11.tar.gz", hash = "sha256:98398a9d69ee80548c762ba991a4728bfc3836768ed226b3945908d1a688371c"}, - {file = "charset_normalizer-2.0.11-py3-none-any.whl", hash = "sha256:2842d8f5e82a1f6aa437380934d5e1cd4fcf2003b06fed6940769c164a480a45"}, -] -click = [ - {file = "click-8.0.3-py3-none-any.whl", hash = "sha256:353f466495adaeb40b6b5f592f9f91cb22372351c84caeb068132442a4518ef3"}, - {file = "click-8.0.3.tar.gz", hash = "sha256:410e932b050f5eed773c4cda94de75971c89cdb3155a72a0831139a79e5ecb5b"}, -] -colorama = [ - {file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2"}, - {file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b"}, -] -coverage = [ - {file = "coverage-6.3-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:e8071e7d9ba9f457fc674afc3de054450be2c9b195c470147fbbc082468d8ff7"}, - {file = "coverage-6.3-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:86c91c511853dfda81c2cf2360502cb72783f4b7cebabef27869f00cbe1db07d"}, - {file = "coverage-6.3-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3c4ce3b647bd1792d4394f5690d9df6dc035b00bcdbc5595099c01282a59ae01"}, - {file = "coverage-6.3-cp310-cp310-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:2a491e159294d756e7fc8462f98175e2d2225e4dbe062cca7d3e0d5a75ba6260"}, - {file = "coverage-6.3-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:5d008e0f67ac800b0ca04d7914b8501312c8c6c00ad8c7ba17754609fae1231a"}, - {file = "coverage-6.3-cp310-cp310-musllinux_1_1_aarch64.whl", hash = "sha256:4578728c36de2801c1deb1c6b760d31883e62e33f33c7ba8f982e609dc95167d"}, - {file = "coverage-6.3-cp310-cp310-musllinux_1_1_i686.whl", hash = "sha256:7ee317486593193e066fc5e98ac0ce712178c21529a85c07b7cb978171f25d53"}, - {file = "coverage-6.3-cp310-cp310-musllinux_1_1_x86_64.whl", hash = "sha256:2bc85664b06ba42d14bb74d6ddf19d8bfc520cb660561d2d9ce5786ae72f71b5"}, - {file = "coverage-6.3-cp310-cp310-win32.whl", hash = "sha256:27a94db5dc098c25048b0aca155f5fac674f2cf1b1736c5272ba28ead2fc267e"}, - {file = "coverage-6.3-cp310-cp310-win_amd64.whl", hash = "sha256:bde4aeabc0d1b2e52c4036c54440b1ad05beeca8113f47aceb4998bb7471e2c2"}, - {file = "coverage-6.3-cp311-cp311-macosx_10_14_x86_64.whl", hash = "sha256:509c68c3e2015022aeda03b003dd68fa19987cdcf64e9d4edc98db41cfc45d30"}, - {file = "coverage-6.3-cp311-cp311-manylinux_2_5_x86_64.manylinux1_x86_64.whl", hash = "sha256:e4ff163602c5c77e7bb4ea81ba5d3b793b4419f8acd296aae149370902cf4e92"}, - {file = "coverage-6.3-cp311-cp311-win_amd64.whl", hash = "sha256:d1675db48490e5fa0b300f6329ecb8a9a37c29b9ab64fa9c964d34111788ca2d"}, - {file = "coverage-6.3-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:7eed8459a2b81848cafb3280b39d7d49950d5f98e403677941c752e7e7ee47cb"}, - {file = "coverage-6.3-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:1b4285fde5286b946835a1a53bba3ad41ef74285ba9e8013e14b5ea93deaeafc"}, - {file = "coverage-6.3-cp37-cp37m-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:a4748349734110fd32d46ff8897b561e6300d8989a494ad5a0a2e4f0ca974fc7"}, - {file = "coverage-6.3-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:823f9325283dc9565ba0aa2d240471a93ca8999861779b2b6c7aded45b58ee0f"}, - {file = "coverage-6.3-cp37-cp37m-musllinux_1_1_aarch64.whl", hash = "sha256:fff16a30fdf57b214778eff86391301c4509e327a65b877862f7c929f10a4253"}, - {file = "coverage-6.3-cp37-cp37m-musllinux_1_1_i686.whl", hash = "sha256:da1a428bdbe71f9a8c270c7baab29e9552ac9d0e0cba5e7e9a4c9ee6465d258d"}, - {file = "coverage-6.3-cp37-cp37m-musllinux_1_1_x86_64.whl", hash = "sha256:7d82c610a2e10372e128023c5baf9ce3d270f3029fe7274ff5bc2897c68f1318"}, - {file = "coverage-6.3-cp37-cp37m-win32.whl", hash = "sha256:11e61c5548ecf74ea1f8b059730b049871f0e32b74f88bd0d670c20c819ad749"}, - {file = "coverage-6.3-cp37-cp37m-win_amd64.whl", hash = "sha256:8e0c3525b1a182c8ffc9bca7e56b521e0c2b8b3e82f033c8e16d6d721f1b54d6"}, - {file = "coverage-6.3-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:a189036c50dcd56100746139a459f0d27540fef95b09aba03e786540b8feaa5f"}, - {file = "coverage-6.3-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:32168001f33025fd756884d56d01adebb34e6c8c0b3395ca8584cdcee9c7c9d2"}, - {file = "coverage-6.3-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:a5d79c9af3f410a2b5acad91258b4ae179ee9c83897eb9de69151b179b0227f5"}, - {file = "coverage-6.3-cp38-cp38-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:85c5fc9029043cf8b07f73fbb0a7ab6d3b717510c3b5642b77058ea55d7cacde"}, - {file = "coverage-6.3-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:a7596aa2f2b8fa5604129cfc9a27ad9beec0a96f18078cb424d029fdd707468d"}, - {file = "coverage-6.3-cp38-cp38-musllinux_1_1_aarch64.whl", hash = "sha256:ce443a3e6df90d692c38762f108fc4c88314bf477689f04de76b3f252e7a351c"}, - {file = "coverage-6.3-cp38-cp38-musllinux_1_1_i686.whl", hash = "sha256:012157499ec4f135fc36cd2177e3d1a1840af9b236cbe80e9a5ccfc83d912a69"}, - {file = "coverage-6.3-cp38-cp38-musllinux_1_1_x86_64.whl", hash = "sha256:0a34d313105cdd0d3644c56df2d743fe467270d6ab93b5d4a347eb9fec8924d6"}, - {file = "coverage-6.3-cp38-cp38-win32.whl", hash = "sha256:6e78b1e25e5c5695dea012be473e442f7094d066925604be20b30713dbd47f89"}, - {file = "coverage-6.3-cp38-cp38-win_amd64.whl", hash = "sha256:433b99f7b0613bdcdc0b00cc3d39ed6d756797e3b078d2c43f8a38288520aec6"}, - {file = "coverage-6.3-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:9ed3244b415725f08ca3bdf02ed681089fd95e9465099a21c8e2d9c5d6ca2606"}, - {file = "coverage-6.3-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:ab4fc4b866b279740e0d917402f0e9a08683e002f43fa408e9655818ed392196"}, - {file = "coverage-6.3-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c8582e9280f8d0f38114fe95a92ae8d0790b56b099d728cc4f8a2e14b1c4a18c"}, - {file = "coverage-6.3-cp39-cp39-manylinux_2_5_i686.manylinux1_i686.manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:c72bb4679283c6737f452eeb9b2a0e570acaef2197ad255fb20162adc80bea76"}, - {file = "coverage-6.3-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ca29c352389ea27a24c79acd117abdd8a865c6eb01576b6f0990cd9a4e9c9f48"}, - {file = "coverage-6.3-cp39-cp39-musllinux_1_1_aarch64.whl", hash = "sha256:152cc2624381df4e4e604e21bd8e95eb8059535f7b768c1fb8b8ae0b26f47ab0"}, - {file = "coverage-6.3-cp39-cp39-musllinux_1_1_i686.whl", hash = "sha256:51372e24b1f7143ee2df6b45cff6a721f3abe93b1e506196f3ffa4155c2497f7"}, - {file = "coverage-6.3-cp39-cp39-musllinux_1_1_x86_64.whl", hash = "sha256:72d9d186508325a456475dd05b1756f9a204c7086b07fffb227ef8cee03b1dc2"}, - {file = "coverage-6.3-cp39-cp39-win32.whl", hash = "sha256:649df3641eb351cdfd0d5533c92fc9df507b6b2bf48a7ef8c71ab63cbc7b5c3c"}, - {file = "coverage-6.3-cp39-cp39-win_amd64.whl", hash = "sha256:e67ccd53da5958ea1ec833a160b96357f90859c220a00150de011b787c27b98d"}, - {file = "coverage-6.3-pp36.pp37.pp38-none-any.whl", hash = "sha256:27ac7cb84538e278e07569ceaaa6f807a029dc194b1c819a9820b9bb5dbf63ab"}, - {file = "coverage-6.3.tar.gz", hash = "sha256:987a84ff98a309994ca77ed3cc4b92424f824278e48e4bf7d1bb79a63cfe2099"}, -] -cryptography = [ - {file = "cryptography-36.0.1-cp36-abi3-macosx_10_10_universal2.whl", hash = "sha256:73bc2d3f2444bcfeac67dd130ff2ea598ea5f20b40e36d19821b4df8c9c5037b"}, - {file = "cryptography-36.0.1-cp36-abi3-macosx_10_10_x86_64.whl", hash = "sha256:2d87cdcb378d3cfed944dac30596da1968f88fb96d7fc34fdae30a99054b2e31"}, - {file = "cryptography-36.0.1-cp36-abi3-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:74d6c7e80609c0f4c2434b97b80c7f8fdfaa072ca4baab7e239a15d6d70ed73a"}, - {file = "cryptography-36.0.1-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.manylinux_2_24_aarch64.whl", hash = "sha256:6c0c021f35b421ebf5976abf2daacc47e235f8b6082d3396a2fe3ccd537ab173"}, - {file = "cryptography-36.0.1-cp36-abi3-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:5d59a9d55027a8b88fd9fd2826c4392bd487d74bf628bb9d39beecc62a644c12"}, - {file = "cryptography-36.0.1-cp36-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:0a817b961b46894c5ca8a66b599c745b9a3d9f822725221f0e0fe49dc043a3a3"}, - {file = "cryptography-36.0.1-cp36-abi3-manylinux_2_24_x86_64.whl", hash = "sha256:94ae132f0e40fe48f310bba63f477f14a43116f05ddb69d6fa31e93f05848ae2"}, - {file = "cryptography-36.0.1-cp36-abi3-musllinux_1_1_aarch64.whl", hash = "sha256:7be0eec337359c155df191d6ae00a5e8bbb63933883f4f5dffc439dac5348c3f"}, - {file = "cryptography-36.0.1-cp36-abi3-musllinux_1_1_x86_64.whl", hash = "sha256:e0344c14c9cb89e76eb6a060e67980c9e35b3f36691e15e1b7a9e58a0a6c6dc3"}, - {file = "cryptography-36.0.1-cp36-abi3-win32.whl", hash = "sha256:4caa4b893d8fad33cf1964d3e51842cd78ba87401ab1d2e44556826df849a8ca"}, - {file = "cryptography-36.0.1-cp36-abi3-win_amd64.whl", hash = "sha256:391432971a66cfaf94b21c24ab465a4cc3e8bf4a939c1ca5c3e3a6e0abebdbcf"}, - {file = "cryptography-36.0.1-pp37-pypy37_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:bb5829d027ff82aa872d76158919045a7c1e91fbf241aec32cb07956e9ebd3c9"}, - {file = "cryptography-36.0.1-pp37-pypy37_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ebc15b1c22e55c4d5566e3ca4db8689470a0ca2babef8e3a9ee057a8b82ce4b1"}, - {file = "cryptography-36.0.1-pp37-pypy37_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:596f3cd67e1b950bc372c33f1a28a0692080625592ea6392987dba7f09f17a94"}, - {file = "cryptography-36.0.1-pp38-pypy38_pp73-macosx_10_10_x86_64.whl", hash = "sha256:30ee1eb3ebe1644d1c3f183d115a8c04e4e603ed6ce8e394ed39eea4a98469ac"}, - {file = "cryptography-36.0.1-pp38-pypy38_pp73-manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:ec63da4e7e4a5f924b90af42eddf20b698a70e58d86a72d943857c4c6045b3ee"}, - {file = "cryptography-36.0.1-pp38-pypy38_pp73-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:ca238ceb7ba0bdf6ce88c1b74a87bffcee5afbfa1e41e173b1ceb095b39add46"}, - {file = "cryptography-36.0.1-pp38-pypy38_pp73-manylinux_2_24_x86_64.whl", hash = "sha256:ca28641954f767f9822c24e927ad894d45d5a1e501767599647259cbf030b903"}, - {file = "cryptography-36.0.1-pp38-pypy38_pp73-win_amd64.whl", hash = "sha256:39bdf8e70eee6b1c7b289ec6e5d84d49a6bfa11f8b8646b5b3dfe41219153316"}, - {file = "cryptography-36.0.1.tar.gz", hash = "sha256:53e5c1dc3d7a953de055d77bef2ff607ceef7a2aac0353b5d630ab67f7423638"}, -] -distlib = [ - {file = "distlib-0.3.4-py2.py3-none-any.whl", hash = "sha256:6564fe0a8f51e734df6333d08b8b94d4ea8ee6b99b5ed50613f731fd4089f34b"}, - {file = "distlib-0.3.4.zip", hash = "sha256:e4b58818180336dc9c529bfb9a0b58728ffc09ad92027a3f30b7cd91e3458579"}, -] -dnspython = [ - {file = "dnspython-2.1.0-py3-none-any.whl", hash = "sha256:95d12f6ef0317118d2a1a6fc49aac65ffec7eb8087474158f42f26a639135216"}, - {file = "dnspython-2.1.0.zip", hash = "sha256:e4a87f0b573201a0f3727fa18a516b055fd1107e0e5477cded4a2de497df1dd4"}, -] -docutils = [ - {file = "docutils-0.18.1-py2.py3-none-any.whl", hash = "sha256:23010f129180089fbcd3bc08cfefccb3b890b0050e1ca00c867036e9d161b98c"}, - {file = "docutils-0.18.1.tar.gz", hash = "sha256:679987caf361a7539d76e584cbeddc311e3aee937877c87346f31debc63e9d06"}, -] -filelock = [ - {file = "filelock-3.4.2-py3-none-any.whl", hash = "sha256:cf0fc6a2f8d26bd900f19bf33915ca70ba4dd8c56903eeb14e1e7a2fd7590146"}, - {file = "filelock-3.4.2.tar.gz", hash = "sha256:38b4f4c989f9d06d44524df1b24bd19e167d851f19b50bf3e3559952dddc5b80"}, -] -flake8 = [ - {file = "flake8-4.0.1-py2.py3-none-any.whl", hash = "sha256:479b1304f72536a55948cb40a32dce8bb0ffe3501e26eaf292c7e60eb5e0428d"}, - {file = "flake8-4.0.1.tar.gz", hash = "sha256:806e034dda44114815e23c16ef92f95c91e4c71100ff52813adf7132a6ad870d"}, -] -flake8-annotations = [ - {file = "flake8-annotations-2.7.0.tar.gz", hash = "sha256:52e53c05b0c06cac1c2dec192ea2c36e85081238add3bd99421d56f574b9479b"}, - {file = "flake8_annotations-2.7.0-py3-none-any.whl", hash = "sha256:3edfbbfb58e404868834fe6ec3eaf49c139f64f0701259f707d043185545151e"}, -] -flake8-bugbear = [ - {file = "flake8-bugbear-22.1.11.tar.gz", hash = "sha256:4c2a4136bd4ecb8bf02d5159af302ffc067642784c9d0488b33ce4610da825ee"}, - {file = "flake8_bugbear-22.1.11-py3-none-any.whl", hash = "sha256:ce7ae44aaaf67ef192b8a6de94a5ac617144e1675ad0654fdea556f48dc18d9b"}, -] -flake8-import-order = [ - {file = "flake8-import-order-0.18.1.tar.gz", hash = "sha256:a28dc39545ea4606c1ac3c24e9d05c849c6e5444a50fb7e9cdd430fc94de6e92"}, - {file = "flake8_import_order-0.18.1-py2.py3-none-any.whl", hash = "sha256:90a80e46886259b9c396b578d75c749801a41ee969a235e163cfe1be7afd2543"}, -] -flake8-polyfill = [ - {file = "flake8-polyfill-1.0.2.tar.gz", hash = "sha256:e44b087597f6da52ec6393a709e7108b2905317d0c0b744cdca6208e670d8eda"}, - {file = "flake8_polyfill-1.0.2-py2.py3-none-any.whl", hash = "sha256:12be6a34ee3ab795b19ca73505e7b55826d5f6ad7230d31b18e106400169b9e9"}, -] -flake8-tidy-imports = [ - {file = "flake8-tidy-imports-4.6.0.tar.gz", hash = "sha256:3e193d8c4bb4492408a90e956d888b27eed14c698387c9b38230da3dad78058f"}, - {file = "flake8_tidy_imports-4.6.0-py3-none-any.whl", hash = "sha256:6ae9f55d628156e19d19f4c359dd5d3e95431a9bd514f5e2748c53c1398c66b2"}, -] -idna = [ - {file = "idna-3.3-py3-none-any.whl", hash = "sha256:84d9dd047ffa80596e0f246e2eab0b391788b0503584e8945f2368256d2735ff"}, - {file = "idna-3.3.tar.gz", hash = "sha256:9d643ff0a55b762d5cdb124b8eaa99c66322e2157b69160bc32796e824360e6d"}, -] -importlib-metadata = [ - {file = "importlib_metadata-4.2.0-py3-none-any.whl", hash = "sha256:057e92c15bc8d9e8109738a48db0ccb31b4d9d5cfbee5a8670879a30be66304b"}, - {file = "importlib_metadata-4.2.0.tar.gz", hash = "sha256:b7e52a1f8dec14a75ea73e0891f3060099ca1d8e6a462a4dff11c3e119ea1b31"}, -] -iniconfig = [ - {file = "iniconfig-1.1.1-py2.py3-none-any.whl", hash = "sha256:011e24c64b7f47f6ebd835bb12a743f2fbe9a26d4cecaa7f53bc4f35ee9da8b3"}, - {file = "iniconfig-1.1.1.tar.gz", hash = "sha256:bc3af051d7d14b2ee5ef9969666def0cd1a000e121eaea580d4a313df4b37f32"}, -] -jeepney = [ - {file = "jeepney-0.7.1-py3-none-any.whl", hash = "sha256:1b5a0ea5c0e7b166b2f5895b91a08c14de8915afda4407fb5022a195224958ac"}, - {file = "jeepney-0.7.1.tar.gz", hash = "sha256:fa9e232dfa0c498bd0b8a3a73b8d8a31978304dcef0515adc859d4e096f96f4f"}, -] -keyring = [ - {file = "keyring-23.5.0-py3-none-any.whl", hash = "sha256:b0d28928ac3ec8e42ef4cc227822647a19f1d544f21f96457965dc01cf555261"}, - {file = "keyring-23.5.0.tar.gz", hash = "sha256:9012508e141a80bd1c0b6778d5c610dd9f8c464d75ac6774248500503f972fb9"}, -] -mccabe = [ - {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"}, - {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"}, -] -mypy-extensions = [ - {file = "mypy_extensions-0.4.3-py2.py3-none-any.whl", hash = "sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d"}, - {file = "mypy_extensions-0.4.3.tar.gz", hash = "sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8"}, -] -nodeenv = [ - {file = "nodeenv-1.6.0-py2.py3-none-any.whl", hash = "sha256:621e6b7076565ddcacd2db0294c0381e01fd28945ab36bcf00f41c5daf63bef7"}, - {file = "nodeenv-1.6.0.tar.gz", hash = "sha256:3ef13ff90291ba2a4a7a4ff9a979b63ffdd00a464dbe04acf0ea6471517a4c2b"}, -] -packaging = [ - {file = "packaging-21.3-py3-none-any.whl", hash = "sha256:ef103e05f519cdc783ae24ea4e2e0f508a9c99b2d4969652eed6a2e1ea5bd522"}, - {file = "packaging-21.3.tar.gz", hash = "sha256:dd47c42927d89ab911e606518907cc2d3a1f38bbd026385970643f9c5b8ecfeb"}, -] -pathspec = [ - {file = "pathspec-0.9.0-py2.py3-none-any.whl", hash = "sha256:7d15c4ddb0b5c802d161efc417ec1a2558ea2653c2e8ad9c19098201dc1c993a"}, - {file = "pathspec-0.9.0.tar.gz", hash = "sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1"}, -] -pep8-naming = [ - {file = "pep8-naming-0.12.1.tar.gz", hash = "sha256:bb2455947757d162aa4cad55dba4ce029005cd1692f2899a21d51d8630ca7841"}, - {file = "pep8_naming-0.12.1-py2.py3-none-any.whl", hash = "sha256:4a8daeaeb33cfcde779309fc0c9c0a68a3bbe2ad8a8308b763c5068f86eb9f37"}, -] -pkginfo = [ - {file = "pkginfo-1.8.2-py2.py3-none-any.whl", hash = "sha256:c24c487c6a7f72c66e816ab1796b96ac6c3d14d49338293d2141664330b55ffc"}, - {file = "pkginfo-1.8.2.tar.gz", hash = "sha256:542e0d0b6750e2e21c20179803e40ab50598d8066d51097a0e382cba9eb02bff"}, -] -platformdirs = [ - {file = "platformdirs-2.4.1-py3-none-any.whl", hash = "sha256:1d7385c7db91728b83efd0ca99a5afb296cab9d0ed8313a45ed8ba17967ecfca"}, - {file = "platformdirs-2.4.1.tar.gz", hash = "sha256:440633ddfebcc36264232365d7840a970e75e1018d15b4327d11f91909045fda"}, -] -pluggy = [ - {file = "pluggy-1.0.0-py2.py3-none-any.whl", hash = "sha256:74134bbf457f031a36d68416e1509f34bd5ccc019f0bcc952c7b909d06b37bd3"}, - {file = "pluggy-1.0.0.tar.gz", hash = "sha256:4224373bacce55f955a878bf9cfa763c1e360858e330072059e10bad68531159"}, -] -py = [ - {file = "py-1.11.0-py2.py3-none-any.whl", hash = "sha256:607c53218732647dff4acdfcd50cb62615cedf612e72d1724fb1a0cc6405b378"}, - {file = "py-1.11.0.tar.gz", hash = "sha256:51c75c4126074b472f746a24399ad32f6053d1b34b68d2fa41e558e6f4a98719"}, -] -pycodestyle = [ - {file = "pycodestyle-2.8.0-py2.py3-none-any.whl", hash = "sha256:720f8b39dde8b293825e7ff02c475f3077124006db4f440dcbc9a20b76548a20"}, - {file = "pycodestyle-2.8.0.tar.gz", hash = "sha256:eddd5847ef438ea1c7870ca7eb78a9d47ce0cdb4851a5523949f2601d0cbbe7f"}, -] -pycparser = [ - {file = "pycparser-2.21-py2.py3-none-any.whl", hash = "sha256:8ee45429555515e1f6b185e78100aea234072576aa43ab53aefcae078162fca9"}, - {file = "pycparser-2.21.tar.gz", hash = "sha256:e644fdec12f7872f86c58ff790da456218b10f863970249516d60a5eaca77206"}, -] -pyflakes = [ - {file = "pyflakes-2.4.0-py2.py3-none-any.whl", hash = "sha256:3bb3a3f256f4b7968c9c788781e4ff07dce46bdf12339dcda61053375426ee2e"}, - {file = "pyflakes-2.4.0.tar.gz", hash = "sha256:05a85c2872edf37a4ed30b0cce2f6093e1d0581f8c19d7393122da7e25b2b24c"}, -] -pygments = [ - {file = "Pygments-2.11.2-py3-none-any.whl", hash = "sha256:44238f1b60a76d78fc8ca0528ee429702aae011c265fe6a8dd8b63049ae41c65"}, - {file = "Pygments-2.11.2.tar.gz", hash = "sha256:4e426f72023d88d03b2fa258de560726ce890ff3b630f88c21cbb8b2503b8c6a"}, -] -pyparsing = [ - {file = "pyparsing-3.0.7-py3-none-any.whl", hash = "sha256:a6c06a88f252e6c322f65faf8f418b16213b51bdfaece0524c1c1bc30c63c484"}, - {file = "pyparsing-3.0.7.tar.gz", hash = "sha256:18ee9022775d270c55187733956460083db60b37d0d0fb357445f3094eed3eea"}, -] -pyright = [ - {file = "pyright-0.0.13-py3-none-any.whl", hash = "sha256:520458c87dc456ed0d405a08a565066aaf5b76281d23861759cfaa3c73f5bda0"}, - {file = "pyright-0.0.13.tar.gz", hash = "sha256:ca97f9121927847d0aea5d2e757a0bf6d3c25c116c90a3ce6263b4167a12dfc4"}, -] -pytest = [ - {file = "pytest-6.2.5-py3-none-any.whl", hash = "sha256:7310f8d27bc79ced999e760ca304d69f6ba6c6649c0b60fb0e04a4a77cacc134"}, - {file = "pytest-6.2.5.tar.gz", hash = "sha256:131b36680866a76e6781d13f101efb86cf674ebb9762eb70d3082b6f29889e89"}, -] -pytest-asyncio = [ - {file = "pytest-asyncio-0.16.0.tar.gz", hash = "sha256:7496c5977ce88c34379df64a66459fe395cd05543f0a2f837016e7144391fcfb"}, - {file = "pytest_asyncio-0.16.0-py3-none-any.whl", hash = "sha256:5f2a21273c47b331ae6aa5b36087047b4899e40f03f18397c0e65fa5cca54e9b"}, -] -pytest-cov = [ - {file = "pytest-cov-3.0.0.tar.gz", hash = "sha256:e7f0f5b1617d2210a2cabc266dfe2f4c75a8d32fb89eafb7ad9d06f6d076d470"}, - {file = "pytest_cov-3.0.0-py3-none-any.whl", hash = "sha256:578d5d15ac4a25e5f961c938b85a05b09fdaae9deef3bb6de9a6e766622ca7a6"}, -] -pywin32-ctypes = [ - {file = "pywin32-ctypes-0.2.0.tar.gz", hash = "sha256:24ffc3b341d457d48e8922352130cf2644024a4ff09762a2261fd34c36ee5942"}, - {file = "pywin32_ctypes-0.2.0-py2.py3-none-any.whl", hash = "sha256:9dc2d991b3479cc2df15930958b674a48a227d5361d413827a4cfd0b5876fc98"}, -] -readme-renderer = [ - {file = "readme_renderer-32.0-py3-none-any.whl", hash = "sha256:a50a0f2123a4c1145ac6f420e1a348aafefcc9211c846e3d51df05fe3d865b7d"}, - {file = "readme_renderer-32.0.tar.gz", hash = "sha256:b512beafa6798260c7d5af3e1b1f097e58bfcd9a575da7c4ddd5e037490a5b85"}, -] -requests = [ - {file = "requests-2.27.1-py2.py3-none-any.whl", hash = "sha256:f22fa1e554c9ddfd16e6e41ac79759e17be9e492b3587efa038054674760e72d"}, - {file = "requests-2.27.1.tar.gz", hash = "sha256:68d7c56fd5a8999887728ef304a6d12edc7be74f1cfa47714fc8b414525c9a61"}, -] -requests-toolbelt = [ - {file = "requests-toolbelt-0.9.1.tar.gz", hash = "sha256:968089d4584ad4ad7c171454f0a5c6dac23971e9472521ea3b6d49d610aa6fc0"}, - {file = "requests_toolbelt-0.9.1-py2.py3-none-any.whl", hash = "sha256:380606e1d10dc85c3bd47bf5a6095f815ec007be7a8b69c878507068df059e6f"}, -] -rfc3986 = [ - {file = "rfc3986-2.0.0-py2.py3-none-any.whl", hash = "sha256:50b1502b60e289cb37883f3dfd34532b8873c7de9f49bb546641ce9cbd256ebd"}, - {file = "rfc3986-2.0.0.tar.gz", hash = "sha256:97aacf9dbd4bfd829baad6e6309fa6573aaf1be3f6fa735c8ab05e46cecb261c"}, -] -secretstorage = [ - {file = "SecretStorage-3.3.1-py3-none-any.whl", hash = "sha256:422d82c36172d88d6a0ed5afdec956514b189ddbfb72fefab0c8a1cee4eaf71f"}, - {file = "SecretStorage-3.3.1.tar.gz", hash = "sha256:fd666c51a6bf200643495a04abb261f83229dcb6fd8472ec393df7ffc8b6f195"}, -] -six = [ - {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"}, - {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"}, -] -toml = [ - {file = "toml-0.10.2-py2.py3-none-any.whl", hash = "sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b"}, - {file = "toml-0.10.2.tar.gz", hash = "sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f"}, -] -tomli = [ - {file = "tomli-2.0.0-py3-none-any.whl", hash = "sha256:b5bde28da1fed24b9bd1d4d2b8cba62300bfb4ec9a6187a957e8ddb9434c5224"}, - {file = "tomli-2.0.0.tar.gz", hash = "sha256:c292c34f58502a1eb2bbb9f5bbc9a5ebc37bee10ffb8c2d6bbdfa8eb13cc14e1"}, -] -tox = [ - {file = "tox-3.24.5-py2.py3-none-any.whl", hash = "sha256:be3362472a33094bce26727f5f771ca0facf6dafa217f65875314e9a6600c95c"}, - {file = "tox-3.24.5.tar.gz", hash = "sha256:67e0e32c90e278251fea45b696d0fef3879089ccbe979b0c556d35d5a70e2993"}, -] -tox-poetry = [ - {file = "tox-poetry-0.4.1.tar.gz", hash = "sha256:2395808e1ce487b5894c10f2202e14702bfa6d6909c0d1e525170d14809ac7ef"}, - {file = "tox_poetry-0.4.1-py2.py3-none-any.whl", hash = "sha256:11d9cd4e51d4cd9484b3ba63f2650ab4cfb4096e5f0682ecf561ddfc3c8e8c92"}, -] -tqdm = [ - {file = "tqdm-4.62.3-py2.py3-none-any.whl", hash = "sha256:8dd278a422499cd6b727e6ae4061c40b48fce8b76d1ccbf5d34fca9b7f925b0c"}, - {file = "tqdm-4.62.3.tar.gz", hash = "sha256:d359de7217506c9851b7869f3708d8ee53ed70a1b8edbba4dbcb47442592920d"}, -] -twine = [ - {file = "twine-3.7.1-py3-none-any.whl", hash = "sha256:8c120845fc05270f9ee3e9d7ebbed29ea840e41f48cd059e04733f7e1d401345"}, - {file = "twine-3.7.1.tar.gz", hash = "sha256:28460a3db6b4532bde6a5db6755cf2dce6c5020bada8a641bb2c5c7a9b1f35b8"}, -] -typed-ast = [ - {file = "typed_ast-1.5.2-cp310-cp310-macosx_10_9_x86_64.whl", hash = "sha256:183b183b7771a508395d2cbffd6db67d6ad52958a5fdc99f450d954003900266"}, - {file = "typed_ast-1.5.2-cp310-cp310-macosx_11_0_arm64.whl", hash = "sha256:676d051b1da67a852c0447621fdd11c4e104827417bf216092ec3e286f7da596"}, - {file = "typed_ast-1.5.2-cp310-cp310-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:bc2542e83ac8399752bc16e0b35e038bdb659ba237f4222616b4e83fb9654985"}, - {file = "typed_ast-1.5.2-cp310-cp310-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:74cac86cc586db8dfda0ce65d8bcd2bf17b58668dfcc3652762f3ef0e6677e76"}, - {file = "typed_ast-1.5.2-cp310-cp310-win_amd64.whl", hash = "sha256:18fe320f354d6f9ad3147859b6e16649a0781425268c4dde596093177660e71a"}, - {file = "typed_ast-1.5.2-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:31d8c6b2df19a777bc8826770b872a45a1f30cfefcfd729491baa5237faae837"}, - {file = "typed_ast-1.5.2-cp36-cp36m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:963a0ccc9a4188524e6e6d39b12c9ca24cc2d45a71cfdd04a26d883c922b4b78"}, - {file = "typed_ast-1.5.2-cp36-cp36m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:0eb77764ea470f14fcbb89d51bc6bbf5e7623446ac4ed06cbd9ca9495b62e36e"}, - {file = "typed_ast-1.5.2-cp36-cp36m-win_amd64.whl", hash = "sha256:294a6903a4d087db805a7656989f613371915fc45c8cc0ddc5c5a0a8ad9bea4d"}, - {file = "typed_ast-1.5.2-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:26a432dc219c6b6f38be20a958cbe1abffcc5492821d7e27f08606ef99e0dffd"}, - {file = "typed_ast-1.5.2-cp37-cp37m-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c7407cfcad702f0b6c0e0f3e7ab876cd1d2c13b14ce770e412c0c4b9728a0f88"}, - {file = "typed_ast-1.5.2-cp37-cp37m-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f30ddd110634c2d7534b2d4e0e22967e88366b0d356b24de87419cc4410c41b7"}, - {file = "typed_ast-1.5.2-cp37-cp37m-win_amd64.whl", hash = "sha256:8c08d6625bb258179b6e512f55ad20f9dfef019bbfbe3095247401e053a3ea30"}, - {file = "typed_ast-1.5.2-cp38-cp38-macosx_10_9_x86_64.whl", hash = "sha256:90904d889ab8e81a956f2c0935a523cc4e077c7847a836abee832f868d5c26a4"}, - {file = "typed_ast-1.5.2-cp38-cp38-macosx_11_0_arm64.whl", hash = "sha256:bbebc31bf11762b63bf61aaae232becb41c5bf6b3461b80a4df7e791fabb3aca"}, - {file = "typed_ast-1.5.2-cp38-cp38-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:c29dd9a3a9d259c9fa19d19738d021632d673f6ed9b35a739f48e5f807f264fb"}, - {file = "typed_ast-1.5.2-cp38-cp38-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:58ae097a325e9bb7a684572d20eb3e1809802c5c9ec7108e85da1eb6c1a3331b"}, - {file = "typed_ast-1.5.2-cp38-cp38-win_amd64.whl", hash = "sha256:da0a98d458010bf4fe535f2d1e367a2e2060e105978873c04c04212fb20543f7"}, - {file = "typed_ast-1.5.2-cp39-cp39-macosx_10_9_x86_64.whl", hash = "sha256:33b4a19ddc9fc551ebabca9765d54d04600c4a50eda13893dadf67ed81d9a098"}, - {file = "typed_ast-1.5.2-cp39-cp39-macosx_11_0_arm64.whl", hash = "sha256:1098df9a0592dd4c8c0ccfc2e98931278a6c6c53cb3a3e2cf7e9ee3b06153344"}, - {file = "typed_ast-1.5.2-cp39-cp39-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:42c47c3b43fe3a39ddf8de1d40dbbfca60ac8530a36c9b198ea5b9efac75c09e"}, - {file = "typed_ast-1.5.2-cp39-cp39-manylinux_2_5_x86_64.manylinux1_x86_64.manylinux_2_12_x86_64.manylinux2010_x86_64.whl", hash = "sha256:f290617f74a610849bd8f5514e34ae3d09eafd521dceaa6cf68b3f4414266d4e"}, - {file = "typed_ast-1.5.2-cp39-cp39-win_amd64.whl", hash = "sha256:df05aa5b241e2e8045f5f4367a9f6187b09c4cdf8578bb219861c4e27c443db5"}, - {file = "typed_ast-1.5.2.tar.gz", hash = "sha256:525a2d4088e70a9f75b08b3f87a51acc9cde640e19cc523c7e41aa355564ae27"}, -] -typing-extensions = [ - {file = "typing_extensions-4.0.1-py3-none-any.whl", hash = "sha256:7f001e5ac290a0c0401508864c7ec868be4e701886d5b573a9528ed3973d9d3b"}, - {file = "typing_extensions-4.0.1.tar.gz", hash = "sha256:4ca091dea149f945ec56afb48dae714f21e8692ef22a395223bcd328961b6a0e"}, -] -urllib3 = [ - {file = "urllib3-1.22-py2.py3-none-any.whl", hash = "sha256:06330f386d6e4b195fbfc736b297f58c5a892e4440e54d294d7004e3a9bbea1b"}, - {file = "urllib3-1.22.tar.gz", hash = "sha256:cc44da8e1145637334317feebd728bd869a35285b93cbb4cca2577da7e62db4f"}, -] -virtualenv = [ - {file = "virtualenv-20.13.0-py2.py3-none-any.whl", hash = "sha256:339f16c4a86b44240ba7223d0f93a7887c3ca04b5f9c8129da7958447d079b09"}, - {file = "virtualenv-20.13.0.tar.gz", hash = "sha256:d8458cf8d59d0ea495ad9b34c2599487f8a7772d796f9910858376d1600dd2dd"}, -] -webencodings = [ - {file = "webencodings-0.5.1-py2.py3-none-any.whl", hash = "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78"}, - {file = "webencodings-0.5.1.tar.gz", hash = "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923"}, -] -zipp = [ - {file = "zipp-3.7.0-py3-none-any.whl", hash = "sha256:b47250dd24f92b7dd6a0a8fc5244da14608f3ca90a5efcd37a3b1642fac9a375"}, - {file = "zipp-3.7.0.tar.gz", hash = "sha256:9f50f446828eb9d45b267433fd3e9da8d801f614129124863f9c51ebceafb87d"}, -] diff --git a/pyproject.toml b/pyproject.toml deleted file mode 100644 index 6ede194..0000000 --- a/pyproject.toml +++ /dev/null @@ -1,73 +0,0 @@ -[tool.poetry] -name = "mcstatus" -version = "0.0.0" # version is handled by git tags and poetry-dynamic-versioning -description = "A library to query Minecraft Servers for their status and capabilities." -authors = ["Nathan Adams "] -license = "Apache-2.0" -readme = "README.md" -repository = "https://github.com/Dinnerbone/mcstatus" -classifiers = [ - "Development Status :: 5 - Production/Stable", - "Intended Audience :: Developers", - "License :: OSI Approved :: Apache Software License", - "Natural Language :: English", - "Operating System :: OS Independent", - "Programming Language :: Python", - "Programming Language :: Python :: 3", - "Programming Language :: Python :: 3.7", - "Programming Language :: Python :: 3.8", - "Programming Language :: Python :: 3.9", - "Programming Language :: Python :: 3.10", - "Topic :: Games/Entertainment", - "Topic :: Software Development :: Libraries :: Python Modules", - "Topic :: System :: Monitoring", -] -packages = [ - { include = "mcstatus" }, - { include = "protocol", from = "mcstatus" }, - { include = "scripts", from = "mcstatus" }, -] - -[tool.poetry.dependencies] -python = ">=3.7,<4" -asyncio-dgram = "1.2.0" -click = ">=7.1.2,<9" -dnspython = "2.1.0" - -[tool.poetry.dev-dependencies] -coverage = "^6.1.1" -pytest = "^6.2.5" -pytest-asyncio = "^0.16.0" -pytest-cov = "^3.0.0" -twine = "^3.5.0" -black = "^22.1.0" -tox = "^3.24.5" -tox-poetry = "^0.4.1" -pyright = "^0.0.13" -typing-extensions = "^4.0.1" -flake8 = "^4.0.1" -flake8-bugbear = "^22.1.11" -flake8-tidy-imports = "^4.6.0" -flake8-import-order = "^0.18.1" -pep8-naming = "^0.12.1" -flake8-annotations = "^2.7.0" - -[tool.poetry.scripts] -mcstatus = 'mcstatus.scripts.mcstatus:cli' - -[tool.pytest.ini_options] -minversion = "6.0" -addopts = "--strict-markers --doctest-modules --cov=mcstatus --cov-append --cov-branch --cov-report=term-missing -vvv --no-cov-on-fail" -testpaths = ["mcstatus/tests"] - -[tool.poetry-dynamic-versioning] -bump = true -enable = true -style = "pep440" - -[tool.black] -line-length = 127 - -[build-system] -requires = ["poetry-core>=1.0.0", "poetry-dynamic-versioning"] -build-backend = "poetry.core.masonry.api" diff --git a/pyrightconfig.json b/pyrightconfig.json deleted file mode 100644 index 4fac18e..0000000 --- a/pyrightconfig.json +++ /dev/null @@ -1,11 +0,0 @@ -{ - "exclude": [ - // Don't type-check files in the virtual environment, it contains - // external library code which we don't need to check - ".venv", - // GitHub validation workflow creates .cache folder to store the - // python environment into, we don't want to check the files in it - // for the same reason as with venv - ".cache" - ] -} diff --git a/release.sh b/release.sh deleted file mode 100755 index 8281ca4..0000000 --- a/release.sh +++ /dev/null @@ -1,14 +0,0 @@ -#!/usr/bin/env bash -set -euo pipefail - -poetry install -poetry run tox --recreate - -rm -rf dist/ -poetry build - -echo "Set these environment variables if you wish to not be pestered:" -echo "export POETRY_PYPI_TOKEN_PYPI=my-token" -echo "export POETRY_HTTP_BASIC_PYPI_USERNAME=username" -echo "export POETRY_HTTP_BASIC_PYPI_PASSWORD=password" -poetry publish --no-interaction diff --git a/tox.ini b/tox.ini deleted file mode 100644 index 77d143b..0000000 --- a/tox.ini +++ /dev/null @@ -1,41 +0,0 @@ -[tox] -isolated_build = True -envlist = - format-check,lint,py{37,38,39,310},coverage - -[gh-actions] -python = - 3.7: py37 - 3.8: py38 - 3.9: py39 - 3.10: py310, coverage - -[testenv] -setenv = - COVERAGE_FILE=.coverage.{envname} -commands = - pytest {posargs} - -[testenv:format] -commands = - black ./mcstatus - -[testenv:format-check] -commands = - black --check ./mcstatus - -[testenv:lint] -passenv = HOME -commands = - pyright ./mcstatus - flake8 ./mcstatus - -[testenv:coverage] -depends = - py{37,38,39,310} -setenv = - COVERAGE_FILE=.coverage -commands = - coverage combine - coverage report --show-missing --fail-under=80 -