mirror of
https://github.com/revanced/revanced-api.git
synced 2025-04-30 06:34:36 +02:00
feat: API Fixes and Adjustments (#23)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> Co-authored-by: pre-commit-ci[bot] <66853113+pre-commit-ci[bot]@users.noreply.github.com>
This commit is contained in:
parent
1273f9224b
commit
b18097e030
11
.github/dependabot.yml
vendored
11
.github/dependabot.yml
vendored
@ -1,10 +1,10 @@
|
|||||||
version: 2
|
version: 2
|
||||||
updates:
|
updates:
|
||||||
|
|
||||||
- package-ecosystem: "github-actions"
|
- package-ecosystem: "github-actions"
|
||||||
directory: "/"
|
directory: "/"
|
||||||
schedule:
|
schedule:
|
||||||
interval: "daily"
|
interval: "daily"
|
||||||
|
target-branch: "dev"
|
||||||
assignees:
|
assignees:
|
||||||
- "alexandreteles"
|
- "alexandreteles"
|
||||||
|
|
||||||
@ -12,5 +12,14 @@ updates:
|
|||||||
directory: "/"
|
directory: "/"
|
||||||
schedule:
|
schedule:
|
||||||
interval: "daily"
|
interval: "daily"
|
||||||
|
target-branch: "dev"
|
||||||
|
assignees:
|
||||||
|
- "alexandreteles"
|
||||||
|
|
||||||
|
- package-ecosystem: "docker"
|
||||||
|
directory: "/"
|
||||||
|
schedule:
|
||||||
|
interval: "daily"
|
||||||
|
target-branch: "dev"
|
||||||
assignees:
|
assignees:
|
||||||
- "alexandreteles"
|
- "alexandreteles"
|
||||||
|
2
.github/workflows/dev.yml
vendored
2
.github/workflows/dev.yml
vendored
@ -59,7 +59,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Build Docker image
|
- name: Build Docker image
|
||||||
id: build
|
id: build
|
||||||
uses: docker/build-push-action@v3
|
uses: docker/build-push-action@v4
|
||||||
with:
|
with:
|
||||||
context: .
|
context: .
|
||||||
file: ./Dockerfile
|
file: ./Dockerfile
|
||||||
|
2
.github/workflows/main.yml
vendored
2
.github/workflows/main.yml
vendored
@ -54,7 +54,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Build and push main Docker image
|
- name: Build and push main Docker image
|
||||||
id: build
|
id: build
|
||||||
uses: docker/build-push-action@v3
|
uses: docker/build-push-action@v4
|
||||||
with:
|
with:
|
||||||
build-args: GH_TOKEN=${{ secrets.GH_TOKEN }}
|
build-args: GH_TOKEN=${{ secrets.GH_TOKEN }}
|
||||||
context: .
|
context: .
|
||||||
|
43
.github/workflows/mypy.yml
vendored
43
.github/workflows/mypy.yml
vendored
@ -1,43 +0,0 @@
|
|||||||
name: "MyPy | Static Type Checking"
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [dev, main]
|
|
||||||
pull_request:
|
|
||||||
types: [opened, reopened, edited, synchronize]
|
|
||||||
workflow_dispatch:
|
|
||||||
|
|
||||||
env:
|
|
||||||
default_branch: dev
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
mypy:
|
|
||||||
name: mypy
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
permissions:
|
|
||||||
actions: read
|
|
||||||
contents: read
|
|
||||||
security-events: write
|
|
||||||
|
|
||||||
steps:
|
|
||||||
- name: Checkout repository
|
|
||||||
uses: actions/checkout@v3
|
|
||||||
|
|
||||||
- name: Set up Python
|
|
||||||
uses: actions/setup-python@v4
|
|
||||||
with:
|
|
||||||
python-version: "3.11.4"
|
|
||||||
|
|
||||||
- name: Install project dependencies
|
|
||||||
run: |
|
|
||||||
python -m pip install --upgrade pip
|
|
||||||
if [ -f requirements.txt ];
|
|
||||||
then pip install -r requirements.txt;
|
|
||||||
fi
|
|
||||||
|
|
||||||
- name: Run mypy
|
|
||||||
uses: sasanquaneuf/mypy-github-action@main
|
|
||||||
with:
|
|
||||||
checkName: "mypy"
|
|
||||||
env:
|
|
||||||
GITHUB_TOKEN: ${{ secrets.GH_TOKEN }}
|
|
9
.github/workflows/pytest.yml
vendored
9
.github/workflows/pytest.yml
vendored
@ -1,8 +1,8 @@
|
|||||||
name: "PyTest | Testing and Code Coverage"
|
name: "PyTest & Codecov | Testing and Code Coverage"
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: [dev, main]
|
branches: [dev]
|
||||||
pull_request:
|
pull_request:
|
||||||
types: [opened, reopened, edited, synchronize]
|
types: [opened, reopened, edited, synchronize]
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
@ -41,3 +41,8 @@ jobs:
|
|||||||
custom-arguments: "--cov --cov-report=xml"
|
custom-arguments: "--cov --cov-report=xml"
|
||||||
env:
|
env:
|
||||||
GITHUB_TOKEN: ${{ secrets.GH_TOKEN }}
|
GITHUB_TOKEN: ${{ secrets.GH_TOKEN }}
|
||||||
|
|
||||||
|
- name: Upload coverage reports to Codecov
|
||||||
|
uses: codecov/codecov-action@v3
|
||||||
|
env:
|
||||||
|
CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }}
|
||||||
|
9
.github/workflows/quodana.yml
vendored
9
.github/workflows/quodana.yml
vendored
@ -1,8 +1,8 @@
|
|||||||
name: "Qodana | Code Quality Scan"
|
name: "Qodana | Code Quality Scan and Static Analysis"
|
||||||
|
|
||||||
on:
|
on:
|
||||||
push:
|
push:
|
||||||
branches: [dev, main]
|
branches: [dev]
|
||||||
pull_request:
|
pull_request:
|
||||||
types: [opened, reopened, edited, synchronize]
|
types: [opened, reopened, edited, synchronize]
|
||||||
workflow_dispatch:
|
workflow_dispatch:
|
||||||
@ -32,11 +32,8 @@ jobs:
|
|||||||
then pip install -r requirements.txt;
|
then pip install -r requirements.txt;
|
||||||
fi
|
fi
|
||||||
|
|
||||||
- name: Install testing tools
|
|
||||||
run: pip install mypy pydantic
|
|
||||||
|
|
||||||
- name: "Qodana Scan"
|
- name: "Qodana Scan"
|
||||||
uses: JetBrains/qodana-action@v2023.1.0
|
uses: JetBrains/qodana-action@v2023.1.5
|
||||||
env:
|
env:
|
||||||
QODANA_TOKEN: ${{ secrets.QODANA_TOKEN }}
|
QODANA_TOKEN: ${{ secrets.QODANA_TOKEN }}
|
||||||
with:
|
with:
|
||||||
|
@ -12,7 +12,7 @@ repos:
|
|||||||
- id: check-toml
|
- id: check-toml
|
||||||
- id: check-merge-conflict
|
- id: check-merge-conflict
|
||||||
- repo: https://github.com/psf/black
|
- repo: https://github.com/psf/black
|
||||||
rev: 23.3.0
|
rev: 23.7.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: black
|
- id: black
|
||||||
language_version: python3.11
|
language_version: python3.11
|
||||||
|
@ -3,8 +3,10 @@
|
|||||||
---
|
---
|
||||||
|
|
||||||

|

|
||||||
|
[](https://codecov.io/gh/ReVanced/revanced-api)
|
||||||
|
[](https://github.com/revanced/revanced-api/actions/workflows/main.yml)
|
||||||
[](https://github.com/revanced/revanced-api/actions/workflows/quodana.yml)
|
[](https://github.com/revanced/revanced-api/actions/workflows/quodana.yml)
|
||||||
[](https://github.com/revanced/revanced-api/actions/workflows/mypy.yml)
|
[](https://github.com/revanced/revanced-api/actions/workflows/pytest.yml)
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
@ -4,8 +4,7 @@ from sanic import Blueprint
|
|||||||
from api.github import github
|
from api.github import github
|
||||||
from api.ping import ping
|
from api.ping import ping
|
||||||
from api.socials import socials
|
from api.socials import socials
|
||||||
from api.apkdl import apkdl
|
|
||||||
from api.compat import github as old
|
from api.compat import github as old
|
||||||
from api.donations import donations
|
from api.donations import donations
|
||||||
|
|
||||||
api = Blueprint.group(ping, github, socials, donations, apkdl, old, url_prefix="/")
|
api = Blueprint.group(ping, github, socials, donations, old, url_prefix="/")
|
||||||
|
@ -93,17 +93,40 @@ class Contributor(dict):
|
|||||||
avatar_url: str,
|
avatar_url: str,
|
||||||
html_url: str,
|
html_url: str,
|
||||||
contributions: Optional[int] = None,
|
contributions: Optional[int] = None,
|
||||||
|
bio: Optional[str] = None,
|
||||||
):
|
):
|
||||||
if contributions:
|
match contributions, bio:
|
||||||
dict.__init__(
|
case None, None:
|
||||||
self,
|
dict.__init__(
|
||||||
login=login,
|
self, login=login, avatar_url=avatar_url, html_url=html_url, bio=bio
|
||||||
avatar_url=avatar_url,
|
)
|
||||||
html_url=html_url,
|
case int(_), None:
|
||||||
contributions=contributions,
|
dict.__init__(
|
||||||
)
|
self,
|
||||||
else:
|
login=login,
|
||||||
dict.__init__(self, login=login, avatar_url=avatar_url, html_url=html_url)
|
avatar_url=avatar_url,
|
||||||
|
html_url=html_url,
|
||||||
|
contributions=contributions,
|
||||||
|
)
|
||||||
|
case None, str(_):
|
||||||
|
dict.__init__(
|
||||||
|
self,
|
||||||
|
login=login,
|
||||||
|
avatar_url=avatar_url,
|
||||||
|
html_url=html_url,
|
||||||
|
bio=bio,
|
||||||
|
)
|
||||||
|
case int(_), str(_):
|
||||||
|
dict.__init__(
|
||||||
|
self,
|
||||||
|
login=login,
|
||||||
|
avatar_url=avatar_url,
|
||||||
|
html_url=html_url,
|
||||||
|
contributions=contributions,
|
||||||
|
bio=bio,
|
||||||
|
)
|
||||||
|
case _:
|
||||||
|
raise ValueError("Invalid arguments")
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
@dataclass
|
||||||
|
@ -1,14 +1,15 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
|
from json import loads
|
||||||
import os
|
import os
|
||||||
from operator import eq
|
from operator import eq
|
||||||
from typing import Any, Optional
|
from typing import Optional
|
||||||
|
|
||||||
import ujson
|
import ujson
|
||||||
from aiohttp import ClientResponse
|
from aiohttp import ClientResponse
|
||||||
from sanic import SanicException
|
from sanic import SanicException
|
||||||
from toolz import filter, map
|
from toolz import filter, map, partial
|
||||||
from toolz.dicttoolz import get_in, keyfilter
|
from toolz.dicttoolz import get_in, keyfilter
|
||||||
from toolz.itertoolz import mapcat
|
from toolz.itertoolz import mapcat, pluck
|
||||||
|
|
||||||
from api.backends.backend import Backend, Repository
|
from api.backends.backend import Backend, Repository
|
||||||
from api.backends.entities import *
|
from api.backends.entities import *
|
||||||
@ -95,19 +96,27 @@ class Github(Backend):
|
|||||||
async def __assemble_contributor(
|
async def __assemble_contributor(
|
||||||
contributor: dict, team_view: bool = False
|
contributor: dict, team_view: bool = False
|
||||||
) -> Contributor:
|
) -> Contributor:
|
||||||
if team_view:
|
match team_view:
|
||||||
filter_contributor = keyfilter(
|
case True:
|
||||||
lambda key: key in {"login", "avatar_url", "html_url"},
|
keys = {"login", "avatar_url", "html_url", "bio"}
|
||||||
contributor,
|
case _:
|
||||||
)
|
keys = {"login", "avatar_url", "html_url", "contributions"}
|
||||||
return Contributor(**filter_contributor)
|
|
||||||
|
|
||||||
filter_contributor = keyfilter(
|
filter_contributor = keyfilter(
|
||||||
lambda key: key in {"login", "avatar_url", "html_url", "contributions"},
|
lambda key: key in keys,
|
||||||
contributor,
|
contributor,
|
||||||
)
|
)
|
||||||
|
|
||||||
return Contributor(**filter_contributor)
|
return Contributor(**filter_contributor)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
async def __validate_request(_response: ClientResponse) -> None:
|
||||||
|
if _response.status != 200:
|
||||||
|
raise SanicException(
|
||||||
|
context=await _response.json(loads=ujson.loads),
|
||||||
|
status_code=_response.status,
|
||||||
|
)
|
||||||
|
|
||||||
async def list_releases(
|
async def list_releases(
|
||||||
self, repository: GithubRepository, per_page: int = 30, page: int = 1
|
self, repository: GithubRepository, per_page: int = 30, page: int = 1
|
||||||
) -> list[Release]:
|
) -> list[Release]:
|
||||||
@ -126,11 +135,7 @@ class Github(Backend):
|
|||||||
response: ClientResponse = await http_get(
|
response: ClientResponse = await http_get(
|
||||||
headers=self.headers, url=list_releases_endpoint
|
headers=self.headers, url=list_releases_endpoint
|
||||||
)
|
)
|
||||||
if response.status != 200:
|
await self.__validate_request(response)
|
||||||
raise SanicException(
|
|
||||||
context=await response.json(loads=ujson.loads),
|
|
||||||
status_code=response.status,
|
|
||||||
)
|
|
||||||
releases: list[Release] = await asyncio.gather(
|
releases: list[Release] = await asyncio.gather(
|
||||||
*map(
|
*map(
|
||||||
lambda release: self.__assemble_release(release),
|
lambda release: self.__assemble_release(release),
|
||||||
@ -156,11 +161,7 @@ class Github(Backend):
|
|||||||
response: ClientResponse = await http_get(
|
response: ClientResponse = await http_get(
|
||||||
headers=self.headers, url=release_by_tag_endpoint
|
headers=self.headers, url=release_by_tag_endpoint
|
||||||
)
|
)
|
||||||
if response.status != 200:
|
await self.__validate_request(response)
|
||||||
raise SanicException(
|
|
||||||
context=await response.json(loads=ujson.loads),
|
|
||||||
status_code=response.status,
|
|
||||||
)
|
|
||||||
return await self.__assemble_release(await response.json(loads=ujson.loads))
|
return await self.__assemble_release(await response.json(loads=ujson.loads))
|
||||||
|
|
||||||
async def get_latest_release(
|
async def get_latest_release(
|
||||||
@ -179,11 +180,7 @@ class Github(Backend):
|
|||||||
response: ClientResponse = await http_get(
|
response: ClientResponse = await http_get(
|
||||||
headers=self.headers, url=latest_release_endpoint
|
headers=self.headers, url=latest_release_endpoint
|
||||||
)
|
)
|
||||||
if response.status != 200:
|
await self.__validate_request(response)
|
||||||
raise SanicException(
|
|
||||||
context=await response.json(loads=ujson.loads),
|
|
||||||
status_code=response.status,
|
|
||||||
)
|
|
||||||
return await self.__assemble_release(await response.json(loads=ujson.loads))
|
return await self.__assemble_release(await response.json(loads=ujson.loads))
|
||||||
|
|
||||||
async def get_latest_pre_release(
|
async def get_latest_pre_release(
|
||||||
@ -202,11 +199,7 @@ class Github(Backend):
|
|||||||
response: ClientResponse = await http_get(
|
response: ClientResponse = await http_get(
|
||||||
headers=self.headers, url=list_releases_endpoint
|
headers=self.headers, url=list_releases_endpoint
|
||||||
)
|
)
|
||||||
if response.status != 200:
|
await self.__validate_request(response)
|
||||||
raise SanicException(
|
|
||||||
context=await response.json(loads=ujson.loads),
|
|
||||||
status_code=response.status,
|
|
||||||
)
|
|
||||||
latest_pre_release = next(
|
latest_pre_release = next(
|
||||||
filter(
|
filter(
|
||||||
lambda release: release["prerelease"],
|
lambda release: release["prerelease"],
|
||||||
@ -229,11 +222,7 @@ class Github(Backend):
|
|||||||
response: ClientResponse = await http_get(
|
response: ClientResponse = await http_get(
|
||||||
headers=self.headers, url=contributors_endpoint
|
headers=self.headers, url=contributors_endpoint
|
||||||
)
|
)
|
||||||
if response.status != 200:
|
await self.__validate_request(response)
|
||||||
raise SanicException(
|
|
||||||
context=await response.json(loads=ujson.loads),
|
|
||||||
status_code=response.status,
|
|
||||||
)
|
|
||||||
contributors: list[Contributor] = await asyncio.gather(
|
contributors: list[Contributor] = await asyncio.gather(
|
||||||
*map(self.__assemble_contributor, await response.json(loads=ujson.loads))
|
*map(self.__assemble_contributor, await response.json(loads=ujson.loads))
|
||||||
)
|
)
|
||||||
@ -241,38 +230,43 @@ class Github(Backend):
|
|||||||
return contributors
|
return contributors
|
||||||
|
|
||||||
async def get_patches(
|
async def get_patches(
|
||||||
self, repository: GithubRepository, tag_name: str
|
self, repository: GithubRepository, tag_name: str = "latest", dev: bool = False
|
||||||
) -> list[dict]:
|
) -> list[dict]:
|
||||||
"""Get a dictionary of patch URLs for a given repository.
|
"""Get a dictionary of patch URLs for a given repository.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
repository (GithubRepository): The repository for which to retrieve patches.
|
repository (GithubRepository): The repository for which to retrieve patches.
|
||||||
tag_name: The name of the release tag.
|
tag_name (str): The name of the release tag.
|
||||||
|
dev (bool): If we should get the latest pre-release instead.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
list[dict]: A JSON object containing the patches.
|
list[dict]: A JSON object containing the patches.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
async def __fetch_download_url(release: Release) -> str:
|
async def __fetch_download_url(_release: Release) -> str:
|
||||||
asset = get_in(["assets"], release)
|
asset = get_in(["assets"], _release)
|
||||||
patch_asset = next(
|
patch_asset = next(
|
||||||
filter(lambda x: eq(get_in(["name"], x), "patches.json"), asset), None
|
filter(lambda x: eq(get_in(["name"], x), "patches.json"), asset), None
|
||||||
)
|
)
|
||||||
return get_in(["browser_download_url"], patch_asset)
|
return get_in(["browser_download_url"], patch_asset)
|
||||||
|
|
||||||
response: ClientResponse = await http_get(
|
match tag_name:
|
||||||
headers=self.headers,
|
case "latest":
|
||||||
url=await __fetch_download_url(
|
match dev:
|
||||||
await self.get_release_by_tag_name(
|
case True:
|
||||||
|
release = await self.get_latest_pre_release(repository)
|
||||||
|
case _:
|
||||||
|
release = await self.get_latest_release(repository)
|
||||||
|
case _:
|
||||||
|
release = await self.get_release_by_tag_name(
|
||||||
repository=repository, tag_name=tag_name
|
repository=repository, tag_name=tag_name
|
||||||
)
|
)
|
||||||
),
|
|
||||||
|
response: ClientResponse = await http_get(
|
||||||
|
headers=self.headers,
|
||||||
|
url=await __fetch_download_url(_release=release),
|
||||||
)
|
)
|
||||||
if response.status != 200:
|
await self.__validate_request(response)
|
||||||
raise SanicException(
|
|
||||||
context=await response.json(loads=ujson.loads),
|
|
||||||
status_code=response.status,
|
|
||||||
)
|
|
||||||
return ujson.loads(await response.read())
|
return ujson.loads(await response.read())
|
||||||
|
|
||||||
async def get_team_members(self, repository: GithubRepository) -> list[Contributor]:
|
async def get_team_members(self, repository: GithubRepository) -> list[Contributor]:
|
||||||
@ -285,18 +279,30 @@ class Github(Backend):
|
|||||||
list[Contributor]: A list of members in the owner organization.
|
list[Contributor]: A list of members in the owner organization.
|
||||||
"""
|
"""
|
||||||
team_members_endpoint: str = f"{self.base_url}/orgs/{repository.owner}/members"
|
team_members_endpoint: str = f"{self.base_url}/orgs/{repository.owner}/members"
|
||||||
|
user_info_endpoint: str = f"{self.base_url}/users/"
|
||||||
response: ClientResponse = await http_get(
|
response: ClientResponse = await http_get(
|
||||||
headers=self.headers, url=team_members_endpoint
|
headers=self.headers, url=team_members_endpoint
|
||||||
)
|
)
|
||||||
if response.status != 200:
|
await self.__validate_request(response)
|
||||||
raise SanicException(
|
logins: list[str] = list(pluck("login", await response.json()))
|
||||||
context=await response.json(loads=ujson.loads),
|
_http_get = partial(http_get, headers=self.headers)
|
||||||
status_code=response.status,
|
user_data_response: list[dict] = await asyncio.gather(
|
||||||
|
*map(
|
||||||
|
lambda login: _http_get(url=f"{user_info_endpoint}{login}"),
|
||||||
|
logins,
|
||||||
)
|
)
|
||||||
|
)
|
||||||
|
user_data = await asyncio.gather(
|
||||||
|
*map(
|
||||||
|
lambda _response: _response.json(loads=ujson.loads),
|
||||||
|
user_data_response,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
print(await response.json(loads=ujson.loads))
|
||||||
team_members: list[Contributor] = await asyncio.gather(
|
team_members: list[Contributor] = await asyncio.gather(
|
||||||
*map(
|
*map(
|
||||||
lambda member: self.__assemble_contributor(member, team_view=True),
|
lambda member: self.__assemble_contributor(member, team_view=True),
|
||||||
await response.json(loads=ujson.loads),
|
list(user_data),
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -315,17 +321,18 @@ class Github(Backend):
|
|||||||
list[dict[str, str]]: A JSON object containing the releases.
|
list[dict[str, str]]: A JSON object containing the releases.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def transform(data, repository):
|
def transform(data: dict, repository: GithubRepository):
|
||||||
"""Transforms a dictionary from the input list into a list of dictionaries with the desired structure.
|
"""Transforms a dictionary from the input list into a list of dictionaries with the desired structure.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
data(dict): A dictionary from the input list.
|
data(dict): A dictionary from the input list.
|
||||||
|
repository(GithubRepository): The repository for which to retrieve releases.
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
_[list]: A list of dictionaries with the desired structure.
|
_[list]: A list of dictionaries with the desired structure.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def process_asset(asset):
|
def process_asset(asset: dict) -> dict:
|
||||||
"""Transforms an asset dictionary into a new dictionary with the desired structure.
|
"""Transforms an asset dictionary into a new dictionary with the desired structure.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
@ -353,3 +360,39 @@ class Github(Backend):
|
|||||||
)
|
)
|
||||||
|
|
||||||
return list(mapcat(lambda pair: transform(*pair), zip(results, repositories)))
|
return list(mapcat(lambda pair: transform(*pair), zip(results, repositories)))
|
||||||
|
|
||||||
|
async def compat_get_contributors(
|
||||||
|
self, repositories: list[GithubRepository]
|
||||||
|
) -> list:
|
||||||
|
"""Get the contributors for a set of repositories (v1 compat).
|
||||||
|
|
||||||
|
Args:
|
||||||
|
repositories (set[GithubRepository]): The repositories for which to retrieve contributors.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
list[dict[str, str]]: A JSON object containing the contributors.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def transform(data: dict, repository: GithubRepository) -> list:
|
||||||
|
"""Transforms a dictionary from the input list into a list of dictionaries with the desired structure.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
data(dict): A dictionary from the input list.
|
||||||
|
repository(GithubRepository): The repository for which to retrieve contributors.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
_[list]: A list of dictionaries with the desired structure.
|
||||||
|
"""
|
||||||
|
return {
|
||||||
|
"name": f"{repository.owner}/{repository.name}",
|
||||||
|
"contributors": data,
|
||||||
|
}
|
||||||
|
|
||||||
|
results = await asyncio.gather(
|
||||||
|
*map(
|
||||||
|
lambda repository: self.get_contributors(repository),
|
||||||
|
repositories,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
|
||||||
|
return list(map(lambda pair: transform(*pair), zip(results, repositories)))
|
||||||
|
@ -17,7 +17,7 @@ from sanic_ext import openapi
|
|||||||
|
|
||||||
from api.backends.github import Github, GithubRepository
|
from api.backends.github import Github, GithubRepository
|
||||||
from api.models.github import *
|
from api.models.github import *
|
||||||
from api.models.compat import ToolsResponseModel
|
from api.models.compat import ToolsResponseModel, ContributorsResponseModel
|
||||||
from config import compat_repositories, owner
|
from config import compat_repositories, owner
|
||||||
|
|
||||||
github: Blueprint = Blueprint("old")
|
github: Blueprint = Blueprint("old")
|
||||||
@ -59,3 +59,29 @@ async def tools(request: Request) -> JSONResponse:
|
|||||||
}
|
}
|
||||||
|
|
||||||
return json(data, status=200)
|
return json(data, status=200)
|
||||||
|
|
||||||
|
|
||||||
|
@github.get("/contributors")
|
||||||
|
@openapi.definition(
|
||||||
|
summary="Get organization-wise contributors.", response=[ContributorsResponseModel]
|
||||||
|
)
|
||||||
|
async def contributors(request: Request) -> JSONResponse:
|
||||||
|
"""
|
||||||
|
Retrieve a list of releases for a Github repository.
|
||||||
|
|
||||||
|
**Returns:**
|
||||||
|
- JSONResponse: A Sanic JSONResponse object containing the list of releases.
|
||||||
|
|
||||||
|
**Raises:**
|
||||||
|
- HTTPException: If there is an error retrieving the releases.
|
||||||
|
"""
|
||||||
|
|
||||||
|
data: dict[str, list] = {
|
||||||
|
"repositories": await github_backend.compat_get_contributors(
|
||||||
|
repositories=[
|
||||||
|
GithubRepository(owner=owner, name=repo) for repo in compat_repositories
|
||||||
|
]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return json(data, status=200)
|
||||||
|
@ -10,7 +10,7 @@ from sanic.response import JSONResponse, json
|
|||||||
from sanic_ext import openapi
|
from sanic_ext import openapi
|
||||||
|
|
||||||
from api.models.donations import DonationsResponseModel
|
from api.models.donations import DonationsResponseModel
|
||||||
from config import donation_info, api_version
|
from config import api_version, wallets, links
|
||||||
|
|
||||||
donations: Blueprint = Blueprint("donations", version=api_version)
|
donations: Blueprint = Blueprint("donations", version=api_version)
|
||||||
|
|
||||||
@ -27,5 +27,10 @@ async def root(request: Request) -> JSONResponse:
|
|||||||
**Returns:**
|
**Returns:**
|
||||||
- JSONResponse: A Sanic JSONResponse instance containing a dictionary with the donation links and wallets.
|
- JSONResponse: A Sanic JSONResponse instance containing a dictionary with the donation links and wallets.
|
||||||
"""
|
"""
|
||||||
data: dict[str, dict] = {"donations": donation_info}
|
data: dict[str, dict] = {
|
||||||
|
"donations": {
|
||||||
|
"wallets": wallets,
|
||||||
|
"links": links,
|
||||||
|
}
|
||||||
|
}
|
||||||
return json(data, status=200)
|
return json(data, status=200)
|
||||||
|
@ -82,15 +82,21 @@ async def latest_release(request: Request, repo: str) -> JSONResponse:
|
|||||||
- HTTPException: If there is an error retrieving the releases.
|
- HTTPException: If there is an error retrieving the releases.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
data: dict[str, Release] = {
|
data: dict[str, Release]
|
||||||
"release": await github_backend.get_latest_pre_release(
|
|
||||||
repository=GithubRepository(owner=owner, name=repo)
|
match request.args.get("dev"):
|
||||||
)
|
case "true":
|
||||||
if request.args.get("dev") == "true"
|
data = {
|
||||||
else await github_backend.get_latest_release(
|
"release": await github_backend.get_latest_pre_release(
|
||||||
repository=GithubRepository(owner=owner, name=repo)
|
repository=GithubRepository(owner=owner, name=repo)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
case _:
|
||||||
|
data = {
|
||||||
|
"release": await github_backend.get_latest_release(
|
||||||
|
repository=GithubRepository(owner=owner, name=repo)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
return json(data, status=200)
|
return json(data, status=200)
|
||||||
|
|
||||||
@ -165,6 +171,9 @@ async def get_patches(request: Request, tag: str) -> JSONResponse:
|
|||||||
**Args:**
|
**Args:**
|
||||||
- tag (str): The tag for the patches to be retrieved.
|
- tag (str): The tag for the patches to be retrieved.
|
||||||
|
|
||||||
|
**Query Parameters:**
|
||||||
|
- dev (bool): Whether or not to retrieve the latest development release.
|
||||||
|
|
||||||
**Returns:**
|
**Returns:**
|
||||||
- JSONResponse: A Sanic JSONResponse object containing the list of patches.
|
- JSONResponse: A Sanic JSONResponse object containing the list of patches.
|
||||||
|
|
||||||
@ -174,9 +183,11 @@ async def get_patches(request: Request, tag: str) -> JSONResponse:
|
|||||||
|
|
||||||
repo: str = "revanced-patches"
|
repo: str = "revanced-patches"
|
||||||
|
|
||||||
|
dev: bool = bool(request.args.get("dev"))
|
||||||
|
|
||||||
data: dict[str, list[dict]] = {
|
data: dict[str, list[dict]] = {
|
||||||
"patches": await github_backend.get_patches(
|
"patches": await github_backend.get_patches(
|
||||||
repository=GithubRepository(owner=owner, name=repo), tag_name=tag
|
repository=GithubRepository(owner=owner, name=repo), tag_name=tag, dev=dev
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,4 +1,5 @@
|
|||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
from api.models.github import ContributorsFields
|
||||||
|
|
||||||
|
|
||||||
class ToolsResponseFields(BaseModel):
|
class ToolsResponseFields(BaseModel):
|
||||||
@ -25,3 +26,24 @@ class ToolsResponseModel(BaseModel):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
tools: list[ToolsResponseFields]
|
tools: list[ToolsResponseFields]
|
||||||
|
|
||||||
|
|
||||||
|
class ContributorsResponseFields(BaseModel):
|
||||||
|
"""Implements the fields for the /contributors endpoint.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
BaseModel (pydantic.BaseModel): BaseModel from pydantic
|
||||||
|
"""
|
||||||
|
|
||||||
|
name: str
|
||||||
|
contributors: list[ContributorsFields]
|
||||||
|
|
||||||
|
|
||||||
|
class ContributorsResponseModel(BaseModel):
|
||||||
|
"""Implements the JSON response model for the /contributors endpoint.
|
||||||
|
|
||||||
|
Args:
|
||||||
|
BaseModel (pydantic.BaseModel): BaseModel from pydantic
|
||||||
|
"""
|
||||||
|
|
||||||
|
repositories: list[ContributorsResponseFields]
|
||||||
|
@ -1,13 +1,18 @@
|
|||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
|
||||||
|
|
||||||
|
class DonationFields(BaseModel):
|
||||||
|
"""
|
||||||
|
A Pydantic BaseModel that represents all the donation links and wallets.
|
||||||
|
"""
|
||||||
|
|
||||||
|
wallets: dict[str, str]
|
||||||
|
links: dict[str, str]
|
||||||
|
|
||||||
|
|
||||||
class DonationsResponseModel(BaseModel):
|
class DonationsResponseModel(BaseModel):
|
||||||
"""
|
"""
|
||||||
A Pydantic BaseModel that represents a dictionary of donation links.
|
A Pydantic BaseModel that represents a dictionary of donation links.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
donations: dict[str, str]
|
donations: DonationFields
|
||||||
"""
|
|
||||||
A dictionary where the keys are the names of the donation destinations, and
|
|
||||||
the values are the links to services or wallet addresses.
|
|
||||||
"""
|
|
||||||
|
@ -117,6 +117,7 @@ class TeamMemberFields(BaseModel):
|
|||||||
login: str
|
login: str
|
||||||
avatar_url: str
|
avatar_url: str
|
||||||
html_url: str
|
html_url: str
|
||||||
|
bio: Optional[str]
|
||||||
|
|
||||||
|
|
||||||
class TeamMembersModel(BaseModel):
|
class TeamMembersModel(BaseModel):
|
||||||
|
2
app.py
2
app.py
@ -48,4 +48,4 @@ async def add_cache_control(request, response):
|
|||||||
async def add_csp(request, response):
|
async def add_csp(request, response):
|
||||||
response.headers[
|
response.headers[
|
||||||
"Content-Security-Policy"
|
"Content-Security-Policy"
|
||||||
] = "default-src * 'unsafe-inline' 'unsafe-eval'; img-src * data:;"
|
] = "default-src * 'unsafe-inline' 'unsafe-eval' data: blob:;"
|
||||||
|
@ -15,9 +15,7 @@ social_links: dict[str, str] = {
|
|||||||
|
|
||||||
# Donation info
|
# Donation info
|
||||||
|
|
||||||
donation_info: dict[str, str] = {
|
wallets: dict[str, str] = {
|
||||||
"opencollective": "https://opencollective.com/revanced",
|
|
||||||
"github": "https://github.com/sponsors/ReVanced",
|
|
||||||
"btc": "bc1q4x8j6mt27y5gv0q625t8wkr87ruy8fprpy4v3f",
|
"btc": "bc1q4x8j6mt27y5gv0q625t8wkr87ruy8fprpy4v3f",
|
||||||
"doge": "D8GH73rNjudgi6bS2krrXWEsU9KShedLXp",
|
"doge": "D8GH73rNjudgi6bS2krrXWEsU9KShedLXp",
|
||||||
"eth": "0x7ab4091e00363654bf84B34151225742cd92FCE5",
|
"eth": "0x7ab4091e00363654bf84B34151225742cd92FCE5",
|
||||||
@ -25,6 +23,11 @@ donation_info: dict[str, str] = {
|
|||||||
"xmr": "46YwWDbZD6jVptuk5mLHsuAmh1BnUMSjSNYacozQQEraWSQ93nb2yYVRHoMR6PmFYWEHsLHg9tr1cH5M8Rtn7YaaGQPCjSh",
|
"xmr": "46YwWDbZD6jVptuk5mLHsuAmh1BnUMSjSNYacozQQEraWSQ93nb2yYVRHoMR6PmFYWEHsLHg9tr1cH5M8Rtn7YaaGQPCjSh",
|
||||||
}
|
}
|
||||||
|
|
||||||
|
links: dict[str, str] = {
|
||||||
|
"opencollective": "https://opencollective.com/revanced",
|
||||||
|
"github": "https://github.com/sponsors/ReVanced",
|
||||||
|
}
|
||||||
|
|
||||||
# API Configuration
|
# API Configuration
|
||||||
|
|
||||||
backend: str = "github"
|
backend: str = "github"
|
||||||
|
638
poetry.lock
generated
638
poetry.lock
generated
File diff suppressed because it is too large
Load Diff
@ -8,27 +8,26 @@ readme = "README.md"
|
|||||||
|
|
||||||
[tool.poetry.dependencies]
|
[tool.poetry.dependencies]
|
||||||
python = "^3.11"
|
python = "^3.11"
|
||||||
aiohttp = {version = ">=3.8.4", extras = ["speedups"]}
|
aiohttp = {version = "^3.8.5", extras = ["speedups"]}
|
||||||
sanic = {version = ">=23.3.0", extras = ["ext", "http3"]}
|
sanic = {version = "^23.3.0", extras = ["ext"]}
|
||||||
ujson = ">=5.7.0"
|
ujson = "^5.8.0"
|
||||||
asyncstdlib = "3.10.8"
|
asyncstdlib = "^3.10.8"
|
||||||
pydantic = "^1.10.11"
|
pydantic = "^1.10.11"
|
||||||
aioquic = ">=0.9.20"
|
cytoolz = "^0.12.1"
|
||||||
cytoolz = ">=0.12.1"
|
beautifulsoup4 = "^4.12.2"
|
||||||
beautifulsoup4 = ">=4.12.2"
|
setuptools = "^68.0.0"
|
||||||
setuptools = ">=67.7.2"
|
lxml = "^4.9.3"
|
||||||
lxml = ">=4.9.2"
|
mypy = "^1.4.1"
|
||||||
mypy = ">=1.2.0"
|
types-ujson = "^5.8.0.0"
|
||||||
types-ujson = ">=5.7.0.5"
|
types-aiofiles = "^23.1.0.4"
|
||||||
types-aiofiles = ">=23.1.0.1"
|
sanic-testing = "^23.3.0"
|
||||||
sanic-testing = ">=23.3.0"
|
pytest-asyncio = "^0.21.1"
|
||||||
pytest-asyncio = ">=0.21.0"
|
types-beautifulsoup4 = "^4.12.0.5"
|
||||||
types-beautifulsoup4 = ">=4.12.0.5"
|
pytest-md = "^0.2.0"
|
||||||
pytest-md = ">=0.2.0"
|
pytest-emoji = "^0.2.0"
|
||||||
pytest-emoji = ">=0.2.0"
|
coverage = "^7.2.7"
|
||||||
coverage = ">=7.2.5"
|
pytest-cov = "^4.1.0"
|
||||||
pytest-cov = "^4.0.0"
|
pytest = "^7.4.0"
|
||||||
pytest = ">=7.4.0"
|
|
||||||
|
|
||||||
[tool.pytest.ini_options]
|
[tool.pytest.ini_options]
|
||||||
asyncio_mode = "auto"
|
asyncio_mode = "auto"
|
||||||
|
@ -1,11 +1,10 @@
|
|||||||
aiodns==3.0.0 ; python_version >= "3.11" and python_version < "4.0"
|
aiodns==3.0.0 ; python_version >= "3.11" and python_version < "4.0"
|
||||||
aiofiles==23.1.0 ; python_version >= "3.11" and python_version < "4.0"
|
aiofiles==23.1.0 ; python_version >= "3.11" and python_version < "4.0"
|
||||||
aiohttp[speedups]==3.8.4 ; python_version >= "3.11" and python_version < "4.0"
|
aiohttp[speedups]==3.8.5 ; python_version >= "3.11" and python_version < "4.0"
|
||||||
aioquic==0.9.21 ; python_version >= "3.11" and python_version < "4.0"
|
|
||||||
aiosignal==1.3.1 ; python_version >= "3.11" and python_version < "4.0"
|
aiosignal==1.3.1 ; python_version >= "3.11" and python_version < "4.0"
|
||||||
anyio==3.7.1 ; python_version >= "3.11" and python_version < "4.0"
|
anyio==3.7.1 ; python_version >= "3.11" and python_version < "4.0"
|
||||||
async-timeout==4.0.2 ; python_version >= "3.11" and python_version < "4.0"
|
async-timeout==4.0.2 ; python_version >= "3.11" and python_version < "4.0"
|
||||||
asyncstdlib==3.10.6 ; python_version >= "3.11" and python_version < "4.0"
|
asyncstdlib==3.10.8 ; python_version >= "3.11" and python_version < "4.0"
|
||||||
attrs==23.1.0 ; python_version >= "3.11" and python_version < "4.0"
|
attrs==23.1.0 ; python_version >= "3.11" and python_version < "4.0"
|
||||||
beautifulsoup4==4.12.2 ; python_version >= "3.11" and python_version < "4.0"
|
beautifulsoup4==4.12.2 ; python_version >= "3.11" and python_version < "4.0"
|
||||||
brotli==1.0.9 ; python_version >= "3.11" and python_version < "4.0"
|
brotli==1.0.9 ; python_version >= "3.11" and python_version < "4.0"
|
||||||
@ -15,9 +14,8 @@ charset-normalizer==3.2.0 ; python_version >= "3.11" and python_version < "4.0"
|
|||||||
colorama==0.4.6 ; python_version >= "3.11" and python_version < "4.0" and sys_platform == "win32"
|
colorama==0.4.6 ; python_version >= "3.11" and python_version < "4.0" and sys_platform == "win32"
|
||||||
coverage==7.2.7 ; python_version >= "3.11" and python_version < "4.0"
|
coverage==7.2.7 ; python_version >= "3.11" and python_version < "4.0"
|
||||||
coverage[toml]==7.2.7 ; python_version >= "3.11" and python_version < "4.0"
|
coverage[toml]==7.2.7 ; python_version >= "3.11" and python_version < "4.0"
|
||||||
cryptography==41.0.2 ; python_version >= "3.11" and python_version < "4.0"
|
|
||||||
cytoolz==0.12.1 ; python_version >= "3.11" and python_version < "4.0"
|
cytoolz==0.12.1 ; python_version >= "3.11" and python_version < "4.0"
|
||||||
frozenlist==1.3.3 ; python_version >= "3.11" and python_version < "4.0"
|
frozenlist==1.4.0 ; python_version >= "3.11" and python_version < "4.0"
|
||||||
h11==0.14.0 ; python_version >= "3.11" and python_version < "4.0"
|
h11==0.14.0 ; python_version >= "3.11" and python_version < "4.0"
|
||||||
html5tagger==1.3.0 ; python_version >= "3.11" and python_version < "4.0"
|
html5tagger==1.3.0 ; python_version >= "3.11" and python_version < "4.0"
|
||||||
httpcore==0.16.3 ; python_version >= "3.11" and python_version < "4.0"
|
httpcore==0.16.3 ; python_version >= "3.11" and python_version < "4.0"
|
||||||
@ -34,19 +32,17 @@ pluggy==1.2.0 ; python_version >= "3.11" and python_version < "4.0"
|
|||||||
pycares==4.3.0 ; python_version >= "3.11" and python_version < "4.0"
|
pycares==4.3.0 ; python_version >= "3.11" and python_version < "4.0"
|
||||||
pycparser==2.21 ; python_version >= "3.11" and python_version < "4.0"
|
pycparser==2.21 ; python_version >= "3.11" and python_version < "4.0"
|
||||||
pydantic==1.10.11 ; python_version >= "3.11" and python_version < "4.0"
|
pydantic==1.10.11 ; python_version >= "3.11" and python_version < "4.0"
|
||||||
pylsqpack==0.3.17 ; python_version >= "3.11" and python_version < "4.0"
|
|
||||||
pyopenssl==23.2.0 ; python_version >= "3.11" and python_version < "4.0"
|
|
||||||
pytest==7.4.0 ; python_version >= "3.11" and python_version < "4.0"
|
pytest==7.4.0 ; python_version >= "3.11" and python_version < "4.0"
|
||||||
pytest-asyncio==0.21.0 ; python_version >= "3.11" and python_version < "4.0"
|
pytest-asyncio==0.21.1 ; python_version >= "3.11" and python_version < "4.0"
|
||||||
pytest-cov==4.1.0 ; python_version >= "3.11" and python_version < "4.0"
|
pytest-cov==4.1.0 ; python_version >= "3.11" and python_version < "4.0"
|
||||||
pytest-emoji==0.2.0 ; python_version >= "3.11" and python_version < "4.0"
|
pytest-emoji==0.2.0 ; python_version >= "3.11" and python_version < "4.0"
|
||||||
pytest-md==0.2.0 ; python_version >= "3.11" and python_version < "4.0"
|
pytest-md==0.2.0 ; python_version >= "3.11" and python_version < "4.0"
|
||||||
pyyaml==6.0 ; python_version >= "3.11" and python_version < "4.0"
|
pyyaml==6.0.1 ; python_version >= "3.11" and python_version < "4.0"
|
||||||
rfc3986[idna2008]==1.5.0 ; python_version >= "3.11" and python_version < "4.0"
|
rfc3986[idna2008]==1.5.0 ; python_version >= "3.11" and python_version < "4.0"
|
||||||
sanic-ext==23.3.0 ; python_version >= "3.11" and python_version < "4.0"
|
sanic-ext==23.3.0 ; python_version >= "3.11" and python_version < "4.0"
|
||||||
sanic-routing==22.8.0 ; python_version >= "3.11" and python_version < "4.0"
|
sanic-routing==22.8.0 ; python_version >= "3.11" and python_version < "4.0"
|
||||||
sanic-testing==23.3.0 ; python_version >= "3.11" and python_version < "4.0"
|
sanic-testing==23.3.0 ; python_version >= "3.11" and python_version < "4.0"
|
||||||
sanic[ext,http3]==23.3.0 ; python_version >= "3.11" and python_version < "4.0"
|
sanic[ext]==23.3.0 ; python_version >= "3.11" and python_version < "4.0"
|
||||||
setuptools==68.0.0 ; python_version >= "3.11" and python_version < "4.0"
|
setuptools==68.0.0 ; python_version >= "3.11" and python_version < "4.0"
|
||||||
sniffio==1.3.0 ; python_version >= "3.11" and python_version < "4.0"
|
sniffio==1.3.0 ; python_version >= "3.11" and python_version < "4.0"
|
||||||
soupsieve==2.4.1 ; python_version >= "3.11" and python_version < "4.0"
|
soupsieve==2.4.1 ; python_version >= "3.11" and python_version < "4.0"
|
||||||
|
@ -1,17 +1,17 @@
|
|||||||
import pytest
|
# import pytest
|
||||||
from sanic import Sanic
|
# from sanic import Sanic
|
||||||
|
|
||||||
from api.models.appinfo import AppInfoModel
|
# from api.models.appinfo import AppInfoModel
|
||||||
|
|
||||||
from config import api_version, apkdl_testing_package
|
# from config import api_version, apkdl_testing_package
|
||||||
|
|
||||||
# socials
|
# # socials
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.asyncio
|
# @pytest.mark.asyncio
|
||||||
async def test_socials(app: Sanic):
|
# async def test_socials(app: Sanic):
|
||||||
_, response = await app.asgi_client.get(
|
# _, response = await app.asgi_client.get(
|
||||||
f"/{api_version}/app/info/{apkdl_testing_package}"
|
# f"/{api_version}/app/info/{apkdl_testing_package}"
|
||||||
)
|
# )
|
||||||
assert response.status == 200
|
# assert response.status == 200
|
||||||
assert AppInfoModel(app_info=response.json["app_info"])
|
# assert AppInfoModel(app_info=response.json["app_info"])
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import pytest
|
import pytest
|
||||||
from sanic import Sanic
|
from sanic import Sanic
|
||||||
|
|
||||||
from api.models.compat import ToolsResponseModel
|
from api.models.compat import ToolsResponseModel, ContributorsResponseModel
|
||||||
|
|
||||||
# compatibility layer
|
# compatibility layer
|
||||||
|
|
||||||
@ -11,3 +11,12 @@ async def test_compat_tools(app: Sanic):
|
|||||||
_, response = await app.asgi_client.get(f"/tools")
|
_, response = await app.asgi_client.get(f"/tools")
|
||||||
assert response.status == 200
|
assert response.status == 200
|
||||||
assert ToolsResponseModel(tools=[tool for tool in response.json["tools"]])
|
assert ToolsResponseModel(tools=[tool for tool in response.json["tools"]])
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.asyncio
|
||||||
|
async def test_compat_contributors(app: Sanic):
|
||||||
|
_, response = await app.asgi_client.get(f"/contributors")
|
||||||
|
assert response.status == 200
|
||||||
|
assert ContributorsResponseModel(
|
||||||
|
repositories=[repo for repo in response.json["repositories"]]
|
||||||
|
)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user