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:
Alexandre Teles (afterSt0rm) 2023-07-19 23:32:48 -03:00 committed by GitHub
parent 1273f9224b
commit b18097e030
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 563 additions and 565 deletions

View File

@ -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"

View File

@ -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

View File

@ -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: .

View File

@ -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 }}

View File

@ -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 }}

View File

@ -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:

View File

@ -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

View File

@ -3,8 +3,10 @@
--- ---
![License: AGPLv3](https://img.shields.io/github/license/revanced/revanced-api) ![License: AGPLv3](https://img.shields.io/github/license/revanced/revanced-api)
[![codecov](https://codecov.io/gh/ReVanced/revanced-api/branch/main/graph/badge.svg?token=10H8D2CRQO)](https://codecov.io/gh/ReVanced/revanced-api)
[![Build and Publish Docker Image](https://github.com/revanced/revanced-api/actions/workflows/main.yml/badge.svg)](https://github.com/revanced/revanced-api/actions/workflows/main.yml)
[![Qodana | Code Quality Scan](https://github.com/revanced/revanced-api/actions/workflows/quodana.yml/badge.svg)](https://github.com/revanced/revanced-api/actions/workflows/quodana.yml) [![Qodana | Code Quality Scan](https://github.com/revanced/revanced-api/actions/workflows/quodana.yml/badge.svg)](https://github.com/revanced/revanced-api/actions/workflows/quodana.yml)
[![MyPy | Static Type Checking](https://github.com/revanced/revanced-api/actions/workflows/mypy.yml/badge.svg)](https://github.com/revanced/revanced-api/actions/workflows/mypy.yml) [![PyTest | Testing and Code Coverage](https://github.com/revanced/revanced-api/actions/workflows/pytest.yml/badge.svg)](https://github.com/revanced/revanced-api/actions/workflows/pytest.yml)
--- ---

View File

@ -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="/")

View File

@ -93,8 +93,14 @@ 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:
case None, None:
dict.__init__(
self, login=login, avatar_url=avatar_url, html_url=html_url, bio=bio
)
case int(_), None:
dict.__init__( dict.__init__(
self, self,
login=login, login=login,
@ -102,8 +108,25 @@ class Contributor(dict):
html_url=html_url, html_url=html_url,
contributions=contributions, contributions=contributions,
) )
else: case None, str(_):
dict.__init__(self, login=login, avatar_url=avatar_url, html_url=html_url) 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

View File

@ -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(
if response.status != 200: headers=self.headers,
raise SanicException( url=await __fetch_download_url(_release=release),
context=await response.json(loads=ujson.loads),
status_code=response.status,
) )
await self.__validate_request(response)
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)))

View File

@ -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)

View File

@ -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)

View File

@ -82,12 +82,18 @@ 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]
match request.args.get("dev"):
case "true":
data = {
"release": await github_backend.get_latest_pre_release( "release": await github_backend.get_latest_pre_release(
repository=GithubRepository(owner=owner, name=repo) repository=GithubRepository(owner=owner, name=repo)
) )
if request.args.get("dev") == "true" }
else await github_backend.get_latest_release( case _:
data = {
"release": await github_backend.get_latest_release(
repository=GithubRepository(owner=owner, name=repo) repository=GithubRepository(owner=owner, name=repo)
) )
} }
@ -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
) )
} }

View File

@ -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]

View File

@ -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.
"""

View File

@ -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
View File

@ -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:;"

View File

@ -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

File diff suppressed because it is too large Load Diff

View File

@ -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"

View File

@ -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"

View File

@ -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"])

View File

@ -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"]]
)