refactor use pydantic

This commit is contained in:
Vlad Stan 2023-01-23 11:52:15 +02:00
parent b57e808198
commit 39add25b3b

View file

@ -7,7 +7,7 @@ import urllib.request
import zipfile import zipfile
from http import HTTPStatus from http import HTTPStatus
from pathlib import Path from pathlib import Path
from typing import Any, List, NamedTuple, Optional from typing import Any, List, NamedTuple, Optional, Tuple
import httpx import httpx
from fastapi.exceptions import HTTPException from fastapi.exceptions import HTTPException
@ -112,31 +112,60 @@ class ExtensionRelease(BaseModel):
details_html: Optional[str] = None details_html: Optional[str] = None
@classmethod @classmethod
def from_github_release(cls, source_repo: str, r: dict) -> "ExtensionRelease": def from_github_release(
cls, source_repo: str, r: "GitHubRepoRelease"
) -> "ExtensionRelease":
return ExtensionRelease( return ExtensionRelease(
name=r["name"], name=r.name,
description=r["name"], description=r.name,
version=r["tag_name"], version=r.tag_name,
archive=r["zipball_url"], archive=r.zipball_url,
source_repo=source_repo, source_repo=source_repo,
# description=r["body"], # bad for JSON # description=r.body, # bad for JSON
html_url=r["html_url"], html_url=r.html_url,
) )
@classmethod @classmethod
async def all_releases(cls, org, repo) -> List["ExtensionRelease"]: async def all_releases(cls, org: str, repo: str) -> List["ExtensionRelease"]:
try: try:
releases_url = f"https://api.github.com/repos/{org}/{repo}/releases" github_releases = await fetch_github_releases(org, repo)
error_msg = "Cannot fetch extension releases"
releases = await gihub_api_get(releases_url, error_msg)
return [ return [
ExtensionRelease.from_github_release(f"{org}/{repo}", r) ExtensionRelease.from_github_release(f"{org}/{repo}", r)
for r in releases for r in github_releases
] ]
except: except Exception as e:
logger.warning(e)
return [] return []
class GitHubRepoRelease(BaseModel):
name: str
tag_name: str
zipball_url: str
html_url: str
class ExplicitRelease(BaseModel):
id: str
name: str
version: str
archive: str
hash: str
dependencies: List[str] = []
icon: Optional[str]
short_description: Optional[str]
html_url: Optional[str]
details: Optional[str]
info_notification: Optional[str]
critical_notification: Optional[str]
class GitHubRelease(BaseModel):
id: str
organisation: str
repository: str
class InstallableExtension(BaseModel): class InstallableExtension(BaseModel):
id: str id: str
name: str name: str
@ -185,7 +214,10 @@ class InstallableExtension(BaseModel):
def has_installed_version(self) -> bool: def has_installed_version(self) -> bool:
if not Path(self.ext_dir).is_dir(): if not Path(self.ext_dir).is_dir():
return False return False
with open(os.path.join(self.ext_dir, "config.json"), "r") as json_file: config_file = os.path.join(self.ext_dir, "config.json")
if not Path(config_file).is_file():
return False
with open(config_file, "r") as json_file:
config_json = json.load(json_file) config_json = json.load(json_file)
return config_json.get("is_installed") == True return config_json.get("is_installed") == True
@ -282,21 +314,26 @@ class InstallableExtension(BaseModel):
return ext return ext
@classmethod @classmethod
async def from_repo( async def from_github_release(
cls, ext_id, org, repo_name cls, github_release: GitHubRelease
) -> Optional["InstallableExtension"]: ) -> Optional["InstallableExtension"]:
try: try:
repo, latest_release, config = await fetch_github_repo_info(org, repo_name) repo, latest_release, config = await fetch_github_repo_info(
github_release.organisation, github_release.repository
)
return InstallableExtension( return InstallableExtension(
id=ext_id, id=github_release.id,
name=config.get("name"), name=config.name,
short_description=config.get("short_description"), short_description=config.short_description,
version="0", version="0",
stars=repo["stargazers_count"], stars=repo.stargazers_count,
icon_url=icon_to_github_url(f"{org}/{repo_name}", config.get("tile")), icon_url=icon_to_github_url(
f"{github_release.organisation}/{github_release.repository}",
config.tile,
),
latest_release=ExtensionRelease.from_github_release( latest_release=ExtensionRelease.from_github_release(
repo["html_url"], latest_release repo.html_url, latest_release
), ),
) )
except Exception as e: except Exception as e:
@ -304,15 +341,15 @@ class InstallableExtension(BaseModel):
return None return None
@classmethod @classmethod
def from_manifest(cls, e: dict) -> "InstallableExtension": def from_explicit_release(cls, e: ExplicitRelease) -> "InstallableExtension":
return InstallableExtension( return InstallableExtension(
id=e["id"], id=e.id,
name=e["name"], name=e.name,
archive=e["archive"], archive=e.archive,
hash=e["hash"], hash=e.hash,
short_description=e["shortDescription"], short_description=e.short_description,
icon=e["icon"], icon=e.icon,
dependencies=e["dependencies"] if "dependencies" in e else [], dependencies=e.dependencies,
) )
@classmethod @classmethod
@ -324,24 +361,21 @@ class InstallableExtension(BaseModel):
for url in settings.lnbits_extensions_manifests: for url in settings.lnbits_extensions_manifests:
try: try:
error_msg = "Cannot fetch extensions manifest" manifest = await fetch_manifest(url)
manifest = await gihub_api_get(url, error_msg)
for r in manifest.get("repos", []): for r in manifest.repos:
if r["id"] in extension_id_list: if r.id in extension_id_list:
continue continue
ext = await InstallableExtension.from_repo( ext = await InstallableExtension.from_github_release(r)
r["id"], r["organisation"], r["repository"]
)
if ext: if ext:
extension_list += [ext] extension_list += [ext]
extension_id_list += [ext.id] extension_id_list += [ext.id]
for e in manifest.get("extensions", []): for e in manifest.extensions:
if e["id"] in extension_id_list: if e.id in extension_id_list:
continue continue
extension_list += [InstallableExtension.from_manifest(e)] extension_list += [InstallableExtension.from_explicit_release(e)]
extension_id_list += [e["id"]] extension_id_list += [e.id]
except Exception as e: except Exception as e:
logger.warning(f"Manifest {url} failed with '{str(e)}'") logger.warning(f"Manifest {url} failed with '{str(e)}'")
@ -353,29 +387,26 @@ class InstallableExtension(BaseModel):
for url in settings.lnbits_extensions_manifests: for url in settings.lnbits_extensions_manifests:
try: try:
error_msg = "Cannot fetch extensions manifest" manifest = await fetch_manifest(url)
manifest = await gihub_api_get(url, error_msg) for r in manifest.repos:
if "repos" in manifest: if r.id == ext_id:
for r in manifest["repos"]:
if r["id"] == ext_id:
repo_releases = await ExtensionRelease.all_releases( repo_releases = await ExtensionRelease.all_releases(
r["organisation"], r["repository"] r.organisation, r.repository
) )
extension_releases += repo_releases extension_releases += repo_releases
if "extensions" in manifest: for e in manifest.extensions:
for e in manifest["extensions"]: if e.id == ext_id:
if e["id"] == ext_id:
extension_releases += [ extension_releases += [
ExtensionRelease( ExtensionRelease(
name=e["name"], name=e.name,
version=e["version"], version=e.version,
archive=e["archive"], archive=e.archive,
hash=e["hash"], hash=e.hash,
source_repo=url, source_repo=url,
description=e["shortDescription"], description=e.short_description,
details_html=e.get("details"), details_html=e.details,
html_url=e.get("htmlUrl"), html_url=e.html_url,
) )
] ]
@ -400,6 +431,18 @@ class InstallableExtension(BaseModel):
return selected_release[0] if len(selected_release) != 0 else None return selected_release[0] if len(selected_release) != 0 else None
class GitHubRepo(BaseModel):
stargazers_count: str
html_url: str
default_branch: str
class ExtensionConfig(BaseModel):
name: str
short_description: str
tile: str
class InstalledExtensionMiddleware: class InstalledExtensionMiddleware:
# This middleware class intercepts calls made to the extensions API and: # This middleware class intercepts calls made to the extensions API and:
# - it blocks the calls if the extension has been disabled or uninstalled. # - it blocks the calls if the extension has been disabled or uninstalled.
@ -451,6 +494,11 @@ class CreateExtension(BaseModel):
source_repo: str source_repo: str
class Manifest(BaseModel):
extensions: List[ExplicitRelease] = []
repos: List[GitHubRelease] = []
def get_valid_extensions() -> List[Extension]: def get_valid_extensions() -> List[Extension]:
return [ return [
extension for extension in ExtensionManager().extensions if extension.is_valid extension for extension in ExtensionManager().extensions if extension.is_valid
@ -481,22 +529,42 @@ def icon_to_github_url(source_repo: str, path: Optional[str]) -> str:
return f"https://github.com/{source_repo}/raw/main/{tail}" return f"https://github.com/{source_repo}/raw/main/{tail}"
async def fetch_github_repo_info(org: str, repository: str): async def fetch_github_repo_info(
org: str, repository: str
) -> Tuple[GitHubRepo, GitHubRepoRelease, ExtensionConfig]:
repo_url = f"https://api.github.com/repos/{org}/{repository}" repo_url = f"https://api.github.com/repos/{org}/{repository}"
error_msg = "Cannot fetch extension repo" error_msg = "Cannot fetch extension repo"
repo = await gihub_api_get(repo_url, error_msg) repo = await gihub_api_get(repo_url, error_msg)
github_repo = GitHubRepo.parse_obj(repo)
lates_release_url = ( lates_release_url = (
f"https://api.github.com/repos/{org}/{repository}/releases/latest" f"https://api.github.com/repos/{org}/{repository}/releases/latest"
) )
error_msg = "Cannot fetch extension releases" error_msg = "Cannot fetch extension releases"
latest_release = await gihub_api_get(lates_release_url, error_msg) latest_release: Any = await gihub_api_get(lates_release_url, error_msg)
config_url = f"""https://raw.githubusercontent.com/{org}/{repository}/{repo["default_branch"]}/config.json""" config_url = f"https://raw.githubusercontent.com/{org}/{repository}/{github_repo.default_branch}/config.json"
error_msg = "Cannot fetch config for extension" error_msg = "Cannot fetch config for extension"
config = await gihub_api_get(config_url, error_msg) config = await gihub_api_get(config_url, error_msg)
return repo, latest_release, config return (
github_repo,
GitHubRepoRelease.parse_obj(latest_release),
ExtensionConfig.parse_obj(config),
)
async def fetch_manifest(url) -> Manifest:
error_msg = "Cannot fetch extensions manifest"
manifest = await gihub_api_get(url, error_msg)
return Manifest.parse_obj(manifest)
async def fetch_github_releases(org: str, repo: str) -> List[GitHubRepoRelease]:
releases_url = f"https://api.github.com/repos/{org}/{repo}/releases"
error_msg = "Cannot fetch extension releases"
releases = await gihub_api_get(releases_url, error_msg)
return [GitHubRepoRelease.parse_obj(r) for r in releases]
async def gihub_api_get(url: str, error_msg: Optional[str]) -> Any: async def gihub_api_get(url: str, error_msg: Optional[str]) -> Any: