From 6db23f6490ce1c7cbcfdac33cd4270e01a24947d Mon Sep 17 00:00:00 2001 From: null <60427892+null8626@users.noreply.github.com> Date: Tue, 26 Mar 2024 18:16:42 +0700 Subject: [PATCH 01/33] refactor: remove unused import The following pull request ~~adds malware~~ removes unused `StatsWrapper` import in the `tests/test_autopost.py` file. --- tests/test_autopost.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tests/test_autopost.py b/tests/test_autopost.py index a4f8ee7..7cf39c1 100644 --- a/tests/test_autopost.py +++ b/tests/test_autopost.py @@ -5,7 +5,7 @@ from aiohttp import ClientSession from pytest_mock import MockerFixture -from topgg import DBLClient, StatsWrapper +from topgg import DBLClient from topgg.autopost import AutoPoster from topgg.errors import ServerError, TopGGException, Unauthorized From a9f6ee7d6348d6e2099e58fac1b66c7435f3a88e Mon Sep 17 00:00:00 2001 From: null8626 Date: Tue, 26 Mar 2024 18:30:08 +0700 Subject: [PATCH 02/33] meta: update links --- README.rst | 2 +- docs/conf.py | 2 +- topgg/http.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/README.rst b/README.rst index 5bdddce..69ab58b 100644 --- a/README.rst +++ b/README.rst @@ -27,7 +27,7 @@ Install from source .. code:: bash - pip3 install git+https://github.com/top-gg/python-sdk/ + pip3 install git+https://github.com/top-gg-community/python-sdk/ Documentation ------------- diff --git a/docs/conf.py b/docs/conf.py index 2d36857..ca87da6 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -50,7 +50,7 @@ autodoc_member_order = "groupwise" extlinks = { - "issue": ("https://github.com/top-gg/python-sdk/issues/%s", "GH-"), + "issue": ("https://github.com/top-gg-community/python-sdk/issues/%s", "GH-"), } intersphinx_mapping = { diff --git a/topgg/http.py b/topgg/http.py index 08160d6..6bd967c 100644 --- a/topgg/http.py +++ b/topgg/http.py @@ -86,7 +86,7 @@ def __init__( [self.global_rate_limiter, self.bot_rate_limiter] ) self.user_agent = ( - f"topggpy (https://github.com/top-gg/python-sdk {__version__}) Python/" + f"topggpy (https://github.com/top-gg-community/python-sdk {__version__}) Python/" f"{sys.version_info[0]}.{sys.version_info[1]} aiohttp/{aiohttp.__version__}" ) From 28c56e44fb2feea9f947d5b84cb51260dd9d0acc Mon Sep 17 00:00:00 2001 From: null8626 Date: Tue, 26 Mar 2024 18:32:38 +0700 Subject: [PATCH 03/33] meta: make the min supported ver 3.8 --- .github/workflows/python-package.yml | 2 +- .github/workflows/python-publish.yml | 2 +- .readthedocs.yml | 2 +- README.rst | 2 +- setup.py | 7 ++++--- 5 files changed, 8 insertions(+), 7 deletions(-) diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index 60b37a8..4d3db9c 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -15,7 +15,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: [ 3.6, 3.7, 3.8, 3.9 ] + python-version: [ 3.8, 3.9, 3.10, 3.11, 3.12 ] steps: - uses: actions/checkout@v2 diff --git a/.github/workflows/python-publish.yml b/.github/workflows/python-publish.yml index 1eba4d8..2208dc3 100644 --- a/.github/workflows/python-publish.yml +++ b/.github/workflows/python-publish.yml @@ -17,7 +17,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v2 with: - python-version: '3.7' + python-version: '3.8' - name: Install dependencies run: | python -m pip install --upgrade pip diff --git a/.readthedocs.yml b/.readthedocs.yml index 388f9a1..35fe322 100644 --- a/.readthedocs.yml +++ b/.readthedocs.yml @@ -7,7 +7,7 @@ build: image: latest python: - version: 3.8 + version: 3.12 install: - requirements: requirements.txt - requirements: requirements-docs.txt diff --git a/README.rst b/README.rst index 69ab58b..8860689 100644 --- a/README.rst +++ b/README.rst @@ -4,7 +4,7 @@ Top.gg Python Library .. image:: https://img.shields.io/pypi/v/topggpy.svg :target: https://pypi.python.org/pypi/topggpy - :alt: View on PyPi + :alt: View on PyPI .. image:: https://img.shields.io/pypi/pyversions/topggpy.svg :target: https://pypi.python.org/pypi/topggpy :alt: v1.0.0 diff --git a/setup.py b/setup.py index 36733af..8dc72df 100644 --- a/setup.py +++ b/setup.py @@ -42,7 +42,7 @@ long_description=readme, package_data={"topgg": ["py.typed"]}, include_package_data=True, - python_requires=">= 3.6", + python_requires=">= 3.8", install_requires=requirements, keywords="discord bot server list discordservers serverlist discordbots botlist topgg top.gg", classifiers=[ @@ -52,10 +52,11 @@ "Natural Language :: English", "Operating System :: OS Independent", "Programming Language :: Python :: 3 :: Only", - "Programming Language :: Python :: 3.6", - "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", "Topic :: Internet", "Topic :: Software Development :: Libraries", "Topic :: Software Development :: Libraries :: Python Modules", From 957af685a0de676febbc7539d783cd086e9b017f Mon Sep 17 00:00:00 2001 From: null8626 Date: Tue, 26 Mar 2024 18:35:28 +0700 Subject: [PATCH 04/33] fix: python 3.12 support --- requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/requirements.txt b/requirements.txt index 9ad0580..1b80c60 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1 @@ -aiohttp>=3.6.0,<3.9.0 \ No newline at end of file +aiohttp>=3.9.0 \ No newline at end of file From 3300b504aeffa72384b75cc5dde706095d68fa70 Mon Sep 17 00:00:00 2001 From: null8626 Date: Tue, 26 Mar 2024 18:36:50 +0700 Subject: [PATCH 05/33] fix: why does it say 3.1 :skull: --- .github/workflows/python-package.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index 4d3db9c..bea34d1 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -15,7 +15,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: [ 3.8, 3.9, 3.10, 3.11, 3.12 ] + python-version: [ 3.8, 3.9, '3.10', 3.11, 3.12 ] steps: - uses: actions/checkout@v2 From c2c7a691b1703181a1ea25980daa7e6481cda69d Mon Sep 17 00:00:00 2001 From: null8626 Date: Tue, 26 Mar 2024 18:49:46 +0700 Subject: [PATCH 06/33] fix: migrate from setup.py to pyproject.toml --- pyproject.toml | 35 +++++++++++++++++++++++ requirements-docs.txt | 2 ++ setup.py | 65 ------------------------------------------- 3 files changed, 37 insertions(+), 65 deletions(-) create mode 100644 pyproject.toml delete mode 100644 setup.py diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..6e789b6 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,35 @@ +[build-system] +requires = ["setuptools"] + +[project] +name = "topggpy" +version = "2.0.1" +description = "A simple API wrapper for Top.gg written in Python." +readme = "README.rst" +license = { text = "MIT" } +authors = [{ name = "Assanali Mukhanov" }, { name = "Norizon" }, { name = "Top.gg" }] +keywords = ["discord", "bot", "topgg", "top.gg"] +dependencies = ["aiohttp>=3.9.0"] +classifiers = [ + "Development Status :: 5 - Production/Stable", + "License :: OSI Approved :: MIT License", + "Intended Audience :: Developers", + "Natural Language :: English", + "Operating System :: OS Independent", + "Programming Language :: Python :: 3 :: Only", + "Programming Language :: Python :: 3.8", + "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Topic :: Internet", + "Topic :: Software Development :: Libraries", + "Topic :: Software Development :: Libraries :: Python Modules", + "Topic :: Utilities" +] +requires-python = ">=3.8" + +[project.urls] +Homepage = "https://topggpy.readthedocs.io/en/stable/" +Documentation = "https://topggpy.readthedocs.io/en/stable/" +Repository = "https://github.com/top-gg-community/python-sdk" \ No newline at end of file diff --git a/requirements-docs.txt b/requirements-docs.txt index e75c15e..b90afd8 100644 --- a/requirements-docs.txt +++ b/requirements-docs.txt @@ -1,2 +1,4 @@ sphinx insegel +sphinxcontrib-napoleon +sphinx-rtd-dark-mode \ No newline at end of file diff --git a/setup.py b/setup.py deleted file mode 100644 index 8dc72df..0000000 --- a/setup.py +++ /dev/null @@ -1,65 +0,0 @@ -import os -import pathlib -import re -import types - -from setuptools import find_packages, setup - -HERE = pathlib.Path(__file__).parent - -txt = (HERE / "topgg" / "__init__.py").read_text("utf-8") - -groups = {} - -for match in re.finditer(r'__(?P.*)__\s*=\s*"(?P[^"]+)"\r?', txt): - group = match.groupdict() - groups[group["identifier"]] = group["value"] - -metadata = types.SimpleNamespace(**groups) - -on_rtd = os.getenv("READTHEDOCS") == "True" - -with open("requirements.txt") as f: - requirements = f.read().splitlines() - -if on_rtd: - requirements.append("sphinxcontrib-napoleon") - requirements.append("sphinx-rtd-dark-mode") - -with open("README.rst") as f: - readme = f.read() - -setup( - name="topggpy", - author=f"{metadata.author}, Top.gg", - author_email="shivaco.osu@gmail.com", - maintainer=f"{metadata.maintainer}, Top.gg", - url="https://github.com/top-gg/python-sdk", - version=metadata.version, - packages=find_packages(), - license=metadata.license, - description="A simple API wrapper for Top.gg written in Python.", - long_description=readme, - package_data={"topgg": ["py.typed"]}, - include_package_data=True, - python_requires=">= 3.8", - install_requires=requirements, - keywords="discord bot server list discordservers serverlist discordbots botlist topgg top.gg", - classifiers=[ - "Development Status :: 5 - Production/Stable", - "License :: OSI Approved :: MIT License", - "Intended Audience :: Developers", - "Natural Language :: English", - "Operating System :: OS Independent", - "Programming Language :: Python :: 3 :: Only", - "Programming Language :: Python :: 3.8", - "Programming Language :: Python :: 3.9", - "Programming Language :: Python :: 3.10", - "Programming Language :: Python :: 3.11", - "Programming Language :: Python :: 3.12", - "Topic :: Internet", - "Topic :: Software Development :: Libraries", - "Topic :: Software Development :: Libraries :: Python Modules", - "Topic :: Utilities", - ], -) From ee2ec2e241a24f1ea51423fca6b6d6c38138903e Mon Sep 17 00:00:00 2001 From: null8626 Date: Tue, 26 Mar 2024 18:50:09 +0700 Subject: [PATCH 07/33] meta: bump version --- topgg/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/topgg/__init__.py b/topgg/__init__.py index 1a9025e..eed4b28 100644 --- a/topgg/__init__.py +++ b/topgg/__init__.py @@ -12,7 +12,7 @@ __author__ = "Assanali Mukhanov" __maintainer__ = "Norizon" __license__ = "MIT" -__version__ = "2.0.0a1" +__version__ = "2.0.1" from .autopost import * from .client import * From 472f69d6b194e79682b63c4eb4fe722170387cc7 Mon Sep 17 00:00:00 2001 From: null8626 Date: Tue, 26 Mar 2024 18:56:43 +0700 Subject: [PATCH 08/33] ci: support pyproject.toml --- .github/workflows/python-package.yml | 2 +- .github/workflows/python-publish.yml | 31 ---------------------------- 2 files changed, 1 insertion(+), 32 deletions(-) delete mode 100644 .github/workflows/python-publish.yml diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index bea34d1..c24b9ba 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -29,7 +29,7 @@ jobs: path: "requirements-dev.txt" - name: Install itself run: | - python setup.py install + python -m pip install . - name: Lint with flake8 run: | # stop the build if there are Python syntax errors or undefined names diff --git a/.github/workflows/python-publish.yml b/.github/workflows/python-publish.yml deleted file mode 100644 index 2208dc3..0000000 --- a/.github/workflows/python-publish.yml +++ /dev/null @@ -1,31 +0,0 @@ -# This workflow will upload a Python Package using Twine when a release is created -# For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries - -name: Upload Python Package - -on: - release: - types: [ created ] - -jobs: - deploy: - - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v2 - - name: Set up Python - uses: actions/setup-python@v2 - with: - python-version: '3.8' - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install setuptools wheel twine -r requirements.txt - - name: Build and publish - env: - TWINE_USERNAME: ${{ secrets.PYPI_USERNAME }} - TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} - run: | - python setup.py sdist bdist_wheel - twine upload dist/* From 5ee4fece90c3e5d05fc504ca5536f9283e9b0796 Mon Sep 17 00:00:00 2001 From: null8626 Date: Tue, 26 Mar 2024 19:00:48 +0700 Subject: [PATCH 09/33] feat: add python-publish.yml again with PyPI token support --- .github/workflows/python-publish.yml | 31 ++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 .github/workflows/python-publish.yml diff --git a/.github/workflows/python-publish.yml b/.github/workflows/python-publish.yml new file mode 100644 index 0000000..5ff91b0 --- /dev/null +++ b/.github/workflows/python-publish.yml @@ -0,0 +1,31 @@ +# This workflow will upload a Python Package using Twine when a release is created +# For more information see: https://help.github.com/en/actions/language-and-framework-guides/using-python-with-github-actions#publishing-to-package-registries + +name: Upload Python Package + +on: + release: + types: [ created ] + +jobs: + deploy: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - name: Set up Python + uses: actions/setup-python@v2 + with: + python-version: '3.12' + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install setuptools wheel twine -r requirements.txt + - name: Build and publish + env: + TWINE_USERNAME: '__token__' + TWINE_PASSWORD: ${{ secrets.PYPI_PASSWORD }} + run: | + python setup.py sdist bdist_wheel + twine upload dist/* \ No newline at end of file From 82187cb79f15b742c669174c20aac6f726a33565 Mon Sep 17 00:00:00 2001 From: null8626 Date: Tue, 26 Mar 2024 19:31:53 +0700 Subject: [PATCH 10/33] meta: update .gitignore [skip ci] --- .gitignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitignore b/.gitignore index 3d7edd2..1d88922 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,4 @@ -dblpy.egg-info/ +topggpy.egg-info/ topggpy.egg-info/ topgg/__pycache__/ build/ From f4e83c06cc4e8d81298e0438ce83867f682f922b Mon Sep 17 00:00:00 2001 From: null8626 Date: Wed, 27 Mar 2024 12:37:03 +0700 Subject: [PATCH 11/33] meta: change maintainers --- LICENSE | 1 + pyproject.toml | 2 +- topgg/__init__.py | 4 ++-- topgg/py.typed | 0 4 files changed, 4 insertions(+), 3 deletions(-) delete mode 100644 topgg/py.typed diff --git a/LICENSE b/LICENSE index 96aaaf8..066ec50 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,5 @@ Copyright 2021 Assanali Mukhanov & Top.gg +Copyright 2024 null & Top.gg Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: diff --git a/pyproject.toml b/pyproject.toml index 6e789b6..fc1cdce 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,7 +7,7 @@ version = "2.0.1" description = "A simple API wrapper for Top.gg written in Python." readme = "README.rst" license = { text = "MIT" } -authors = [{ name = "Assanali Mukhanov" }, { name = "Norizon" }, { name = "Top.gg" }] +authors = [{ name = "null8626" }, { name = "Top.gg" }] keywords = ["discord", "bot", "topgg", "top.gg"] dependencies = ["aiohttp>=3.9.0"] classifiers = [ diff --git a/topgg/__init__.py b/topgg/__init__.py index eed4b28..d54460a 100644 --- a/topgg/__init__.py +++ b/topgg/__init__.py @@ -5,12 +5,12 @@ ~~~~~~~~~~~~~~~~~~~~~~~~~ A basic wrapper for the Top.gg API. :copyright: (c) 2021 Assanali Mukhanov & Top.gg +:copyright: (c) 2024 null & Top.gg :license: MIT, see LICENSE for more details. """ __title__ = "topggpy" -__author__ = "Assanali Mukhanov" -__maintainer__ = "Norizon" +__author__ = "null8626" __license__ = "MIT" __version__ = "2.0.1" diff --git a/topgg/py.typed b/topgg/py.typed deleted file mode 100644 index e69de29..0000000 From 533295ae1b11c70b9da84b2422edc426f061f499 Mon Sep 17 00:00:00 2001 From: null8626 Date: Wed, 27 Mar 2024 12:37:24 +0700 Subject: [PATCH 12/33] doc: trauma --- README.rst | 8 -------- 1 file changed, 8 deletions(-) diff --git a/README.rst b/README.rst index 8860689..041b963 100644 --- a/README.rst +++ b/README.rst @@ -17,18 +17,10 @@ A simple API wrapper for `Top.gg `_ written in Python, supporti Installation ------------ -Install via pip (recommended) - .. code:: bash pip3 install topggpy -Install from source - -.. code:: bash - - pip3 install git+https://github.com/top-gg-community/python-sdk/ - Documentation ------------- From df9b489ad9b6c938d0958e1d93288cca09b2eb77 Mon Sep 17 00:00:00 2001 From: null8626 Date: Wed, 27 Mar 2024 13:07:46 +0700 Subject: [PATCH 13/33] meta: remove duplicated path in .gitignore --- .gitignore | 1 - 1 file changed, 1 deletion(-) diff --git a/.gitignore b/.gitignore index 1d88922..41a6149 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,4 @@ topggpy.egg-info/ -topggpy.egg-info/ topgg/__pycache__/ build/ dist/ From eda566e9234883ed0ca9d8803bda301a4092d425 Mon Sep 17 00:00:00 2001 From: null8626 Date: Wed, 27 Mar 2024 13:31:06 +0700 Subject: [PATCH 14/33] meta: update LICENSE --- LICENSE | 2 +- topgg/__init__.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/LICENSE b/LICENSE index 066ec50..c4cd04c 100644 --- a/LICENSE +++ b/LICENSE @@ -1,5 +1,5 @@ Copyright 2021 Assanali Mukhanov & Top.gg -Copyright 2024 null & Top.gg +Copyright 2024 null8626 & Top.gg Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: diff --git a/topgg/__init__.py b/topgg/__init__.py index d54460a..3c5f845 100644 --- a/topgg/__init__.py +++ b/topgg/__init__.py @@ -5,7 +5,7 @@ ~~~~~~~~~~~~~~~~~~~~~~~~~ A basic wrapper for the Top.gg API. :copyright: (c) 2021 Assanali Mukhanov & Top.gg -:copyright: (c) 2024 null & Top.gg +:copyright: (c) 2024 null8626 & Top.gg :license: MIT, see LICENSE for more details. """ From e1460488291baa81cced9330072f66680e5a5e30 Mon Sep 17 00:00:00 2001 From: null8626 Date: Thu, 28 Mar 2024 07:55:47 +0700 Subject: [PATCH 15/33] *: use ruff, remove need for default_bot_id, deprecate get_bots, make def_avatar optional --- .gitattributes | 1 + .github/workflows/python-package.yml | 7 +- .isort.cfg | 3 - MANIFEST.in | 19 +- docs/whats_new.rst | 12 ++ examples/discordpy_example/__main__.py | 1 - examples/hikari_example/__main__.py | 1 - examples/hikari_example/callbacks/autopost.py | 5 +- examples/hikari_example/callbacks/webhook.py | 1 + requirements-dev.txt | 22 +-- ruff.toml | 9 + tests/test_autopost.py | 176 +++++++++--------- tests/test_client.py | 48 ++--- tests/test_data_container.py | 16 +- tests/test_ratelimiter.py | 56 +++--- tests/test_type.py | 19 +- tests/test_webhook.py | 156 ++++++++-------- topgg/autopost.py | 42 ++--- topgg/client.py | 122 ++++-------- topgg/data.py | 18 +- topgg/http.py | 29 ++- topgg/ratelimiter.py | 7 +- topgg/types.py | 2 +- topgg/webhook.py | 59 +++--- 24 files changed, 352 insertions(+), 479 deletions(-) create mode 100644 .gitattributes delete mode 100644 .isort.cfg create mode 100644 ruff.toml diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..44b4224 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +* eol=lf \ No newline at end of file diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index c24b9ba..473cba8 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -30,12 +30,9 @@ jobs: - name: Install itself run: | python -m pip install . - - name: Lint with flake8 + - name: Lint with ruff run: | - # stop the build if there are Python syntax errors or undefined names - flake8 . --count --select=E9,F63,F7,F82 --show-source --statistics - # exit-zero treats all errors as warnings. The GitHub editor is 127 chars wide - flake8 . --count --exit-zero --max-complexity=10 --max-line-length=120 --statistics + ruff check - name: Test with pytest run: | pytest diff --git a/.isort.cfg b/.isort.cfg deleted file mode 100644 index 317f1d3..0000000 --- a/.isort.cfg +++ /dev/null @@ -1,3 +0,0 @@ -[settings] -profile=black -multi_line_output=3 \ No newline at end of file diff --git a/MANIFEST.in b/MANIFEST.in index dc068af..5dec6b1 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -1,3 +1,16 @@ -include LICENSE -include requirements.txt -include README.rst +prune .github +prune .ruff_cache +prune docs +prune examples +prune scripts +prune tests +exclude .gitattributes +exclude .gitignore +exclude .readthedocs.yml +exclude mypy.ini +exclude pytest.ini +exclude requirements-dev.txt +exclude requirements-docs.txt +exclude ruff.toml +exclude ISSUE_TEMPLATE.md +exclude PULL_REQUEST_TEMPLATE.md \ No newline at end of file diff --git a/docs/whats_new.rst b/docs/whats_new.rst index 8fc1d0e..23e3ffe 100644 --- a/docs/whats_new.rst +++ b/docs/whats_new.rst @@ -8,6 +8,18 @@ What's New This page keeps a detailed human friendly rendering of what's new and changed in specific versions. +v2.0.1 +====== +* Added Python 3.12 support +* Dropped Python 3.6 and 3.7 support +* Removed the need to manually set a ``default_bot_id`` property +* :attr:`BotData.def_avatar` is now an optional string +* :meth:`DBLClient.get_bots` is now deprecated +* :meth:`DBLClient.get_guild_count` no longer accepts a ``bot_id`` argument +* :meth:`DBLClient.get_bot_votes` no longer raises a ``ClientException`` without a ``default_bot_id`` property +* :meth:`DBLClient.get_bot_info` no longer raises a ``ClientException`` without a ``default_bot_id`` property +* :meth:`DBLClient.generate_widget`` no longer raises a ``ClientException`` without a ``default_bot_id`` property + v2.0.0a ======= * :obj:`~.DBLClient` now doesn't take in ``discord.Client`` instance diff --git a/examples/discordpy_example/__main__.py b/examples/discordpy_example/__main__.py index f1c1f6d..67e5823 100644 --- a/examples/discordpy_example/__main__.py +++ b/examples/discordpy_example/__main__.py @@ -39,7 +39,6 @@ @client.event async def on_ready(): assert client.user is not None - dblclient.default_bot_id = client.user.id # if it's ready, then the event loop's run, # hence it's safe starting the autopost here diff --git a/examples/hikari_example/__main__.py b/examples/hikari_example/__main__.py index 0bef502..857329c 100644 --- a/examples/hikari_example/__main__.py +++ b/examples/hikari_example/__main__.py @@ -40,7 +40,6 @@ async def on_started(event: hikari.StartedEvent): me: hikari.OwnUser = event.app.get_me() assert me is not None - dblclient.default_bot_id = me.id # since StartedEvent is a lifetime event # this event will only get dispatched once diff --git a/examples/hikari_example/callbacks/autopost.py b/examples/hikari_example/callbacks/autopost.py index 3ac467b..6bab89f 100644 --- a/examples/hikari_example/callbacks/autopost.py +++ b/examples/hikari_example/callbacks/autopost.py @@ -29,6 +29,7 @@ _LOGGER = logging.getLogger("callbacks.autopost") + # these functions can be async too! def on_autopost_success( # uncomment this if you want to get access to app @@ -56,6 +57,4 @@ def on_autopost_error( def stats(app: hikari.GatewayBot = topgg.data(hikari.GatewayBot)): - return topgg.StatsWrapper( - guild_count=len(app.cache.get_guilds_view()), shard_count=app.shard_count - ) + return topgg.StatsWrapper(guild_count=len(app.cache.get_guilds_view()), shard_count=app.shard_count) diff --git a/examples/hikari_example/callbacks/webhook.py b/examples/hikari_example/callbacks/webhook.py index 50c53a7..49daeda 100644 --- a/examples/hikari_example/callbacks/webhook.py +++ b/examples/hikari_example/callbacks/webhook.py @@ -31,6 +31,7 @@ _LOGGER = logging.getLogger("callbacks.webhook") + # this can be async too! @topgg.endpoint("/dblwebhook", topgg.WebhookType.BOT, "youshallnotpass") async def endpoint( diff --git a/requirements-dev.txt b/requirements-dev.txt index e5d5d95..36ad305 100644 --- a/requirements-dev.txt +++ b/requirements-dev.txt @@ -1,13 +1,9 @@ -# Formatting -git+https://github.com/timothycrosley/isort -git+https://github.com/psf/black - -# Unit Testing -mock -pytest -pytest-asyncio -pytest-mock -pytest-cov - -# Linting -flake8 +# Formatting and Linting +ruff + +# Unit Testing +mock +pytest +pytest-asyncio +pytest-mock +pytest-cov \ No newline at end of file diff --git a/ruff.toml b/ruff.toml new file mode 100644 index 0000000..650555f --- /dev/null +++ b/ruff.toml @@ -0,0 +1,9 @@ +line-length = 120 + +[format] +docstring-code-format = true +docstring-code-line-length = 120 +line-ending = "lf" + +[lint] +ignore = ["E722", "F401", "F403"] \ No newline at end of file diff --git a/tests/test_autopost.py b/tests/test_autopost.py index 7cf39c1..07d60a5 100644 --- a/tests/test_autopost.py +++ b/tests/test_autopost.py @@ -1,92 +1,84 @@ -import datetime - -import mock -import pytest -from aiohttp import ClientSession -from pytest_mock import MockerFixture - -from topgg import DBLClient -from topgg.autopost import AutoPoster -from topgg.errors import ServerError, TopGGException, Unauthorized - - -@pytest.fixture -def session() -> ClientSession: - return mock.Mock(ClientSession) - - -@pytest.fixture -def autopost(session: ClientSession) -> AutoPoster: - return AutoPoster(DBLClient("", session=session)) - - -@pytest.mark.asyncio -async def test_AutoPoster_breaks_autopost_loop_on_401( - mocker: MockerFixture, session: ClientSession -) -> None: - response = mock.Mock("reason, status") - response.reason = "Unauthorized" - response.status = 401 - - mocker.patch( - "topgg.DBLClient.post_guild_count", side_effect=Unauthorized(response, {}) - ) - - callback = mock.Mock() - autopost = DBLClient("", session=session).autopost().stats(callback) - assert isinstance(autopost, AutoPoster) - assert not isinstance(autopost.stats()(callback), AutoPoster) - - with pytest.raises(Unauthorized): - await autopost.start() - - callback.assert_called_once() - assert not autopost.is_running - - -@pytest.mark.asyncio -async def test_AutoPoster_raises_missing_stats(autopost: AutoPoster) -> None: - with pytest.raises( - TopGGException, match="you must provide a callback that returns the stats." - ): - await autopost.start() - - -@pytest.mark.asyncio -async def test_AutoPoster_raises_already_running(autopost: AutoPoster) -> None: - autopost.stats(mock.Mock()).start() - with pytest.raises(TopGGException, match="the autopost is already running."): - await autopost.start() - - -@pytest.mark.asyncio -async def test_AutoPoster_interval_too_short(autopost: AutoPoster) -> None: - with pytest.raises(ValueError, match="interval must be greated than 900 seconds."): - autopost.set_interval(50) - - -@pytest.mark.asyncio -async def test_AutoPoster_error_callback( - mocker: MockerFixture, autopost: AutoPoster -) -> None: - error_callback = mock.Mock() - response = mock.Mock("reason, status") - response.reason = "Internal Server Error" - response.status = 500 - side_effect = ServerError(response, {}) - - mocker.patch("topgg.DBLClient.post_guild_count", side_effect=side_effect) - task = autopost.on_error(error_callback).stats(mock.Mock()).start() - autopost.stop() - await task - error_callback.assert_called_once_with(side_effect) - - -def test_AutoPoster_interval(autopost: AutoPoster): - assert autopost.interval == 900 - autopost.set_interval(datetime.timedelta(hours=1)) - assert autopost.interval == 3600 - autopost.interval = datetime.timedelta(hours=2) - assert autopost.interval == 7200 - autopost.interval = 3600 - assert autopost.interval == 3600 +import datetime + +import mock +import pytest +from aiohttp import ClientSession +from pytest_mock import MockerFixture + +from topgg import DBLClient +from topgg.autopost import AutoPoster +from topgg.errors import ServerError, TopGGException, Unauthorized + + +@pytest.fixture +def session() -> ClientSession: + return mock.Mock(ClientSession) + + +@pytest.fixture +def autopost(session: ClientSession) -> AutoPoster: + return AutoPoster(DBLClient("", session=session)) + + +@pytest.mark.asyncio +async def test_AutoPoster_breaks_autopost_loop_on_401(mocker: MockerFixture, session: ClientSession) -> None: + response = mock.Mock("reason, status") + response.reason = "Unauthorized" + response.status = 401 + + mocker.patch("topgg.DBLClient.post_guild_count", side_effect=Unauthorized(response, {})) + + callback = mock.Mock() + autopost = DBLClient("", session=session).autopost().stats(callback) + assert isinstance(autopost, AutoPoster) + assert not isinstance(autopost.stats()(callback), AutoPoster) + + with pytest.raises(Unauthorized): + await autopost.start() + + callback.assert_called_once() + assert not autopost.is_running + + +@pytest.mark.asyncio +async def test_AutoPoster_raises_missing_stats(autopost: AutoPoster) -> None: + with pytest.raises(TopGGException, match="you must provide a callback that returns the stats."): + await autopost.start() + + +@pytest.mark.asyncio +async def test_AutoPoster_raises_already_running(autopost: AutoPoster) -> None: + autopost.stats(mock.Mock()).start() + with pytest.raises(TopGGException, match="the autopost is already running."): + await autopost.start() + + +@pytest.mark.asyncio +async def test_AutoPoster_interval_too_short(autopost: AutoPoster) -> None: + with pytest.raises(ValueError, match="interval must be greated than 900 seconds."): + autopost.set_interval(50) + + +@pytest.mark.asyncio +async def test_AutoPoster_error_callback(mocker: MockerFixture, autopost: AutoPoster) -> None: + error_callback = mock.Mock() + response = mock.Mock("reason, status") + response.reason = "Internal Server Error" + response.status = 500 + side_effect = ServerError(response, {}) + + mocker.patch("topgg.DBLClient.post_guild_count", side_effect=side_effect) + task = autopost.on_error(error_callback).stats(mock.Mock()).start() + autopost.stop() + await task + error_callback.assert_called_once_with(side_effect) + + +def test_AutoPoster_interval(autopost: AutoPoster): + assert autopost.interval == 900 + autopost.set_interval(datetime.timedelta(hours=1)) + assert autopost.interval == 3600 + autopost.interval = datetime.timedelta(hours=2) + assert autopost.interval == 7200 + autopost.interval = 3600 + assert autopost.interval == 3600 diff --git a/tests/test_client.py b/tests/test_client.py index fb634ea..fbd7a19 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -1,5 +1,3 @@ -import typing as t - import mock import pytest from aiohttp import ClientSession @@ -8,6 +6,9 @@ from topgg import errors +MOCK_TOKEN = "amogus.eyJpZCI6IjEwMjY1MjU1NjgzNDQyNjQ3MjQiLCJib3QiOnRydWUsImlhdCI6MTY5OTk4NDYyM30.amogus" + + @pytest.fixture def session() -> ClientSession: return mock.Mock(ClientSession) @@ -15,14 +16,14 @@ def session() -> ClientSession: @pytest.fixture def client() -> topgg.DBLClient: - client = topgg.DBLClient(token="TOKEN", default_bot_id=1234) + client = topgg.DBLClient(token=MOCK_TOKEN) client.http = mock.Mock(topgg.http.HTTPClient) return client @pytest.mark.asyncio async def test_HTTPClient_with_external_session(session: ClientSession): - http = topgg.http.HTTPClient("TOKEN", session=session) + http = topgg.http.HTTPClient(MOCK_TOKEN, session=session) assert not http._own_session await http.close() session.close.assert_not_called() @@ -30,26 +31,16 @@ async def test_HTTPClient_with_external_session(session: ClientSession): @pytest.mark.asyncio async def test_HTTPClient_with_no_external_session(session: ClientSession): - http = topgg.http.HTTPClient("TOKEN") + http = topgg.http.HTTPClient(MOCK_TOKEN) http.session = session assert http._own_session await http.close() session.close.assert_called_once() -@pytest.mark.asyncio -async def test_DBLClient_get_bot_votes_with_no_default_bot_id(): - client = topgg.DBLClient("TOKEN") - with pytest.raises( - errors.ClientException, - match="you must set default_bot_id when constructing the client.", - ): - await client.get_bot_votes() - - @pytest.mark.asyncio async def test_DBLClient_post_guild_count_with_no_args(): - client = topgg.DBLClient("TOKEN", default_bot_id=1234) + client = topgg.DBLClient(MOCK_TOKEN) with pytest.raises(TypeError, match="stats or guild_count must be provided."): await client.post_guild_count() @@ -67,20 +58,9 @@ async def test_DBLClient_post_guild_count_with_no_args(): ), ], ) -@pytest.mark.asyncio -async def test_DBLClient_get_guild_count_with_no_id( - method: t.Callable, kwargs: t.Dict[str, t.Any] -): - client = topgg.DBLClient("TOKEN") - with pytest.raises( - errors.ClientException, match="bot_id or default_bot_id is unset." - ): - await method(client, **kwargs) - - @pytest.mark.asyncio async def test_closed_DBLClient_raises_exception(): - client = topgg.DBLClient("TOKEN") + client = topgg.DBLClient(MOCK_TOKEN) assert not client.is_closed await client.close() assert client.is_closed @@ -88,6 +68,11 @@ async def test_closed_DBLClient_raises_exception(): await client.get_weekend_status() +@pytest.mark.asyncio +async def test_closed_DBLClient_bot_id(): + assert client.bot_id == 1026525568344264724 + + @pytest.mark.asyncio async def test_DBLClient_get_weekend_status(client: topgg.DBLClient): client.http.get_weekend_status = mock.AsyncMock() @@ -116,13 +101,6 @@ async def test_DBLClient_get_bot_votes(client: topgg.DBLClient): client.http.get_bot_votes.assert_called_once() -@pytest.mark.asyncio -async def test_DBLClient_get_bots(client: topgg.DBLClient): - client.http.get_bots = mock.AsyncMock(return_value={"results": []}) - await client.get_bots() - client.http.get_bots.assert_called_once() - - @pytest.mark.asyncio async def test_DBLClient_get_user_info(client: topgg.DBLClient): client.http.get_user_info = mock.AsyncMock(return_value={}) diff --git a/tests/test_data_container.py b/tests/test_data_container.py index 978574f..f89466e 100644 --- a/tests/test_data_container.py +++ b/tests/test_data_container.py @@ -13,20 +13,13 @@ def data_container() -> DataContainerMixin: return dc -async def _async_callback( - text: str = data(str), number: int = data(int), mapping: dict = data(dict) -): - ... +async def _async_callback(text: str = data(str), number: int = data(int), mapping: dict = data(dict)): ... -def _sync_callback( - text: str = data(str), number: int = data(int), mapping: dict = data(dict) -): - ... +def _sync_callback(text: str = data(str), number: int = data(int), mapping: dict = data(dict)): ... -def _invalid_callback(number: float = data(float)): - ... +def _invalid_callback(number: float = data(float)): ... @pytest.mark.asyncio @@ -42,8 +35,7 @@ async def test_data_container_invoke_sync_callback(data_container: DataContainer def test_data_container_raises_data_already_exists(data_container: DataContainerMixin): with pytest.raises( TopGGException, - match=" already exists. If you wish to override it, " - "pass True into the override parameter.", + match=" already exists. If you wish to override it, " "pass True into the override parameter.", ): data_container.set_data("TEST") diff --git a/tests/test_ratelimiter.py b/tests/test_ratelimiter.py index f1fbed6..9153b3a 100644 --- a/tests/test_ratelimiter.py +++ b/tests/test_ratelimiter.py @@ -1,28 +1,28 @@ -import pytest - -from topgg.ratelimiter import AsyncRateLimiter - -n = period = 10 - - -@pytest.fixture -def limiter() -> AsyncRateLimiter: - return AsyncRateLimiter(max_calls=n, period=period) - - -@pytest.mark.asyncio -async def test_AsyncRateLimiter_calls(limiter: AsyncRateLimiter) -> None: - for _ in range(n): - async with limiter: - pass - - assert len(limiter.calls) == limiter.max_calls == n - - -@pytest.mark.asyncio -async def test_AsyncRateLimiter_timespan_property(limiter: AsyncRateLimiter) -> None: - for _ in range(n): - async with limiter: - pass - - assert limiter._timespan < period +import pytest + +from topgg.ratelimiter import AsyncRateLimiter + +n = period = 10 + + +@pytest.fixture +def limiter() -> AsyncRateLimiter: + return AsyncRateLimiter(max_calls=n, period=period) + + +@pytest.mark.asyncio +async def test_AsyncRateLimiter_calls(limiter: AsyncRateLimiter) -> None: + for _ in range(n): + async with limiter: + pass + + assert len(limiter.calls) == limiter.max_calls == n + + +@pytest.mark.asyncio +async def test_AsyncRateLimiter_timespan_property(limiter: AsyncRateLimiter) -> None: + for _ in range(n): + async with limiter: + pass + + assert limiter._timespan < period diff --git a/tests/test_type.py b/tests/test_type.py index caec363..8cea66c 100644 --- a/tests/test_type.py +++ b/tests/test_type.py @@ -143,12 +143,7 @@ def test_widget_options_fields(widget_options: types.WidgetOptions) -> None: for attr in widget_options: if "id" in attr.lower(): assert isinstance(widget_options[attr], int) or widget_options[attr] is None - assert ( - widget_options.get(attr) - == widget_options[attr] - == widget_options[attr] - == getattr(widget_options, attr) - ) + assert widget_options.get(attr) == widget_options[attr] == widget_options[attr] == getattr(widget_options, attr) def test_vote_data_fields(vote_data: types.VoteDataDict) -> None: @@ -165,11 +160,7 @@ def test_bot_vote_data_fields(bot_vote_data: types.BotVoteData) -> None: assert isinstance(bot_vote_data["bot"], int) for attr in bot_vote_data: - assert ( - getattr(bot_vote_data, attr) - == bot_vote_data.get(attr) - == bot_vote_data[attr] - ) + assert getattr(bot_vote_data, attr) == bot_vote_data.get(attr) == bot_vote_data[attr] def test_server_vote_data_fields(server_vote_data: types.BotVoteData) -> None: @@ -178,11 +169,7 @@ def test_server_vote_data_fields(server_vote_data: types.BotVoteData) -> None: assert isinstance(server_vote_data["guild"], int) for attr in server_vote_data: - assert ( - getattr(server_vote_data, attr) - == server_vote_data.get(attr) - == server_vote_data[attr] - ) + assert getattr(server_vote_data, attr) == server_vote_data.get(attr) == server_vote_data[attr] def test_bot_stats_data_attrs(bot_stats_data: types.BotStatsData) -> None: diff --git a/tests/test_webhook.py b/tests/test_webhook.py index 8ef3c71..db1da09 100644 --- a/tests/test_webhook.py +++ b/tests/test_webhook.py @@ -1,80 +1,76 @@ -import typing as t - -import aiohttp -import mock -import pytest - -from topgg import WebhookManager, WebhookType -from topgg.errors import TopGGException - -auth = "youshallnotpass" - - -@pytest.fixture -def webhook_manager() -> WebhookManager: - return ( - WebhookManager() - .endpoint() - .type(WebhookType.BOT) - .auth(auth) - .route("/dbl") - .callback(print) - .add_to_manager() - .endpoint() - .type(WebhookType.GUILD) - .auth(auth) - .route("/dsl") - .callback(print) - .add_to_manager() - ) - - -def test_WebhookManager_routes(webhook_manager: WebhookManager) -> None: - assert len(webhook_manager.app.router.routes()) == 2 - - -@pytest.mark.asyncio -@pytest.mark.parametrize( - "headers, result, state", - [({"authorization": auth}, 200, True), ({}, 401, False)], -) -async def test_WebhookManager_validates_auth( - webhook_manager: WebhookManager, headers: t.Dict[str, str], result: int, state: bool -) -> None: - await webhook_manager.start(5000) - - try: - for path in ("dbl", "dsl"): - async with aiohttp.request( - "POST", f"http://localhost:5000/{path}", headers=headers, json={} - ) as r: - assert r.status == result - finally: - await webhook_manager.close() - assert not webhook_manager.is_running - - -def test_WebhookEndpoint_callback_unset(webhook_manager: WebhookManager): - with pytest.raises( - TopGGException, - match="endpoint missing callback.", - ): - webhook_manager.endpoint().add_to_manager() - - -def test_WebhookEndpoint_route_unset(webhook_manager: WebhookManager): - with pytest.raises( - TopGGException, - match="endpoint missing type.", - ): - webhook_manager.endpoint().callback(mock.Mock()).add_to_manager() - - -def test_WebhookEndpoint_type_unset(webhook_manager: WebhookManager): - with pytest.raises( - TopGGException, - match="endpoint missing route.", - ): - webhook_manager.endpoint().callback(mock.Mock()).type( - WebhookType.BOT - ).add_to_manager() +import typing as t + +import aiohttp +import mock +import pytest + +from topgg import WebhookManager, WebhookType +from topgg.errors import TopGGException + +auth = "youshallnotpass" + + +@pytest.fixture +def webhook_manager() -> WebhookManager: + return ( + WebhookManager() + .endpoint() + .type(WebhookType.BOT) + .auth(auth) + .route("/dbl") + .callback(print) + .add_to_manager() + .endpoint() + .type(WebhookType.GUILD) + .auth(auth) + .route("/dsl") + .callback(print) + .add_to_manager() + ) + + +def test_WebhookManager_routes(webhook_manager: WebhookManager) -> None: + assert len(webhook_manager.app.router.routes()) == 2 + + +@pytest.mark.asyncio +@pytest.mark.parametrize( + "headers, result, state", + [({"authorization": auth}, 200, True), ({}, 401, False)], +) +async def test_WebhookManager_validates_auth( + webhook_manager: WebhookManager, headers: t.Dict[str, str], result: int, state: bool +) -> None: + await webhook_manager.start(5000) + + try: + for path in ("dbl", "dsl"): + async with aiohttp.request("POST", f"http://localhost:5000/{path}", headers=headers, json={}) as r: + assert r.status == result + finally: + await webhook_manager.close() + assert not webhook_manager.is_running + + +def test_WebhookEndpoint_callback_unset(webhook_manager: WebhookManager): + with pytest.raises( + TopGGException, + match="endpoint missing callback.", + ): + webhook_manager.endpoint().add_to_manager() + + +def test_WebhookEndpoint_route_unset(webhook_manager: WebhookManager): + with pytest.raises( + TopGGException, + match="endpoint missing type.", + ): + webhook_manager.endpoint().callback(mock.Mock()).add_to_manager() + + +def test_WebhookEndpoint_type_unset(webhook_manager: WebhookManager): + with pytest.raises( + TopGGException, + match="endpoint missing route.", + ): + webhook_manager.endpoint().callback(mock.Mock()).type(WebhookType.BOT).add_to_manager() diff --git a/topgg/autopost.py b/topgg/autopost.py index 3bfe4af..96aa988 100644 --- a/topgg/autopost.py +++ b/topgg/autopost.py @@ -78,17 +78,13 @@ def __init__(self, client: "DBLClient") -> None: def _default_error_handler(self, exception: Exception) -> None: print("Ignoring exception in auto post loop:", file=sys.stderr) - traceback.print_exception( - type(exception), exception, exception.__traceback__, file=sys.stderr - ) + traceback.print_exception(type(exception), exception, exception.__traceback__, file=sys.stderr) @t.overload - def on_success(self, callback: None) -> t.Callable[[CallbackT], CallbackT]: - ... + def on_success(self, callback: None) -> t.Callable[[CallbackT], CallbackT]: ... @t.overload - def on_success(self, callback: CallbackT) -> "AutoPoster": - ... + def on_success(self, callback: CallbackT) -> "AutoPoster": ... def on_success(self, callback: t.Any = None) -> t.Any: """ @@ -103,15 +99,15 @@ def on_success(self, callback: t.Any = None) -> t.Any: # The following are valid. autopost = dblclient.autopost().on_success(lambda: print("Success!")) + # Used as decorator, the decorated function will become the AutoPoster object. @autopost.on_success - def autopost(): - ... + def autopost(): ... + # Used as decorator factory, the decorated function will still be the function itself. @autopost.on_success() - def on_success(): - ... + def on_success(): ... """ if callback is not None: self._success = callback @@ -124,12 +120,10 @@ def decorator(callback: CallbackT) -> CallbackT: return decorator @t.overload - def on_error(self, callback: None) -> t.Callable[[CallbackT], CallbackT]: - ... + def on_error(self, callback: None) -> t.Callable[[CallbackT], CallbackT]: ... @t.overload - def on_error(self, callback: CallbackT) -> "AutoPoster": - ... + def on_error(self, callback: CallbackT) -> "AutoPoster": ... def on_error(self, callback: t.Any = None) -> t.Any: """ @@ -148,15 +142,15 @@ def on_error(self, callback: t.Any = None) -> t.Any: # The following are valid. autopost = dblclient.autopost().on_error(lambda exc: print("Failed posting stats!", exc)) + # Used as decorator, the decorated function will become the AutoPoster object. @autopost.on_error - def autopost(exc: Exception): - ... + def autopost(exc: Exception): ... + # Used as decorator factory, the decorated function will still be the function itself. @autopost.on_error() - def on_error(exc: Exception): - ... + def on_error(exc: Exception): ... """ if callback is not None: self._error = callback @@ -169,12 +163,10 @@ def decorator(callback: CallbackT) -> CallbackT: return decorator @t.overload - def stats(self, callback: None) -> t.Callable[[StatsCallbackT], StatsCallbackT]: - ... + def stats(self, callback: None) -> t.Callable[[StatsCallbackT], StatsCallbackT]: ... @t.overload - def stats(self, callback: StatsCallbackT) -> "AutoPoster": - ... + def stats(self, callback: StatsCallbackT) -> "AutoPoster": ... def stats(self, callback: t.Any = None) -> t.Any: """ @@ -295,9 +287,7 @@ def start(self) -> "asyncio.Task[None]": If there's no callback provided or the autopost is already running. """ if not hasattr(self, "_stats"): - raise errors.TopGGException( - "you must provide a callback that returns the stats." - ) + raise errors.TopGGException("you must provide a callback that returns the stats.") if self.is_running: raise errors.TopGGException("the autopost is already running.") diff --git a/topgg/client.py b/topgg/client.py index 0f1a72d..ff7717e 100644 --- a/topgg/client.py +++ b/topgg/client.py @@ -3,6 +3,7 @@ # The MIT License (MIT) # Copyright (c) 2021 Assanali Mukhanov +# Copyright (c) 2024 null8626 # Permission is hereby granted, free of charge, to any person obtaining a # copy of this software and associated documentation files (the "Software"), @@ -24,7 +25,11 @@ __all__ = ["DBLClient"] +import base64 +import json +import re import typing as t +import warnings import aiohttp @@ -45,28 +50,35 @@ class DBLClient(DataContainerMixin): token (:obj:`str`): Your bot's Top.gg API Token. Keyword Args: - default_bot_id (:obj:`typing.Optional` [ :obj:`int` ]) - The default bot_id. You can override this by passing it when calling a method. session (:class:`aiohttp.ClientSession`) An `aiohttp session`_ to use for requests to the API. **kwargs: Arbitrary kwargs to be passed to :class:`aiohttp.ClientSession` if session was not provided. """ - __slots__ = ("http", "default_bot_id", "_token", "_is_closed", "_autopost") + __slots__ = ("http", "bot_id", "_token", "_is_closed", "_autopost") http: HTTPClient def __init__( self, token: str, *, - default_bot_id: t.Optional[int] = None, session: t.Optional[aiohttp.ClientSession] = None, **kwargs: t.Any, ) -> None: super().__init__() self._token = token - self.default_bot_id = default_bot_id + + try: + encoded_json = re.sub(r"[^a-zA-Z0-9\+\/]+", "", token.split(".")[1]) + missing_padding = len(encoded_json) % 4 + if missing_padding: + encoded_json += "=" * (4 - missing_padding) + + self.bot_id = int(json.loads(base64.b64decode(encoded_json))["id"]) + except: + raise errors.ClientException("invalid token.") + self._is_closed = False if session is not None: self.http = HTTPClient(token, session=session) @@ -83,13 +95,6 @@ async def _ensure_session(self) -> None: if not hasattr(self, "http"): self.http = HTTPClient(self._token, session=None) - def _validate_and_get_bot_id(self, bot_id: t.Optional[int]) -> int: - bot_id = bot_id or self.default_bot_id - if bot_id is None: - raise errors.ClientException("bot_id or default_bot_id is unset.") - - return bot_id - async def get_weekend_status(self) -> bool: """Gets weekend status from Top.gg. @@ -105,8 +110,7 @@ async def get_weekend_status(self) -> bool: return data["is_weekend"] @t.overload - async def post_guild_count(self, stats: types.StatsWrapper) -> None: - ... + async def post_guild_count(self, stats: types.StatsWrapper) -> None: ... @t.overload async def post_guild_count( @@ -115,8 +119,7 @@ async def post_guild_count( guild_count: t.Union[int, t.List[int]], shard_count: t.Optional[int] = None, shard_id: t.Optional[int] = None, - ) -> None: - ... + ) -> None: ... async def post_guild_count( self, @@ -161,28 +164,19 @@ async def post_guild_count( await self._ensure_session() await self.http.post_guild_count(guild_count, shard_count, shard_id) - async def get_guild_count( - self, bot_id: t.Optional[int] = None - ) -> types.BotStatsData: - """Gets a bot's guild count and shard info from Top.gg. - - Args: - bot_id (int) - ID of the bot you want to look up. Defaults to the provided Client object. + async def get_guild_count(self) -> types.BotStatsData: + """Gets this bot's guild count and shard info from Top.gg. Returns: :obj:`~.types.BotStatsData`: The guild count and shards of a bot on Top.gg. Raises: - :obj:`~.errors.ClientException` - If neither bot_id or default_bot_id was set. :obj:`~.errors.ClientStateException` If the client has been closed. """ - bot_id = self._validate_and_get_bot_id(bot_id) await self._ensure_session() - response = await self.http.get_guild_count(bot_id) + response = await self.http.get_guild_count(self.bot_id) return types.BotStatsData(**response) async def get_bot_votes(self) -> t.List[types.BriefUserData]: @@ -196,17 +190,11 @@ async def get_bot_votes(self) -> t.List[types.BriefUserData]: Users who voted for your bot. Raises: - :obj:`~.errors.ClientException` - If default_bot_id isn't provided when constructing the client. :obj:`~.errors.ClientStateException` If the client has been closed. """ - if not self.default_bot_id: - raise errors.ClientException( - "you must set default_bot_id when constructing the client." - ) await self._ensure_session() - response = await self.http.get_bot_votes(self.default_bot_id) + response = await self.http.get_bot_votes(self.bot_id) return [types.BriefUserData(**user) for user in response] async def get_bot_info(self, bot_id: t.Optional[int] = None) -> types.BotData: @@ -216,7 +204,7 @@ async def get_bot_info(self, bot_id: t.Optional[int] = None) -> types.BotData: Args: bot_id (int) - ID of the bot to look up. Defaults to the provided Client object. + ID of the bot to look up. Defaults to this bot's ID. Returns: :obj:`~.types.BotData`: @@ -224,14 +212,11 @@ async def get_bot_info(self, bot_id: t.Optional[int] = None) -> types.BotData: `here `_. Raises: - :obj:`~.errors.ClientException` - If neither bot_id or default_bot_id was set. :obj:`~.errors.ClientStateException` If the client has been closed. """ - bot_id = self._validate_and_get_bot_id(bot_id) await self._ensure_session() - response = await self.http.get_bot_info(bot_id) + response = await self.http.get_bot_info(bot_id or self.bot_id) return types.BotData(**response) async def get_bots( @@ -242,38 +227,16 @@ async def get_bots( search: t.Optional[t.Dict[str, t.Any]] = None, fields: t.Optional[t.List[str]] = None, ) -> types.DataDict[str, t.Any]: - """This function is a coroutine. - - Gets information about listed bots on Top.gg. - - Args: - limit (int) - The number of results to look up. Defaults to 50. Max 500 allowed. - offset (int) - The amount of bots to skip. Defaults to 0. - sort (str) - The field to sort by. Prefix with ``-`` to reverse the order. - search (:obj:`dict` [ :obj:`str`, :obj:`typing.Any` ]) - The search data. - fields (:obj:`list` [ :obj:`str` ]) - Fields to output. - - Returns: - :obj:`~.types.DataDict`: - Info on bots that match the search query on Top.gg. - - Raises: - :obj:`~.errors.ClientStateException` - If the client has been closed. - """ + """This function is deprecated.""" + + warnings.warn("get_bots is now deprecated.", DeprecationWarning) + sort = sort or "" search = search or {} fields = fields or [] await self._ensure_session() response = await self.http.get_bots(limit, offset, sort, search, fields) - response["results"] = [ - types.BotData(**bot_data) for bot_data in response["results"] - ] + response["results"] = [types.BotData(**bot_data) for bot_data in response["results"]] return types.DataDict(**response) async def get_user_info(self, user_id: int) -> types.UserData: @@ -308,18 +271,11 @@ async def get_user_vote(self, user_id: int) -> bool: :obj:`bool`: Info about the user's vote. Raises: - :obj:`~.errors.ClientException` - If default_bot_id isn't provided when constructing the client. :obj:`~.errors.ClientStateException` If the client has been closed. """ - if not self.default_bot_id: - raise errors.ClientException( - "you must set default_bot_id when constructing the client." - ) - await self._ensure_session() - data = await self.http.get_user_vote(self.default_bot_id, user_id) + data = await self.http.get_user_vote(self.bot_id, user_id) return bool(data["voted"]) def generate_widget(self, *, options: types.WidgetOptions) -> str: @@ -334,28 +290,22 @@ def generate_widget(self, *, options: types.WidgetOptions) -> str: str: Generated widget URL. Raises: - :obj:`~.errors.ClientException` - If bot_id or default_bot_id is unset. TypeError: If options passed is not of type WidgetOptions. """ if not isinstance(options, types.WidgetOptions): - raise TypeError( - "options argument passed to generate_widget must be of type WidgetOptions" - ) - - bot_id = options.id or self.default_bot_id - if bot_id is None: - raise errors.ClientException("bot_id or default_bot_id is unset.") + raise TypeError("options argument passed to generate_widget must be of type WidgetOptions") + bot_id = options.id or self.bot_id widget_query = f"noavatar={str(options.noavatar).lower()}" + for key, value in options.colors.items(): widget_query += f"&{key.lower()}{'' if key.lower().endswith('color') else 'color'}={value:x}" + widget_format = options.format widget_type = f"/{options.type}" if options.type else "" - url = f"""https://top.gg/api/widget{widget_type}/{bot_id}.{widget_format}?{widget_query}""" - return url + return f"https://top.gg/api/widget{widget_type}/{bot_id}.{widget_format}?{widget_query}" async def close(self) -> None: """Closes all connections.""" diff --git a/topgg/data.py b/topgg/data.py index 7126d3b..baafde6 100644 --- a/topgg/data.py +++ b/topgg/data.py @@ -52,6 +52,7 @@ def data(type_: t.Type[T]) -> T: dblclient = topgg.DBLClient(TOKEN).set_data(client) autopost: topgg.AutoPoster = dblclient.autopost() + @autopost.stats() def get_stats(client: Client = topgg.data(Client)): return topgg.StatsWrapper(guild_count=len(client.guilds), shard_count=len(client.shards)) @@ -79,9 +80,7 @@ class DataContainerMixin: def __init__(self) -> None: self._data: t.Dict[t.Type, t.Any] = {type(self): self} - def set_data( - self: DataContainerT, data_: t.Any, *, override: bool = False - ) -> DataContainerT: + def set_data(self: DataContainerT, data_: t.Any, *, override: bool = False) -> DataContainerT: """ Sets data to be available in your functions. @@ -105,20 +104,16 @@ def set_data( return self @t.overload - def get_data(self, type_: t.Type[T]) -> t.Optional[T]: - ... + def get_data(self, type_: t.Type[T]) -> t.Optional[T]: ... @t.overload - def get_data(self, type_: t.Type[T], default: t.Any = None) -> t.Any: - ... + def get_data(self, type_: t.Type[T], default: t.Any = None) -> t.Any: ... def get_data(self, type_: t.Any, default: t.Any = None) -> t.Any: """Gets the injected data.""" return self._data.get(type_, default) - async def _invoke_callback( - self, callback: t.Callable[..., T], *args: t.Any, **kwargs: t.Any - ) -> T: + async def _invoke_callback(self, callback: t.Callable[..., T], *args: t.Any, **kwargs: t.Any) -> T: parameters: t.Mapping[str, inspect.Parameter] try: parameters = inspect.signature(callback).parameters @@ -128,8 +123,7 @@ async def _invoke_callback( signatures: t.Dict[str, Data] = { k: v.default for k, v in parameters.items() - if v.kind is inspect.Parameter.POSITIONAL_OR_KEYWORD - and isinstance(v.default, Data) + if v.kind is inspect.Parameter.POSITIONAL_OR_KEYWORD and isinstance(v.default, Data) } for k, v in signatures.items(): diff --git a/topgg/http.py b/topgg/http.py index 6bd967c..9e34242 100644 --- a/topgg/http.py +++ b/topgg/http.py @@ -3,6 +3,7 @@ # The MIT License (MIT) # Copyright (c) 2021 Assanali Mukhanov +# Copyright (c) 2024 null8626 # Permission is hereby granted, free of charge, to any person obtaining a # copy of this software and associated documentation files (the "Software"), @@ -28,6 +29,7 @@ import json import logging import sys +import warnings from datetime import datetime from typing import Any, Coroutine, Dict, Iterable, List, Optional, Sequence, Union, cast @@ -76,15 +78,9 @@ def __init__( self.token = token self._own_session = session is None self.session: aiohttp.ClientSession = session or aiohttp.ClientSession(**kwargs) - self.global_rate_limiter = AsyncRateLimiter( - max_calls=99, period=1, callback=_rate_limit_handler - ) - self.bot_rate_limiter = AsyncRateLimiter( - max_calls=59, period=60, callback=_rate_limit_handler - ) - self.rate_limiters = AsyncRateLimiterManager( - [self.global_rate_limiter, self.bot_rate_limiter] - ) + self.global_rate_limiter = AsyncRateLimiter(max_calls=99, period=1, callback=_rate_limit_handler) + self.bot_rate_limiter = AsyncRateLimiter(max_calls=59, period=60, callback=_rate_limit_handler) + self.rate_limiters = AsyncRateLimiterManager([self.global_rate_limiter, self.bot_rate_limiter]) self.user_agent = ( f"topggpy (https://github.com/top-gg-community/python-sdk {__version__}) Python/" f"{sys.version_info[0]}.{sys.version_info[1]} aiohttp/{aiohttp.__version__}" @@ -92,11 +88,7 @@ def __init__( async def request(self, method: str, endpoint: str, **kwargs: Any) -> dict: """Handles requests to the API.""" - rate_limiters = ( - self.rate_limiters - if endpoint.startswith("/bots") - else self.global_rate_limiter - ) + rate_limiters = self.rate_limiters if endpoint.startswith("/bots") else self.global_rate_limiter url = f"{self.BASE}{endpoint}" if not self.token: @@ -210,7 +202,10 @@ def get_bots( search: Dict[str, str], fields: Sequence[str], ) -> Coroutine[Any, Any, dict]: - """Gets an object of bots on Top.gg.""" + """This function is now deprecated.""" + + warnings.warn("get_bots is now deprecated.", DeprecationWarning) + limit = min(limit, 500) fields = ", ".join(fields) search = " ".join([f"{field}: {value}" for field, value in search.items()]) @@ -240,9 +235,7 @@ async def _rate_limit_handler(until: float) -> None: """Handles the displayed message when we are ratelimited.""" duration = round(until - datetime.utcnow().timestamp()) mins = duration / 60 - fmt = ( - "We have exhausted a ratelimit quota. Retrying in %.2f seconds (%.3f minutes)." - ) + fmt = "We have exhausted a ratelimit quota. Retrying in %.2f seconds (%.3f minutes)." _LOGGER.warning(fmt, duration, mins) diff --git a/topgg/ratelimiter.py b/topgg/ratelimiter.py index 028a98e..d2d75f7 100644 --- a/topgg/ratelimiter.py +++ b/topgg/ratelimiter.py @@ -102,9 +102,4 @@ async def __aexit__( exc_val: BaseException, exc_tb: TracebackType, ) -> None: - await asyncio.gather( - *[ - manager.__aexit__(exc_type, exc_val, exc_tb) - for manager in self.rate_limiters - ] - ) + await asyncio.gather(*[manager.__aexit__(exc_type, exc_val, exc_tb) for manager in self.rate_limiters]) diff --git a/topgg/types.py b/topgg/types.py index 2da13f9..8bfd067 100644 --- a/topgg/types.py +++ b/topgg/types.py @@ -214,7 +214,7 @@ class BotData(DataDict[str, t.Any]): avatar: t.Optional[str] """The avatar hash of the bot.""" - def_avatar: str + def_avatar: t.Optional[str] """The avatar hash of the bot's default avatar.""" prefix: str diff --git a/topgg/webhook.py b/topgg/webhook.py index 4b94ec2..28b92e9 100644 --- a/topgg/webhook.py +++ b/topgg/webhook.py @@ -74,12 +74,10 @@ def __init__(self) -> None: self._is_running = False @t.overload - def endpoint(self, endpoint_: None = None) -> "BoundWebhookEndpoint": - ... + def endpoint(self, endpoint_: None = None) -> "BoundWebhookEndpoint": ... @t.overload - def endpoint(self, endpoint_: "WebhookEndpoint") -> "WebhookManager": - ... + def endpoint(self, endpoint_: "WebhookEndpoint") -> "WebhookManager": ... def endpoint(self, endpoint_: t.Optional["WebhookEndpoint"] = None) -> t.Any: """Helper method that returns a WebhookEndpoint object. @@ -109,9 +107,7 @@ def endpoint(self, endpoint_: t.Optional["WebhookEndpoint"] = None) -> t.Any: self.app.router.add_post( endpoint_._route, - self._get_handler( - endpoint_._type, endpoint_._auth, endpoint_._callback - ), + self._get_handler(endpoint_._type, endpoint_._auth, endpoint_._callback), ) return self @@ -150,9 +146,7 @@ async def close(self) -> None: await self._webserver.stop() self._is_running = False - def _get_handler( - self, type_: WebhookType, auth: str, callback: t.Callable[..., t.Any] - ) -> _HandlerT: + def _get_handler(self, type_: WebhookType, auth: str, callback: t.Callable[..., t.Any]) -> _HandlerT: async def _handler(request: aiohttp.web.Request) -> web.Response: if request.headers.get("Authorization", "") != auth: return web.Response(status=401, text="Unauthorized") @@ -225,12 +219,10 @@ def auth(self: T, auth_: str) -> T: return self @t.overload - def callback(self, callback_: None) -> t.Callable[[CallbackT], CallbackT]: - ... + def callback(self, callback_: None) -> t.Callable[[CallbackT], CallbackT]: ... @t.overload - def callback(self: T, callback_: CallbackT) -> T: - ... + def callback(self: T, callback_: CallbackT) -> T: ... def callback(self, callback_: t.Any = None) -> t.Any: """ @@ -245,25 +237,21 @@ def callback(self, callback_: t.Any = None) -> t.Any: import topgg webhook_manager = topgg.WebhookManager() - endpoint = ( - topgg.WebhookEndpoint() - .type(topgg.WebhookType.BOT) - .route("/dblwebhook") - .auth("youshallnotpass") - ) + endpoint = topgg.WebhookEndpoint().type(topgg.WebhookType.BOT).route("/dblwebhook").auth("youshallnotpass") # The following are valid. endpoint.callback(lambda vote_data: print("Receives a vote!", vote_data)) + # Used as decorator, the decorated function will become the WebhookEndpoint object. @endpoint.callback - def endpoint(vote_data: topgg.BotVoteData): - ... + def endpoint(vote_data: topgg.BotVoteData): ... + # Used as decorator factory, the decorated function will still be the function itself. @endpoint.callback() - def on_vote(vote_data: topgg.BotVoteData): - ... + def on_vote(vote_data: topgg.BotVoteData): ... + webhook_manager.endpoint(endpoint) """ @@ -286,25 +274,22 @@ class BoundWebhookEndpoint(WebhookEndpoint): import topgg webhook_manager = ( - topgg.WebhookManager() - .endpoint() - .type(topgg.WebhookType.BOT) - .route("/dblwebhook") - .auth("youshallnotpass") + topgg.WebhookManager().endpoint().type(topgg.WebhookType.BOT).route("/dblwebhook").auth("youshallnotpass") ) # The following are valid. endpoint.callback(lambda vote_data: print("Receives a vote!", vote_data)) + # Used as decorator, the decorated function will become the BoundWebhookEndpoint object. @endpoint.callback - def endpoint(vote_data: topgg.BotVoteData): - ... + def endpoint(vote_data: topgg.BotVoteData): ... + # Used as decorator factory, the decorated function will still be the function itself. @endpoint.callback() - def on_vote(vote_data: topgg.BotVoteData): - ... + def on_vote(vote_data: topgg.BotVoteData): ... + endpoint.add_to_manager() """ @@ -330,9 +315,7 @@ def add_to_manager(self) -> WebhookManager: return self.manager -def endpoint( - route: str, type: WebhookType, auth: str = "" -) -> t.Callable[[t.Callable[..., t.Any]], WebhookEndpoint]: +def endpoint(route: str, type: WebhookType, auth: str = "") -> t.Callable[[t.Callable[..., t.Any]], WebhookEndpoint]: """ A decorator factory for instantiating WebhookEndpoint. @@ -353,13 +336,13 @@ def endpoint( import topgg + @topgg.endpoint("/dblwebhook", WebhookType.BOT, "youshallnotpass") async def on_vote( vote_data: topgg.BotVoteData, # database here is an injected data database: Database = topgg.data(Database), - ): - ... + ): ... """ def decorator(func: t.Callable[..., t.Any]) -> WebhookEndpoint: From a4ce9f20612b01527e52c5eeed5f4cf8f5cf011b Mon Sep 17 00:00:00 2001 From: null8626 Date: Thu, 28 Mar 2024 08:05:29 +0700 Subject: [PATCH 16/33] fix: let's see if this fixes it --- tests/test_client.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_client.py b/tests/test_client.py index fbd7a19..d70f241 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -50,6 +50,7 @@ async def test_DBLClient_post_guild_count_with_no_args(): [ (topgg.DBLClient.get_guild_count, {}), (topgg.DBLClient.get_bot_info, {}), + (topgg.DBLClient.get_weekend_status, {}), ( topgg.DBLClient.generate_widget, { From d4a9123a1dd0f74e54d9dabba3d0a4e248d66ed6 Mon Sep 17 00:00:00 2001 From: null8626 Date: Thu, 28 Mar 2024 08:10:41 +0700 Subject: [PATCH 17/33] fix: this should fix it --- tests/test_client.py | 14 -------------- topgg/client.py | 4 ++-- topgg/http.py | 4 ++-- 3 files changed, 4 insertions(+), 18 deletions(-) diff --git a/tests/test_client.py b/tests/test_client.py index d70f241..eaa1857 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -45,20 +45,6 @@ async def test_DBLClient_post_guild_count_with_no_args(): await client.post_guild_count() -@pytest.mark.parametrize( - "method, kwargs", - [ - (topgg.DBLClient.get_guild_count, {}), - (topgg.DBLClient.get_bot_info, {}), - (topgg.DBLClient.get_weekend_status, {}), - ( - topgg.DBLClient.generate_widget, - { - "options": topgg.types.WidgetOptions(), - }, - ), - ], -) @pytest.mark.asyncio async def test_closed_DBLClient_raises_exception(): client = topgg.DBLClient(MOCK_TOKEN) diff --git a/topgg/client.py b/topgg/client.py index ff7717e..eddd829 100644 --- a/topgg/client.py +++ b/topgg/client.py @@ -228,9 +228,9 @@ async def get_bots( fields: t.Optional[t.List[str]] = None, ) -> types.DataDict[str, t.Any]: """This function is deprecated.""" - + warnings.warn("get_bots is now deprecated.", DeprecationWarning) - + sort = sort or "" search = search or {} fields = fields or [] diff --git a/topgg/http.py b/topgg/http.py index 9e34242..0071766 100644 --- a/topgg/http.py +++ b/topgg/http.py @@ -203,9 +203,9 @@ def get_bots( fields: Sequence[str], ) -> Coroutine[Any, Any, dict]: """This function is now deprecated.""" - + warnings.warn("get_bots is now deprecated.", DeprecationWarning) - + limit = min(limit, 500) fields = ", ".join(fields) search = " ".join([f"{field}: {value}" for field, value in search.items()]) From d3b48dcc12d636b0f109e67b0da620f771037b6c Mon Sep 17 00:00:00 2001 From: null8626 Date: Thu, 28 Mar 2024 08:14:02 +0700 Subject: [PATCH 18/33] fix: AAAAAAAAAAHHHHHHH --- tests/test_autopost.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/tests/test_autopost.py b/tests/test_autopost.py index 07d60a5..115297c 100644 --- a/tests/test_autopost.py +++ b/tests/test_autopost.py @@ -10,6 +10,9 @@ from topgg.errors import ServerError, TopGGException, Unauthorized +MOCK_TOKEN = "amogus.eyJpZCI6IjEwMjY1MjU1NjgzNDQyNjQ3MjQiLCJib3QiOnRydWUsImlhdCI6MTY5OTk4NDYyM30.amogus" + + @pytest.fixture def session() -> ClientSession: return mock.Mock(ClientSession) @@ -17,7 +20,7 @@ def session() -> ClientSession: @pytest.fixture def autopost(session: ClientSession) -> AutoPoster: - return AutoPoster(DBLClient("", session=session)) + return AutoPoster(DBLClient(MOCK_TOKEN, session=session)) @pytest.mark.asyncio @@ -29,7 +32,7 @@ async def test_AutoPoster_breaks_autopost_loop_on_401(mocker: MockerFixture, ses mocker.patch("topgg.DBLClient.post_guild_count", side_effect=Unauthorized(response, {})) callback = mock.Mock() - autopost = DBLClient("", session=session).autopost().stats(callback) + autopost = DBLClient(MOCK_TOKEN, session=session).autopost().stats(callback) assert isinstance(autopost, AutoPoster) assert not isinstance(autopost.stats()(callback), AutoPoster) From e7bd0b8049e9cd66400ab1e06fd63c72bf0904ae Mon Sep 17 00:00:00 2001 From: null8626 Date: Thu, 28 Mar 2024 08:16:11 +0700 Subject: [PATCH 19/33] fix: fix bot_id test --- tests/test_client.py | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/tests/test_client.py b/tests/test_client.py index eaa1857..fa11c43 100644 --- a/tests/test_client.py +++ b/tests/test_client.py @@ -56,8 +56,12 @@ async def test_closed_DBLClient_raises_exception(): @pytest.mark.asyncio -async def test_closed_DBLClient_bot_id(): +async def test_DBLClient_bot_id(): + client = topgg.DBLClient(MOCK_TOKEN) + assert not client.is_closed assert client.bot_id == 1026525568344264724 + await client.close() + assert client.is_closed @pytest.mark.asyncio From 99d8ab7120ee499501fcc2a77f367fd358a19680 Mon Sep 17 00:00:00 2001 From: null8626 Date: Thu, 28 Mar 2024 12:55:48 +0700 Subject: [PATCH 20/33] refactor: collapse if-statement --- topgg/client.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/topgg/client.py b/topgg/client.py index eddd829..cab3463 100644 --- a/topgg/client.py +++ b/topgg/client.py @@ -71,9 +71,7 @@ def __init__( try: encoded_json = re.sub(r"[^a-zA-Z0-9\+\/]+", "", token.split(".")[1]) - missing_padding = len(encoded_json) % 4 - if missing_padding: - encoded_json += "=" * (4 - missing_padding) + encoded_json += "=" * (4 - (len(encoded_json) % 4)) self.bot_id = int(json.loads(base64.b64decode(encoded_json))["id"]) except: From b6be4c4da92e18b15590b3e996a5a94f5b9e1cc1 Mon Sep 17 00:00:00 2001 From: null8626 Date: Thu, 28 Mar 2024 12:56:58 +0700 Subject: [PATCH 21/33] doc: update readme --- README.rst | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/README.rst b/README.rst index 041b963..99fa29e 100644 --- a/README.rst +++ b/README.rst @@ -12,7 +12,7 @@ Top.gg Python Library :target: https://topggpy.readthedocs.io/en/latest/?badge=latest :alt: Documentation Status -A simple API wrapper for `Top.gg `_ written in Python, supporting discord.py. +A simple API wrapper for `Top.gg `_ written in Python. Installation ------------ @@ -31,7 +31,6 @@ Features * POST server count * GET bot info, server count, upvote info -* GET all bots * GET user info * GET widgets (large and small) including custom ones. See `docs.top.gg `_ for more info. * GET weekend status From d2ae045179eb1d7e13868829a7427a221cae456d Mon Sep 17 00:00:00 2001 From: null8626 Date: Thu, 28 Mar 2024 15:10:58 +0700 Subject: [PATCH 22/33] doc: documentation overhaul --- .gitignore | 11 +- docs/_static/css/custom.css | 7 -- docs/_static/favicon.ico | Bin 0 -> 15406 bytes docs/_static/img/favicon-16x16.png | Bin 1150 -> 0 bytes docs/_static/style.css | 48 ++++++++ docs/api.rst | 2 +- docs/api/autopost.rst | 6 +- docs/api/client.rst | 6 +- docs/api/data.rst | 6 +- docs/api/errors.rst | 9 +- docs/api/types.rst | 6 +- docs/api/webhook.rst | 6 +- docs/conf.py | 169 +++-------------------------- docs/index.rst | 56 ++++++++-- docs/repository.rst | 5 + docs/support.rst | 5 + docs/topgg.svg | 12 +- docs/whats_new.rst | 63 +++++------ requirements-docs.txt | 5 +- topgg/autopost.py | 14 +-- topgg/client.py | 22 ++-- topgg/data.py | 6 +- topgg/webhook.py | 8 +- 23 files changed, 203 insertions(+), 269 deletions(-) delete mode 100644 docs/_static/css/custom.css create mode 100644 docs/_static/favicon.ico delete mode 100644 docs/_static/img/favicon-16x16.png create mode 100644 docs/_static/style.css create mode 100644 docs/repository.rst create mode 100644 docs/support.rst diff --git a/.gitignore b/.gitignore index 41a6149..51f781c 100644 --- a/.gitignore +++ b/.gitignore @@ -1,10 +1,9 @@ topggpy.egg-info/ -topgg/__pycache__/ build/ dist/ -/docs/_build -/docs/_templates -.vscode -/.idea/ -__pycache__ +docs/_build +docs/_templates +.vscode/ +.idea/ +**/__pycache__/ .coverage diff --git a/docs/_static/css/custom.css b/docs/_static/css/custom.css deleted file mode 100644 index c7e6795..0000000 --- a/docs/_static/css/custom.css +++ /dev/null @@ -1,7 +0,0 @@ -header #logo-container img { - height: 100px; -} - -#search input[type="text"] { - font-size: 1em; -} \ No newline at end of file diff --git a/docs/_static/favicon.ico b/docs/_static/favicon.ico new file mode 100644 index 0000000000000000000000000000000000000000..ad108bb290ac60782ab9222a51717307e5772b1c GIT binary patch literal 15406 zcmeHOJ!lj`6rPwEK@{U37*RMyG=>P5+r0}SYQ#bWK@iW%LSk}%NkFg=Ec8UdBDDyD zDHSb(7FOB_Vre0YHn9j;NE8VP_!E=o_wH`uX7w(!JCj>ZX5lgW^WOWu`F3u0W_C}A zCebX~+62m&$h8QO5<QX^LX6^EJYKoJTZosNh3J3}LJ(0tkD_6=;p4O_+e4jf zg(PhWN}S^|4pGJCvkr~*9iX+Ud3^DuJjiiNE8e9E&G^Y`@fYIMcG00t(sC|!dFo&0 z@6u;^9oJUNT&UM=9M60G2a|Njd+gsw%xuP``>ZMUfBLU46)*j>E{$S;5jI~_j(2EU z=JJ-R@f-vGbAHRG=%D{xwc}!uR`^W_YVx0}b}a8hqn6yy#9+NJ?w{;t6=-w?`g>>_ z`li!f%cba8m}gNKEg09^pUHjnL9?*2yh}U7Z6Jezjr}%tN8!V_-ME7Lk@LR zvtlQ;2B`nIKM(g!#Wc10bq4M_{AM4aoGbly{*2G%IK5PIK2`i0dA zOx$}V?UxH*@=z(4qy8$m(r3Bwx&5mc`ma4`ucuUegZLDCsYS&~b4P(WrJ}#8pX>*1 zX%(;v)L#K!C&3b}I0v)qBoM)FRspMkRlq7>T!CDIPUIaL@f+qLBbbL>9Z1rd@>(V} z83#9zka^4bK#g*S=k%yc96l=zz#2=T$e-5;_}%0+uEixs!xN$LBzUTWFPCfX0aGBq0%po!;7v>K*zJcSw z+&OQZ`O9`OjeBxU)&cxKFqbeE5r5`7l}*rDZu{W>Eev;#iI6{`&A$MLcHE2ajmRII z`8&=uEKr~0t!tF}S9m-``PcWPUXHc)qvnscGf|7*QrDv9kKeA+JN|0*h@5}OepA-R z@(=gik^1SDR)2l{`S}&r3Jx9N@nEf0U$aj_q1{#@+U0w1X5M}?^Jd;##`@^B*%*yOY^0yDLB`lHflOp++i7Bq){+Bl zaGeIxJ%)e9^r(3}TDy1IO(D-uBE9N0<`Xx);z2R6k5cgP8;ZdL)RHlj!>15Ua;U_^ zuzFr20b~BmII78Lhu&%#%DwPwqJaF~I-1W}sA-AhHKY4vuucDEA#7DCMO|5fTQ{R5(yJ`Q4NN3L`QaNN&f9gqQbAjhu5w5@yr&)(^ z&2{A_$a`$oKi#gMpWL<&dH)9QlQieV%b6b!%P!LG#=Yyh1JbMmwYwP9ObW7hg2&US v9)*)#h~(SyuFyFs9PdCsZ+hKRi&Idd7szfcS;n^X-IeEGef#wv`0e}&td}C1 diff --git a/docs/_static/style.css b/docs/_static/style.css new file mode 100644 index 0000000..ee1e4dd --- /dev/null +++ b/docs/_static/style.css @@ -0,0 +1,48 @@ +body { + --color-link-underline: rgba(0, 0, 0, 0); + --color-link-underline--hover: var(--color-link); + --color-inline-code-background: rgba(0, 0, 0, 0); + --color-api-background-hover: var(--color-background-primary); + --color-highlight-on-target: var(--color-background-primary) !important; + + --font-stack: "Inter", sans-serif !important; + --font-stack--monospace: "Roboto Mono", monospace !important; +} + +aside.toc-drawer { + visibility: hidden; +} + +#furo-readthedocs-versions, .injected, .edit-this-page, .related-pages, .headerlink { + visibility: hidden; + user-select: none; +} + +dd dt { + color: var(--color-foreground-secondary); +} + +aside.toc-drawer .docutils:hover, .sidebar-brand-text:hover { + transition: 0.15s; + filter: opacity(75%); +} + +.highlight .c1, em { + font-style: normal !important; +} + +.highlight .nn { + text-decoration: none !important; +} + +h1 { + font-weight: 900; +} + +.sidebar-brand-text { + font-weight: bolder; +} + +.sidebar-scroll .reference.internal { + color: var(--color-brand-primary); +} \ No newline at end of file diff --git a/docs/api.rst b/docs/api.rst index 6969165..c1dc98f 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -1,7 +1,7 @@ .. currentmodule:: topgg ############# -API Reference +API reference ############# The following section outlines the API of topggpy. diff --git a/docs/api/autopost.rst b/docs/api/autopost.rst index 668af79..0151646 100644 --- a/docs/api/autopost.rst +++ b/docs/api/autopost.rst @@ -1,6 +1,6 @@ -####################### -Auto-post API Reference -####################### +################## +Autopost reference +################## .. automodule:: topgg.autopost :members: diff --git a/docs/api/client.rst b/docs/api/client.rst index 1bac197..1d8966e 100644 --- a/docs/api/client.rst +++ b/docs/api/client.rst @@ -1,6 +1,6 @@ -#################### -Client API Reference -#################### +################ +Client reference +################ .. automodule:: topgg.client :members: diff --git a/docs/api/data.rst b/docs/api/data.rst index 3f10ff2..090494e 100644 --- a/docs/api/data.rst +++ b/docs/api/data.rst @@ -1,6 +1,6 @@ -################## -Data API Reference -################## +############## +Data reference +############## .. automodule:: topgg.data :members: diff --git a/docs/api/errors.rst b/docs/api/errors.rst index 804fdfa..d54af67 100644 --- a/docs/api/errors.rst +++ b/docs/api/errors.rst @@ -1,7 +1,6 @@ -#################### -Errors API Reference -#################### +################ +Errors reference +################ .. automodule:: topgg.errors - :members: - :inherited-members: \ No newline at end of file + :members: \ No newline at end of file diff --git a/docs/api/types.rst b/docs/api/types.rst index a6a70f8..14b983d 100644 --- a/docs/api/types.rst +++ b/docs/api/types.rst @@ -1,6 +1,6 @@ -#################### -Models API Reference -#################### +################ +Models reference +################ .. automodule:: topgg.types :members: diff --git a/docs/api/webhook.rst b/docs/api/webhook.rst index 53a41c9..c1b067a 100644 --- a/docs/api/webhook.rst +++ b/docs/api/webhook.rst @@ -1,6 +1,6 @@ -##################### -Webhook API Reference -##################### +################# +Webhook reference +################# .. automodule:: topgg.webhook :members: diff --git a/docs/conf.py b/docs/conf.py index ca87da6..f4f8004 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -21,23 +21,10 @@ import os import sys -import alabaster - sys.path.insert(0, os.path.abspath("../")) -from topgg import __version__ as version - -# import re +from topgg import __version__ as version -# -- General configuration ------------------------------------------------ - -# If your documentation needs a minimal Sphinx version, state it here. -# -# needs_sphinx = '1.0' - -# Add any Sphinx extension module names here, as strings. They can be -# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom -# ones. extensions = [ "sphinx.ext.autodoc", "sphinx.ext.viewcode", @@ -45,12 +32,13 @@ "sphinx.ext.extlinks", "sphinx.ext.intersphinx", "sphinx.ext.napoleon", + "sphinx_reredirects", ] autodoc_member_order = "groupwise" extlinks = { - "issue": ("https://github.com/top-gg-community/python-sdk/issues/%s", "GH-"), + "issue": ("https://github.com/top-gg-community/python-sdk/issues/%s", "#%s"), } intersphinx_mapping = { @@ -59,167 +47,38 @@ "aiohttp": ("https://docs.aiohttp.org/en/stable/", None), } -releases_github_path = "top-gg/python-sdk" - -# Add any paths that contain templates here, relative to this directory. -templates_path = ["_templates"] +redirects = {"repository": "https://github.com/top-gg-community/python-sdk", "support": "https://discord.gg/dbl"} -# The suffix(es) of source filenames. -# You can specify multiple suffix as a list of string: -# -# source_suffix = ['.rst', '.md'] +releases_github_path = "top-gg-community/python-sdk" source_suffix = ".rst" - -# The master toctree document. master_doc = "index" -# General information about the project. project = "topggpy" -copyright = "2021, Assanali Mukhanov" -author = "Assanali Mukhanov" - -# The version info for the project you're documenting, acts as replacement for -# |version| and |release|, also used in various other places throughout the -# built documents. -# -# The short X.Y version. - -# with open('../dbl/__init__.py') as f: -# version = re.search(r'^__version__\s*=\s*[\'"]([^\'"]*)[\'"]', f.read(), re.MULTILINE).group(1) -# The full version, including alpha/beta/rc tags. +copyright = "2021 Assanali Mukhanov; 2024 null8626" +author = "null8626" release = version -# The language for content autogenerated by Sphinx. Refer to documentation -# for a list of supported languages. -# -# This is also used if you do content translation via gettext catalogs. -# Usually you set "language" from the command line for these cases. -language = None - -# List of patterns, relative to source directory, that match files and -# directories to ignore when looking for source files. -# This patterns also effect to html_static_path and html_extra_path +language = "en" exclude_patterns = ["_build"] -# -- Options for HTML output ---------------------------------------------- - -html_theme_options = {"navigation_depth": 2} -html_theme_path = [alabaster.get_path()] -# The theme to use for HTML and HTML Help pages. See the documentation for -# a list of builtin themes. -# -html_theme = "insegel" - -# The name of an image file (relative to this directory) to place at the top -# of the sidebar. +html_css_files = [ + "style.css", + "https://fonts.googleapis.com/css2?family=Inter:wght@100..900&family=Roboto+Mono&display=swap", +] +html_favicon = "_static/favicon.ico" +html_theme = "furo" html_logo = "topgg.svg" - -# Add any paths that contain custom static files (such as style sheets) here, -# relative to this directory. They are copied after the builtin static files, -# so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ["_static"] - -# Add any extra paths that contain custom files (such as robots.txt or -# .htaccess) here, relative to this directory. These files are copied -# directly to the root of the documentation. -# html_extra_path = [] - -# If not '', a 'Last updated on:' timestamp is inserted at every page bottom, -# using the given strftime format. -# html_last_updated_fmt = '%b %d, %Y' - -# If true, SmartyPants will be used to convert quotes and dashes to -# typographically correct entities. -# html_use_smartypants = True - -# Custom sidebar templates, maps document names to template names. -# html_sidebars = {} - -# Additional templates that should be rendered to pages, maps page names to -# template names. -# html_additional_pages = {} - -# If false, no module index is generated. -# html_domain_indices = True - -# If false, no index is generated. -# html_use_index = True - -# If true, the index is split into individual pages for each letter. -# html_split_index = False - -# If true, links to the reST sources are added to the pages. -# html_show_sourcelink = True - -# If true, "Created using Sphinx" is shown in the HTML footer. Default is True. html_show_sphinx = False - -# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. html_show_copyright = True - -# If true, an OpenSearch description file will be output, and all pages will -# contain a tag referring to it. The value of this option must be the -# base URL from which the finished HTML is served. -# html_use_opensearch = '' - -# This is the file name suffix for HTML files (e.g. ".xhtml"). -# html_file_suffix = None - -# Language to be used for generating the HTML full-text search index. -# Sphinx supports the following languages: -# 'da', 'de', 'en', 'es', 'fi', 'fr', 'hu', 'it', 'ja' -# 'nl', 'no', 'pt', 'ro', 'ru', 'sv', 'tr' -# html_search_language = 'en' - -# A dictionary with options for the search language support, empty by default. -# Now only 'ja' uses this config value -# html_search_options = {'type': 'default'} - -# The name of a javascript file (relative to the configuration directory) that -# implements a search results scorer. If empty, the default will be used. -# html_search_scorer = 'scorer.js' - - -# -- Options for HTMLHelp output ------------------------------------------ - -# Output file base name for HTML help builder. htmlhelp_basename = "topggpydoc" -# -- Options for LaTeX output --------------------------------------------- - -latex_elements = { - # The paper size ('letterpaper' or 'a4paper'). - # - # 'papersize': 'letterpaper', - # The font size ('10pt', '11pt' or '12pt'). - # - # 'pointsize': '10pt', - # Additional stuff for the LaTeX preamble. - # - # 'preamble': '', - # Latex figure (float) alignment - # - # 'figure_align': 'htbp', -} - -# Grouping the document tree into LaTeX files. List of tuples -# (source start file, target name, title, -# author, documentclass [howto, manual, or own class]). latex_documents = [ (master_doc, "topggpy.tex", "topggpy Documentation", "Assanali Mukhanov", "manual"), ] -# -- Options for manual page output --------------------------------------- - -# One entry per manual page. List of tuples -# (source start file, name, description, authors, manual section). man_pages = [(master_doc, "topggpy", "topggpy Documentation", [author], 1)] -# -- Options for Texinfo output ------------------------------------------- - -# Grouping the document tree into Texinfo files. List of tuples -# (source start file, target name, title, author, -# dir menu entry, description, category) texinfo_documents = [ ( master_doc, diff --git a/docs/index.rst b/docs/index.rst index a634d38..d8b9e3d 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -3,19 +3,53 @@ You can adapt this file completely to your liking, but it should at least contain the root `toctree` directive. -################################### -Welcome to topggpy's documentation! -################################### +##################### +Top.gg Python Library +##################### + +.. image:: https://img.shields.io/pypi/v/topggpy.svg + :target: https://pypi.python.org/pypi/topggpy + :alt: View on PyPI +.. image:: https://img.shields.io/pypi/pyversions/topggpy.svg + :target: https://pypi.python.org/pypi/topggpy + :alt: v1.0.0 +.. image:: https://readthedocs.org/projects/topggpy/badge/?version=latest + :target: https://topggpy.readthedocs.io/en/latest/?badge=latest + :alt: Documentation Status + +A simple API wrapper for `Top.gg `_ written in Python. + +Installation +------------ + +.. code:: bash + + pip3 install topggpy + +Features +-------- + +* POST server count +* GET bot info, server count, upvote info +* GET user info +* GET widgets (large and small) including custom ones. See `docs.top.gg `_ for more info. +* GET weekend status +* Built-in webhook to handle Top.gg votes +* Automated server count posting +* Searching for bots via the API + +Additional information +---------------------- + +* Before using the webhook provided by this library, make sure that you have specified port open. +* Optimal values for port are between 1024 and 49151. +* If you happen to need help implementing topggpy in your bot, feel free to ask in the ``#development`` or ``#api`` channels in our `Discord server `_. .. toctree:: - :maxdepth: 1 + :maxdepth: 2 + :hidden: api whats_new - -Indices and tables -================== - -* :ref:`genindex` -* :ref:`modindex` -* :ref:`search` + repository + support \ No newline at end of file diff --git a/docs/repository.rst b/docs/repository.rst new file mode 100644 index 0000000..a542ad6 --- /dev/null +++ b/docs/repository.rst @@ -0,0 +1,5 @@ +================= +GitHub repository +================= + +You should be redirected in a few moments. Otherwise, click here: https://github.com/top-gg-community/python-sdk \ No newline at end of file diff --git a/docs/support.rst b/docs/support.rst new file mode 100644 index 0000000..531270f --- /dev/null +++ b/docs/support.rst @@ -0,0 +1,5 @@ +============== +Support server +============== + +You should be redirected in a few moments. Otherwise, click here: https://discord.gg/dbl \ No newline at end of file diff --git a/docs/topgg.svg b/docs/topgg.svg index 9afe235..63f5812 100644 --- a/docs/topgg.svg +++ b/docs/topgg.svg @@ -1,10 +1,4 @@ - - - - - + + + diff --git a/docs/whats_new.rst b/docs/whats_new.rst index 23e3ffe..eaf52c6 100644 --- a/docs/whats_new.rst +++ b/docs/whats_new.rst @@ -3,22 +3,23 @@ .. _whats_new: ########## -What's New +What's new ########## This page keeps a detailed human friendly rendering of what's new and changed in specific versions. v2.0.1 ====== -* Added Python 3.12 support -* Dropped Python 3.6 and 3.7 support +* Added Python 3.12 support (:issue:`78`) +* Dropped Python 3.6 and 3.7 support (:issue:`75`) * Removed the need to manually set a ``default_bot_id`` property -* :attr:`BotData.def_avatar` is now an optional string -* :meth:`DBLClient.get_bots` is now deprecated -* :meth:`DBLClient.get_guild_count` no longer accepts a ``bot_id`` argument -* :meth:`DBLClient.get_bot_votes` no longer raises a ``ClientException`` without a ``default_bot_id`` property -* :meth:`DBLClient.get_bot_info` no longer raises a ``ClientException`` without a ``default_bot_id`` property -* :meth:`DBLClient.generate_widget`` no longer raises a ``ClientException`` without a ``default_bot_id`` property +* :attr:`~.BotData.def_avatar` is now an optional string +* :meth:`~.DBLClient.get_bots` is now deprecated +* :meth:`~.DBLClient.get_guild_count` no longer accepts a ``bot_id`` argument +* :meth:`~.DBLClient.get_bot_votes` no longer raises a :class:`~.ClientException` without a ``default_bot_id`` property +* :meth:`~.DBLClient.get_bot_info` no longer raises a :class:`~.ClientException` without a ``default_bot_id`` property +* :meth:`~.DBLClient.generate_widget` no longer raises a :class:`~.ClientException` without a ``default_bot_id`` property +* Documentation overhaul v2.0.0a ======= @@ -31,15 +32,15 @@ v2.0.0a v1.4.0 ====== -* The type of data passed to ``on_dbl_vote`` has been changed from :class:`dict` to :obj:`BotVoteData` -* The type of data passed to ``on_dsl_vote`` has been changed from :class:`dict` to :obj:`ServerVoteData` +* The type of data passed to ``on_dbl_vote`` has been changed from :class:`~.dict` to :obj:`BotVoteData` +* The type of data passed to ``on_dsl_vote`` has been changed from :class:`~.dict` to :obj:`ServerVoteData` v1.3.0 ====== * Introduced `global ratelimiter `__ to follow Top.gg global ratelimits - * Fixed an :exc:`AttributeError` raised by :meth:`HTTPClient.request` + * Fixed an :exc:`AttributeError` raised by :meth:`~.HTTPClient.request` * `Resource-specific ratelimit `__ is now actually resource-specific @@ -47,41 +48,41 @@ v1.2.0 ====== * Introduced global ratelimiter along with bot endpoints ratelimiter -* Follow consistency with typing in :class:`HTTPClient` and :class:`DBLClient` along with updated docstrings (:issue:`55`) +* Follow consistency with typing in :class:`~.HTTPClient` and :class:`~.DBLClient` along with updated docstrings (:issue:`55`) v1.1.0 ====== * Introduced `data models `__ - * :meth:`DBLClient.get_bot_votes` now returns a list of :class:`BriefUserData` objects + * :meth:`~.DBLClient.get_bot_votes` now returns a list of :class:`~.BriefUserData` objects - * :meth:`DBLClient.get_bot_info` now returns a :class:`BotData` object + * :meth:`~.DBLClient.get_bot_info` now returns a :class:`~.BotData` object - * :meth:`DBLClient.get_guild_count` now returns a :class:`BotStatsData` object + * :meth:`~.DBLClient.get_guild_count` now returns a :class:`~.BotStatsData` object - * :meth:`DBLClient.get_user_info` now returns a :class:`UserData` object + * :meth:`~.DBLClient.get_user_info` now returns a :class:`~.UserData` object -* :meth:`WebhookManager.run` now returns an :class:`asyncio.Task`, meaning it can now be optionally awaited +* :meth:`~.WebhookManager.run` now returns an :class:`~.asyncio.Task`, meaning it can now be optionally awaited v1.0.1 ====== -* :attr:`WebhookManager.webserver` now instead returns :class:`aiohttp.web.Application` for ease of use +* :attr:`~.WebhookManager.webserver` now instead returns :class:`~.aiohttp.web.Application` for ease of use v1.0.0 ====== * Renamed the module folder from ``dbl`` to ``topgg`` -* Added ``post_shard_count`` argument to :meth:`DBLClient.post_guild_count` +* Added ``post_shard_count`` argument to :meth:`~.DBLClient.post_guild_count` * Autopost now supports automatic shard posting (:issue:`42`) * Large webhook system rework, read the :obj:`api/webhook` section for more * Added support for server webhooks -* Renamed ``DBLException`` to :class:`TopGGException` -* Renamed ``DBLClient.get_bot_upvotes()`` to :meth:`DBLClient.get_bot_votes` -* Added :meth:`DBLClient.generate_widget` along with the ``widgets`` section in the documentation +* Renamed ``DBLException`` to :class:`~.TopGGException` +* Renamed ``DBLClient.get_bot_upvotes()`` to :meth:`~.DBLClient.get_bot_votes` +* Added :meth:`~.DBLClient.generate_widget` along with the ``widgets`` section in the documentation * Implemented a properly working ratelimiter * Added :func:`on_autopost_error` * All autopost events now follow ``on_autopost_x`` naming format, e.g. :func:`on_autopost_error`, :func:`on_autopost_success` @@ -90,7 +91,7 @@ v1.0.0 v0.4.0 ====== -* :meth:`DBLClient.post_guild_count` now supports a custom ``guild_count`` argument, which accepts either an integer or list of integers +* :meth:`~.DBLClient.post_guild_count` now supports a custom ``guild_count`` argument, which accepts either an integer or list of integers * Reworked how shard info is posted * Removed ``InvalidArgument`` and ``ConnectionClosed`` exceptions * Added ``ServerError`` exception @@ -99,12 +100,12 @@ v0.3.3 ====== * Internal changes regarding support of Top.gg migration -* Fixed errors raised when using :meth:`DBLClient.close` without built-in webhook +* Fixed errors raised when using :meth:`~.DBLClient.close` without built-in webhook v0.3.2 ====== -* ``Client`` class has been renamed to ``DBLClient`` +* ``Client`` class has been renamed to :class:`~.DBLClient` v0.3.1 ====== @@ -116,7 +117,7 @@ v0.3.1 v0.3.0 ====== -* :class:`DBLClient` now has ``autopost`` kwarg that will post server count automatically every 30 minutes +* :class:`~.DBLClient` now has ``autopost`` kwarg that will post server count automatically every 30 minutes * Fixed code 403 errors * Added ``on_dbl_vote``, an event that is called when you test your webhook * Added ``on_dbl_test``, an event that is called when someone tests your webhook @@ -126,7 +127,7 @@ v0.2.1 * Added webhook * Removed support for discord.py versions lower than 1.0.0 -* Made :meth:`DBLClient.get_weekend_status` return a boolean value +* Made :meth:`~.DBLClient.get_weekend_status` return a boolean value * Added webhook example in README * Removed ``post_server_count`` and ``get_server_count`` @@ -141,9 +142,9 @@ v0.2.0 * Made ``get_server_count`` an alias for ``get_guild_count`` -* Added :meth:`DBLClient.get_weekend_status` -* Removed all parameters from :meth:`DBLClient.get_upvote_info` -* Added limit to :meth:`DBLClient.get_bots` +* Added :meth:`~.DBLClient.get_weekend_status` +* Removed all parameters from :meth:`~.DBLClient.get_upvote_info` +* Added limit to :meth:`~.DBLClient.get_bots` * Fixed example in README v0.1.6 diff --git a/requirements-docs.txt b/requirements-docs.txt index b90afd8..8315d05 100644 --- a/requirements-docs.txt +++ b/requirements-docs.txt @@ -1,4 +1,3 @@ +furo sphinx -insegel -sphinxcontrib-napoleon -sphinx-rtd-dark-mode \ No newline at end of file +sphinx-reredirects \ No newline at end of file diff --git a/topgg/autopost.py b/topgg/autopost.py index 96aa988..7025b9b 100644 --- a/topgg/autopost.py +++ b/topgg/autopost.py @@ -184,16 +184,14 @@ def stats(self, callback: t.Any = None) -> t.Any: # In this example, we fetch the stats from a Discord client instance. client = Client(...) dblclient = topgg.DBLClient(TOKEN).set_data(client) - autopost = ( - dblclient - .autopost() - .on_success(lambda: print("Successfully posted the stats!") - ) + autopost = dblclient.autopost().on_success(lambda: print("Successfully posted the stats!")) + @autopost.stats() def get_stats(client: Client = topgg.data(Client)): return topgg.StatsWrapper(guild_count=len(client.guilds), shard_count=len(client.shards)) + # somewhere after the event loop has started autopost.start() """ @@ -222,11 +220,11 @@ def set_interval(self, seconds: t.Union[float, datetime.timedelta]) -> "AutoPost Sets the interval between posting stats. Args: - seconds (:obj:`typing.Union` [ :obj:`float`, :obj:`datetime.timedelta` ]) + seconds (Union[:obj:`float`, :obj:`datetime.timedelta`]) The interval. Raises: - :obj:`ValueError` + ValueError If the provided interval is less than 900 seconds. """ if isinstance(seconds, datetime.timedelta): @@ -283,7 +281,7 @@ def start(self) -> "asyncio.Task[None]": This method must be called when the event loop has already running! Raises: - :obj:`~.errors.TopGGException` + :exc:`~.errors.TopGGException` If there's no callback provided or the autopost is already running. """ if not hasattr(self, "_stats"): diff --git a/topgg/client.py b/topgg/client.py index cab3463..1be4edd 100644 --- a/topgg/client.py +++ b/topgg/client.py @@ -100,7 +100,7 @@ async def get_weekend_status(self) -> bool: :obj:`bool`: The boolean value of weekend status. Raises: - :obj:`~.errors.ClientStateException` + :exc:`~.errors.ClientStateException` If the client has been closed. """ await self._ensure_session() @@ -139,18 +139,18 @@ async def post_guild_count( An instance of StatsWrapper containing guild_count, shard_count, and shard_id. Keyword Arguments: - guild_count (:obj:`typing.Optional` [:obj:`typing.Union` [ :obj:`int`, :obj:`list` [ :obj:`int` ]]]) + guild_count (Optional[Union[:obj:`int`, List[:obj:`int`]]]) Number of guilds the bot is in. Applies the number to a shard instead if shards are specified. If not specified, length of provided client's property `.guilds` will be posted. - shard_count (:obj:`.typing.Optional` [ :obj:`int` ]) + shard_count (Optional[:obj:`int`]) The total number of shards. - shard_id (:obj:`.typing.Optional` [ :obj:`int` ]) + shard_id (Optional[:obj:`int`]) The index of the current shard. Top.gg uses `0 based indexing`_ for shards. Raises: TypeError If no argument is provided. - :obj:`~.errors.ClientStateException` + :exc:`~.errors.ClientStateException` If the client has been closed. """ if stats: @@ -170,7 +170,7 @@ async def get_guild_count(self) -> types.BotStatsData: The guild count and shards of a bot on Top.gg. Raises: - :obj:`~.errors.ClientStateException` + :exc:`~.errors.ClientStateException` If the client has been closed. """ await self._ensure_session() @@ -184,11 +184,11 @@ async def get_bot_votes(self) -> t.List[types.BriefUserData]: This API endpoint is only available to the bot's owner. Returns: - :obj:`list` [ :obj:`~.types.BriefUserData` ]: + List[:obj:`~.types.BriefUserData` ]: Users who voted for your bot. Raises: - :obj:`~.errors.ClientStateException` + :exc:`~.errors.ClientStateException` If the client has been closed. """ await self._ensure_session() @@ -210,7 +210,7 @@ async def get_bot_info(self, bot_id: t.Optional[int] = None) -> types.BotData: `here `_. Raises: - :obj:`~.errors.ClientStateException` + :exc:`~.errors.ClientStateException` If the client has been closed. """ await self._ensure_session() @@ -251,7 +251,7 @@ async def get_user_info(self, user_id: int) -> types.UserData: Information about a Top.gg user. Raises: - :obj:`~.errors.ClientStateException` + :exc:`~.errors.ClientStateException` If the client has been closed. """ await self._ensure_session() @@ -269,7 +269,7 @@ async def get_user_vote(self, user_id: int) -> bool: :obj:`bool`: Info about the user's vote. Raises: - :obj:`~.errors.ClientStateException` + :exc:`~.errors.ClientStateException` If the client has been closed. """ await self._ensure_session() diff --git a/topgg/data.py b/topgg/data.py index baafde6..7d5f422 100644 --- a/topgg/data.py +++ b/topgg/data.py @@ -36,7 +36,7 @@ def data(type_: t.Type[T]) -> T: Represents the injected data. This should be set as the parameter's default value. Args: - `type_` (:obj:`type` [ :obj:`T` ]) + `type_` (:obj:`type` [ :obj:`T`]) The type of the injected data. Returns: @@ -85,13 +85,13 @@ def set_data(self: DataContainerT, data_: t.Any, *, override: bool = False) -> D Sets data to be available in your functions. Args: - `data_` (:obj:`typing.Any`) + `data_` (Any) The data to be injected. override (:obj:`bool`) Whether or not to override another instance that already exists. Raises: - :obj:`~.errors.TopGGException` + :exc:`~.errors.TopGGException` If override is False and another instance of the same type exists. """ type_ = type(data_) diff --git a/topgg/webhook.py b/topgg/webhook.py index 28b92e9..01b30f7 100644 --- a/topgg/webhook.py +++ b/topgg/webhook.py @@ -83,16 +83,16 @@ def endpoint(self, endpoint_: t.Optional["WebhookEndpoint"] = None) -> t.Any: """Helper method that returns a WebhookEndpoint object. Args: - `endpoint_` (:obj:`typing.Optional` [ :obj:`WebhookEndpoint` ]) + `endpoint_` (Optional[:obj:`WebhookEndpoint`]) The endpoint to add. Returns: - :obj:`typing.Union` [ :obj:`WebhookManager`, :obj:`BoundWebhookEndpoint` ]: + Union[:obj:`WebhookManager`, :obj:`BoundWebhookEndpoint` ]: An instance of :obj:`WebhookManager` if endpoint was provided, otherwise :obj:`BoundWebhookEndpoint`. Raises: - :obj:`~.errors.TopGGException` + :exc:`~.errors.TopGGException` If the endpoint is lacking attributes. """ if endpoint_: @@ -328,7 +328,7 @@ def endpoint(route: str, type: WebhookType, auth: str = "") -> t.Callable[[t.Cal The auth for the endpoint. Returns: - :obj:`typing.Callable` [[ :obj:`typing.Callable` [..., :obj:`typing.Any` ]], :obj:`WebhookEndpoint` ]: + Callable[[Callable[..., Any]], :obj:`WebhookEndpoint`]: The actual decorator. :Example: From 99b9bc305a42848223971a2f941bdf1c85e64175 Mon Sep 17 00:00:00 2001 From: null8626 Date: Thu, 28 Mar 2024 17:13:18 +0700 Subject: [PATCH 23/33] doc: add examples --- docs/_static/script.js | 22 ++++++++++++++++++++++ docs/api.rst | 6 ------ docs/conf.py | 8 +++++++- docs/examples.rst | 9 +++++++++ docs/examples/discord_py.rst | 5 +++++ docs/examples/hikari.rst | 5 +++++ docs/index.rst | 1 + docs/whats_new.rst | 4 ---- examples/discordpy_example/__main__.py | 1 + examples/hikari_example/__main__.py | 1 + 10 files changed, 51 insertions(+), 11 deletions(-) create mode 100644 docs/_static/script.js create mode 100644 docs/examples.rst create mode 100644 docs/examples/discord_py.rst create mode 100644 docs/examples/hikari.rst diff --git a/docs/_static/script.js b/docs/_static/script.js new file mode 100644 index 0000000..0d7b93f --- /dev/null +++ b/docs/_static/script.js @@ -0,0 +1,22 @@ +document.addEventListener('load', () => { + try { + document.querySelector('.edit-this-page').remove() + + // remove these useless crap that appears on official readthedocs builds + document.querySelector('#furo-readthedocs-versions').remove() + document.querySelector('.injected').remove() + } catch { + // we're building this locally, forget it + } +}) + +const findChildrenWithName = (elem, name) => [...elem.children].find(child => child.nodeName === name) + +for (const label of document.querySelectorAll('.sidebar-container label')) { + const link = findChildrenWithName(label.parentElement, 'A') + + link.addEventListener('click', event => { + event.preventDefault() + label.click() + }) +} \ No newline at end of file diff --git a/docs/api.rst b/docs/api.rst index c1dc98f..28409ff 100644 --- a/docs/api.rst +++ b/docs/api.rst @@ -1,13 +1,7 @@ -.. currentmodule:: topgg - ############# API reference ############# -The following section outlines the API of topggpy. - -Index: - .. toctree:: :maxdepth: 2 diff --git a/docs/conf.py b/docs/conf.py index f4f8004..492b540 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -47,7 +47,12 @@ "aiohttp": ("https://docs.aiohttp.org/en/stable/", None), } -redirects = {"repository": "https://github.com/top-gg-community/python-sdk", "support": "https://discord.gg/dbl"} +redirects = { + "repository": "https://github.com/top-gg-community/python-sdk", + "support": "https://discord.gg/dbl", + "examples/discord_py": "https://github.com/Top-gg-Community/python-sdk/tree/master/examples/discordpy_example", + "examples/hikari": "https://github.com/Top-gg-Community/python-sdk/tree/master/examples/hikari_example", +} releases_github_path = "top-gg-community/python-sdk" source_suffix = ".rst" @@ -61,6 +66,7 @@ language = "en" exclude_patterns = ["_build"] +html_js_files = ["script.js"] html_css_files = [ "style.css", "https://fonts.googleapis.com/css2?family=Inter:wght@100..900&family=Roboto+Mono&display=swap", diff --git a/docs/examples.rst b/docs/examples.rst new file mode 100644 index 0000000..c18a83f --- /dev/null +++ b/docs/examples.rst @@ -0,0 +1,9 @@ +######## +Examples +######## + + .. toctree:: + :maxdepth: 2 + + examples/discord_py + examples/hikari \ No newline at end of file diff --git a/docs/examples/discord_py.rst b/docs/examples/discord_py.rst new file mode 100644 index 0000000..e77808d --- /dev/null +++ b/docs/examples/discord_py.rst @@ -0,0 +1,5 @@ +================== +Discord.py example +================== + +You should be redirected in a few moments. Otherwise, click here: https://github.com/Top-gg-Community/python-sdk/tree/master/examples/discordpy_example \ No newline at end of file diff --git a/docs/examples/hikari.rst b/docs/examples/hikari.rst new file mode 100644 index 0000000..89fc651 --- /dev/null +++ b/docs/examples/hikari.rst @@ -0,0 +1,5 @@ +============== +Hikari example +============== + +You should be redirected in a few moments. Otherwise, click here: https://github.com/Top-gg-Community/python-sdk/tree/master/examples/hikari_example \ No newline at end of file diff --git a/docs/index.rst b/docs/index.rst index d8b9e3d..f616a71 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -50,6 +50,7 @@ Additional information :hidden: api + examples whats_new repository support \ No newline at end of file diff --git a/docs/whats_new.rst b/docs/whats_new.rst index eaf52c6..d506500 100644 --- a/docs/whats_new.rst +++ b/docs/whats_new.rst @@ -1,7 +1,3 @@ -.. currentmodule:: topgg - -.. _whats_new: - ########## What's new ########## diff --git a/examples/discordpy_example/__main__.py b/examples/discordpy_example/__main__.py index 67e5823..7077170 100644 --- a/examples/discordpy_example/__main__.py +++ b/examples/discordpy_example/__main__.py @@ -1,6 +1,7 @@ # The MIT License (MIT) # Copyright (c) 2021 Norizon +# Copyright (c) 2024 null8626 # Permission is hereby granted, free of charge, to any person obtaining a # copy of this software and associated documentation files (the "Software"), diff --git a/examples/hikari_example/__main__.py b/examples/hikari_example/__main__.py index 857329c..82da8ba 100644 --- a/examples/hikari_example/__main__.py +++ b/examples/hikari_example/__main__.py @@ -1,6 +1,7 @@ # The MIT License (MIT) # Copyright (c) 2021 Norizon +# Copyright (c) 2024 null8626 # Permission is hereby granted, free of charge, to any person obtaining a # copy of this software and associated documentation files (the "Software"), From 8ea8f7adfe8a01e474e455918d9fca140274b077 Mon Sep 17 00:00:00 2001 From: null8626 Date: Thu, 28 Mar 2024 17:26:38 +0700 Subject: [PATCH 24/33] doc: more redirects and refactor js script --- docs/_static/script.js | 4 +--- docs/api.rst | 13 ------------- docs/api/index.rst | 13 +++++++++++++ docs/conf.py | 2 ++ docs/{examples.rst => examples/index.rst} | 4 ++-- docs/index.rst | 7 ++----- 6 files changed, 20 insertions(+), 23 deletions(-) delete mode 100644 docs/api.rst create mode 100644 docs/api/index.rst rename docs/{examples.rst => examples/index.rst} (58%) diff --git a/docs/_static/script.js b/docs/_static/script.js index 0d7b93f..c8cfd5b 100644 --- a/docs/_static/script.js +++ b/docs/_static/script.js @@ -10,10 +10,8 @@ document.addEventListener('load', () => { } }) -const findChildrenWithName = (elem, name) => [...elem.children].find(child => child.nodeName === name) - for (const label of document.querySelectorAll('.sidebar-container label')) { - const link = findChildrenWithName(label.parentElement, 'A') + const link = [...label.parentElement.children].find(child => child.nodeName === 'A') link.addEventListener('click', event => { event.preventDefault() diff --git a/docs/api.rst b/docs/api.rst deleted file mode 100644 index 28409ff..0000000 --- a/docs/api.rst +++ /dev/null @@ -1,13 +0,0 @@ -############# -API reference -############# - - .. toctree:: - :maxdepth: 2 - - api/autopost - api/client - api/data - api/errors - api/types - api/webhook \ No newline at end of file diff --git a/docs/api/index.rst b/docs/api/index.rst new file mode 100644 index 0000000..4c4e05b --- /dev/null +++ b/docs/api/index.rst @@ -0,0 +1,13 @@ +############# +API reference +############# + + .. toctree:: + :maxdepth: 2 + + autopost + client + data + errors + types + webhook \ No newline at end of file diff --git a/docs/conf.py b/docs/conf.py index 492b540..50eaf9d 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -50,6 +50,8 @@ redirects = { "repository": "https://github.com/top-gg-community/python-sdk", "support": "https://discord.gg/dbl", + "api/index": "autopost.html", + "examples/index": "discord_py.html", "examples/discord_py": "https://github.com/Top-gg-Community/python-sdk/tree/master/examples/discordpy_example", "examples/hikari": "https://github.com/Top-gg-Community/python-sdk/tree/master/examples/hikari_example", } diff --git a/docs/examples.rst b/docs/examples/index.rst similarity index 58% rename from docs/examples.rst rename to docs/examples/index.rst index c18a83f..2057326 100644 --- a/docs/examples.rst +++ b/docs/examples/index.rst @@ -5,5 +5,5 @@ Examples .. toctree:: :maxdepth: 2 - examples/discord_py - examples/hikari \ No newline at end of file + discord_py + hikari \ No newline at end of file diff --git a/docs/index.rst b/docs/index.rst index f616a71..2f9bd0e 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -10,9 +10,6 @@ Top.gg Python Library .. image:: https://img.shields.io/pypi/v/topggpy.svg :target: https://pypi.python.org/pypi/topggpy :alt: View on PyPI -.. image:: https://img.shields.io/pypi/pyversions/topggpy.svg - :target: https://pypi.python.org/pypi/topggpy - :alt: v1.0.0 .. image:: https://readthedocs.org/projects/topggpy/badge/?version=latest :target: https://topggpy.readthedocs.io/en/latest/?badge=latest :alt: Documentation Status @@ -49,8 +46,8 @@ Additional information :maxdepth: 2 :hidden: - api - examples + api/index.rst + examples/index.rst whats_new repository support \ No newline at end of file From 1ed5505e7f6bde3fe6c626466a961c41e67dba81 Mon Sep 17 00:00:00 2001 From: null8626 Date: Thu, 28 Mar 2024 17:48:20 +0700 Subject: [PATCH 25/33] doc: tweaks --- topgg/client.py | 7 +++++-- topgg/http.py | 5 ++++- topgg/webhook.py | 2 +- 3 files changed, 10 insertions(+), 4 deletions(-) diff --git a/topgg/client.py b/topgg/client.py index 1be4edd..b895806 100644 --- a/topgg/client.py +++ b/topgg/client.py @@ -184,7 +184,7 @@ async def get_bot_votes(self) -> t.List[types.BriefUserData]: This API endpoint is only available to the bot's owner. Returns: - List[:obj:`~.types.BriefUserData` ]: + List[:obj:`~.types.BriefUserData`]: Users who voted for your bot. Raises: @@ -225,7 +225,10 @@ async def get_bots( search: t.Optional[t.Dict[str, t.Any]] = None, fields: t.Optional[t.List[str]] = None, ) -> types.DataDict[str, t.Any]: - """This function is deprecated.""" + """ + Warning: + This function is deprecated. + """ warnings.warn("get_bots is now deprecated.", DeprecationWarning) diff --git a/topgg/http.py b/topgg/http.py index 0071766..4333c4a 100644 --- a/topgg/http.py +++ b/topgg/http.py @@ -202,7 +202,10 @@ def get_bots( search: Dict[str, str], fields: Sequence[str], ) -> Coroutine[Any, Any, dict]: - """This function is now deprecated.""" + """ + Warning: + This function is deprecated. + """ warnings.warn("get_bots is now deprecated.", DeprecationWarning) diff --git a/topgg/webhook.py b/topgg/webhook.py index 01b30f7..9a3fd7f 100644 --- a/topgg/webhook.py +++ b/topgg/webhook.py @@ -87,7 +87,7 @@ def endpoint(self, endpoint_: t.Optional["WebhookEndpoint"] = None) -> t.Any: The endpoint to add. Returns: - Union[:obj:`WebhookManager`, :obj:`BoundWebhookEndpoint` ]: + Union[:obj:`WebhookManager`, :obj:`BoundWebhookEndpoint`]: An instance of :obj:`WebhookManager` if endpoint was provided, otherwise :obj:`BoundWebhookEndpoint`. From ad4386b3affffa3b6ed06af32d2b429435106d0a Mon Sep 17 00:00:00 2001 From: null8626 Date: Thu, 28 Mar 2024 17:52:03 +0700 Subject: [PATCH 26/33] doc: show monthly downloads --- docs/index.rst | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/index.rst b/docs/index.rst index 2f9bd0e..82acd4a 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -10,9 +10,9 @@ Top.gg Python Library .. image:: https://img.shields.io/pypi/v/topggpy.svg :target: https://pypi.python.org/pypi/topggpy :alt: View on PyPI -.. image:: https://readthedocs.org/projects/topggpy/badge/?version=latest +.. image:: https://img.shields.io/pypi/dm/topggpy?style=flat-square :target: https://topggpy.readthedocs.io/en/latest/?badge=latest - :alt: Documentation Status + :alt: Monthly PyPI downloads A simple API wrapper for `Top.gg `_ written in Python. From dd1a277a9399e236db15a0d6b5d11a9a1e19d1cb Mon Sep 17 00:00:00 2001 From: null8626 Date: Thu, 28 Mar 2024 17:54:23 +0700 Subject: [PATCH 27/33] doc: show up monthly pypi downloads --- README.rst | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/README.rst b/README.rst index 99fa29e..b29327f 100644 --- a/README.rst +++ b/README.rst @@ -5,12 +5,9 @@ Top.gg Python Library .. image:: https://img.shields.io/pypi/v/topggpy.svg :target: https://pypi.python.org/pypi/topggpy :alt: View on PyPI -.. image:: https://img.shields.io/pypi/pyversions/topggpy.svg - :target: https://pypi.python.org/pypi/topggpy - :alt: v1.0.0 -.. image:: https://readthedocs.org/projects/topggpy/badge/?version=latest +.. image:: https://img.shields.io/pypi/dm/topggpy?style=flat-square :target: https://topggpy.readthedocs.io/en/latest/?badge=latest - :alt: Documentation Status + :alt: Monthly PyPI downloads A simple API wrapper for `Top.gg `_ written in Python. From cd6380f72ccdd3d59657faed79c320c69b85ed72 Mon Sep 17 00:00:00 2001 From: null8626 Date: Thu, 28 Mar 2024 17:59:02 +0700 Subject: [PATCH 28/33] doc: use pip, not pip3 --- README.rst | 2 +- docs/index.rst | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/README.rst b/README.rst index b29327f..423eae6 100644 --- a/README.rst +++ b/README.rst @@ -16,7 +16,7 @@ Installation .. code:: bash - pip3 install topggpy + pip install topggpy Documentation ------------- diff --git a/docs/index.rst b/docs/index.rst index 82acd4a..f2aebe2 100644 --- a/docs/index.rst +++ b/docs/index.rst @@ -21,7 +21,7 @@ Installation .. code:: bash - pip3 install topggpy + pip install topggpy Features -------- From 023d63e0af639078aa2f2640586f4a7c68714319 Mon Sep 17 00:00:00 2001 From: null8626 Date: Thu, 28 Mar 2024 21:16:10 +0700 Subject: [PATCH 29/33] meta: update project URLs --- README.rst | 2 +- pyproject.toml | 7 ++++--- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/README.rst b/README.rst index 423eae6..928930b 100644 --- a/README.rst +++ b/README.rst @@ -21,7 +21,7 @@ Installation Documentation ------------- -Documentation can be found `here `_ +Documentation can be found `here `_ Features -------- diff --git a/pyproject.toml b/pyproject.toml index fc1cdce..5c2dc6f 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -30,6 +30,7 @@ classifiers = [ requires-python = ">=3.8" [project.urls] -Homepage = "https://topggpy.readthedocs.io/en/stable/" -Documentation = "https://topggpy.readthedocs.io/en/stable/" -Repository = "https://github.com/top-gg-community/python-sdk" \ No newline at end of file +Documentation = "https://topggpy.readthedocs.io/en/latest/" +"Release notes" = "https://topggpy.readthedocs.io/en/latest/whats_new.html" +Repository = "https://github.com/top-gg-community/python-sdk" +"Support server" = "https://discord.gg/dbl" \ No newline at end of file From f80bd29fb4bc6430d3cddb0e2497f4e0a17112ed Mon Sep 17 00:00:00 2001 From: null8626 Date: Thu, 6 Jun 2024 10:30:30 +0700 Subject: [PATCH 30/33] fix: shutdown web.Application on close() --- topgg/webhook.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/topgg/webhook.py b/topgg/webhook.py index 9a3fd7f..a491fb6 100644 --- a/topgg/webhook.py +++ b/topgg/webhook.py @@ -3,6 +3,7 @@ # The MIT License (MIT) # Copyright (c) 2021 Assanali Mukhanov +# Copyright (c) 2024 null8626 # Permission is hereby granted, free of charge, to any person obtaining a # copy of this software and associated documentation files (the "Software"), @@ -70,7 +71,6 @@ class WebhookManager(DataContainerMixin): def __init__(self) -> None: super().__init__() - self.__app = web.Application() self._is_running = False @t.overload @@ -120,6 +120,8 @@ async def start(self, port: int) -> None: port (int) The port to run the webhook on. """ + + self.__app = web.Application() runner = web.AppRunner(self.__app) await runner.setup() self._webserver = web.TCPSite(runner, "0.0.0.0", port) @@ -144,6 +146,7 @@ def app(self) -> web.Application: async def close(self) -> None: """Stops the webhook.""" await self._webserver.stop() + await self.__app.shutdown() self._is_running = False def _get_handler(self, type_: WebhookType, auth: str, callback: t.Callable[..., t.Any]) -> _HandlerT: From dcfdaea0f04a252461dbafa88ea17cb01e241805 Mon Sep 17 00:00:00 2001 From: null8626 Date: Thu, 6 Jun 2024 13:29:09 +0700 Subject: [PATCH 31/33] fix: initialize __app as None at initialization --- topgg/webhook.py | 3 +++ 1 file changed, 3 insertions(+) diff --git a/topgg/webhook.py b/topgg/webhook.py index a491fb6..b3c961b 100644 --- a/topgg/webhook.py +++ b/topgg/webhook.py @@ -71,6 +71,8 @@ class WebhookManager(DataContainerMixin): def __init__(self) -> None: super().__init__() + + self.__app = None self._is_running = False @t.overload @@ -145,6 +147,7 @@ def app(self) -> web.Application: async def close(self) -> None: """Stops the webhook.""" + await self._webserver.stop() await self.__app.shutdown() self._is_running = False From 549eb593d47f516bc0c131300d93aecbeb1611e1 Mon Sep 17 00:00:00 2001 From: null8626 Date: Sat, 29 Jun 2024 18:07:20 +0700 Subject: [PATCH 32/33] meta: bump aiohttp --- pyproject.toml | 2 +- requirements.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/pyproject.toml b/pyproject.toml index 5c2dc6f..80a0f4d 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -9,7 +9,7 @@ readme = "README.rst" license = { text = "MIT" } authors = [{ name = "null8626" }, { name = "Top.gg" }] keywords = ["discord", "bot", "topgg", "top.gg"] -dependencies = ["aiohttp>=3.9.0"] +dependencies = ["aiohttp>=3.9.5"] classifiers = [ "Development Status :: 5 - Production/Stable", "License :: OSI Approved :: MIT License", diff --git a/requirements.txt b/requirements.txt index 1b80c60..86edf4d 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1 @@ -aiohttp>=3.9.0 \ No newline at end of file +aiohttp>=3.9.5 \ No newline at end of file From 47e96f51766b51c2504b7c440338b5f718c8675b Mon Sep 17 00:00:00 2001 From: null8626 Date: Mon, 1 Jul 2024 21:41:26 +0700 Subject: [PATCH 33/33] refactor: remove unneeded non-base64 removal --- topgg/client.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/topgg/client.py b/topgg/client.py index b895806..50e0fc4 100644 --- a/topgg/client.py +++ b/topgg/client.py @@ -27,7 +27,6 @@ import base64 import json -import re import typing as t import warnings @@ -70,7 +69,7 @@ def __init__( self._token = token try: - encoded_json = re.sub(r"[^a-zA-Z0-9\+\/]+", "", token.split(".")[1]) + encoded_json = token.split(".")[1] encoded_json += "=" * (4 - (len(encoded_json) % 4)) self.bot_id = int(json.loads(base64.b64decode(encoded_json))["id"])