Skip to content

Commit df9d2c7

Browse files
committed
refactor: replace typer with cyclopts
1 parent 8c72081 commit df9d2c7

5 files changed

Lines changed: 198 additions & 265 deletions

File tree

pyproject.toml

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -38,17 +38,13 @@ version = "2.0.3"
3838

3939
[project.optional-dependencies]
4040
cli = [
41-
"async-mega-py[default]",
42-
"typer-slim>=0.21.1"
43-
]
44-
default = [
45-
"python-dotenv>=1.2.1",
46-
"rich>=14.0.0"
41+
"cyclopts>=4.10.1",
42+
"python-dotenv>=1.2.1"
4743
]
4844

4945
[project.scripts]
50-
async-mega-py = "mega.cli:main"
51-
mega-py = "mega.cli:main"
46+
async-mega-py = "mega.__main__:app"
47+
mega-py = "mega.__main__:app"
5248

5349
[project.urls]
5450
Homepage = "https://github.com/NTFSvolume/mega.py"
@@ -131,6 +127,5 @@ dev = [
131127
"pytest-asyncio>=0.25.0",
132128
"pytest>=8.3.5",
133129
"rich>=14.0.0",
134-
"ruff>=0.15.0",
135-
"typer-slim>=0.21.1"
130+
"ruff>=0.15.0"
136131
]

src/mega/__main__.py

Lines changed: 146 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,147 @@
1-
from mega.cli import main
1+
from __future__ import annotations
22

3-
if __name__ == "__main__":
4-
main()
3+
import contextlib
4+
import json
5+
import logging
6+
from pathlib import Path
7+
from typing import TYPE_CHECKING, Annotated
8+
9+
import yarl
10+
from cyclopts import App, Parameter
11+
12+
from mega import __version__, env
13+
from mega.api import LOG_HTTP_TRAFFIC
14+
from mega.client import MegaNzClient
15+
from mega.transfer_it import TransferItClient
16+
from mega.utils import Site, setup_logger
17+
18+
if TYPE_CHECKING:
19+
from collections.abc import AsyncGenerator
20+
21+
22+
logger = logging.getLogger("mega")
23+
24+
Verbose = Annotated[
25+
int,
26+
Parameter(
27+
name=["-v", "--verbose"], count=True, help="Increase verbosity (-v shows debug logs, -vv shows HTTP traffic)"
28+
),
29+
]
30+
31+
32+
def parse_verbose(count: int) -> None:
33+
if count > 1:
34+
LOG_HTTP_TRAFFIC.set(True)
35+
36+
level = logging.DEBUG if count else logging.INFO
37+
setup_logger(level)
38+
39+
40+
app = App(
41+
help=(
42+
"CLI app for the [bold black]Mega.nz[/bold black] and [bold black]Transfer.it[/bold black].\n"
43+
f"Set [bold green]{env.EMAIL.name}[/bold green] and [bold green]{env.PASSWORD.name}[/bold green]\n"
44+
"enviroment variables to use them as credentials for Mega"
45+
),
46+
version=__version__,
47+
)
48+
CWD = Path.cwd()
49+
50+
51+
@contextlib.asynccontextmanager
52+
async def connect() -> AsyncGenerator[MegaNzClient]:
53+
async with MegaNzClient() as mega:
54+
await mega.login(env.EMAIL, env.PASSWORD)
55+
with mega.progress_bar:
56+
yield mega
57+
58+
59+
async def transfer_it(url: str, output_dir: Path) -> None:
60+
async with TransferItClient() as client:
61+
with client.progress_bar:
62+
transfer_id = client.parse_url(url)
63+
logger.info(f"Downloading '{url}'")
64+
results = await client.download_transfer(transfer_id, output_dir)
65+
logger.info(
66+
f"Download of '{url}' finished. Successful = {len(results.success)}, failed = {len(results.fails)}"
67+
)
68+
69+
70+
@app.command()
71+
async def download(url: str, output_dir: Path = CWD, *, verbose: Verbose = 0) -> None:
72+
"""Download a public file or folder by its URL (transfer.it / mega.nz)"""
73+
parse_verbose(verbose)
74+
site = Site(yarl.URL(url).origin())
75+
if site is Site.TRANSFER_IT:
76+
return await transfer_it(url, output_dir)
77+
78+
async with connect() as mega:
79+
parsed_url = mega.parse_url(url)
80+
if parsed_url.is_folder:
81+
await download_folder(mega, url, output_dir)
82+
else:
83+
await download_file(mega, url, output_dir)
84+
85+
86+
@app.command()
87+
async def dump(output_dir: Path = CWD, *, verbose: Verbose = 0) -> None:
88+
"""Dump a copy of your filesystem to disk"""
89+
parse_verbose(verbose)
90+
async with connect() as mega:
91+
fs = await mega.get_filesystem()
92+
out = output_dir / "filesystem.json"
93+
out.parent.mkdir(exist_ok=True)
94+
logger.info(f"Creating filesystem dump at '{out!s}'")
95+
out.write_text(json.dumps(fs.dump(), indent=2, ensure_ascii=False))
96+
97+
98+
@app.command()
99+
async def stats(*, verbose: Verbose = 0) -> None:
100+
"""Show account stats"""
101+
parse_verbose(verbose)
102+
async with connect() as mega:
103+
stats = await mega.get_account_stats()
104+
logger.info(f"Account stats for {env.EMAIL or 'TEMP ACCOUNT'}:")
105+
logger.info(stats.storage.dump())
106+
logger.info(stats.balance.dump())
107+
fs = await mega.get_filesystem()
108+
metrics = {root.attributes.name: stats.metrics[root.id] for root in (fs.root, fs.inbox, fs.trash_bin)}
109+
logger.info(metrics)
110+
111+
112+
@app.command()
113+
async def upload(file_path: Path, *, verbose: Verbose = 0) -> None:
114+
"""Upload a file to your account"""
115+
parse_verbose(verbose)
116+
async with connect() as mega:
117+
if not env.EMAIL:
118+
logger.warning("Files uploaded by a temp account can not be exported")
119+
120+
folder = await mega.create_folder("uploaded by mega.py")
121+
logger.info(f'Uploading "{file_path!s}"')
122+
file = await mega.upload(file_path, folder.id)
123+
path = (await mega.get_filesystem()).absolute_path(file.id)
124+
logger.info(f'File uploaded to your cloud. Path = "{path}"')
125+
if not env.EMAIL:
126+
return
127+
128+
link = await mega.export(file)
129+
logger.info(f'Public link for "{file_path!s}": {link}')
130+
131+
132+
async def download_file(mega: MegaNzClient, url: str, output: Path) -> None:
133+
public_handle, public_key = mega.parse_file_url(url)
134+
logger.info(f"Downloading {url}")
135+
path = await mega.download_public_file(public_handle, public_key, output)
136+
logger.info(f'Download of {url} finished. File save at "{path!s}"')
137+
138+
139+
async def download_folder(mega: MegaNzClient, url: str, output: Path) -> None:
140+
public_handle, public_key, root_node = mega.parse_folder_url(url)
141+
logger.info(f"Downloading {url}")
142+
results = await mega.download_public_folder(public_handle, public_key, output, root_node)
143+
logger.info(f"Download of '{url}' finished. Successful = {len(results.success)}, failed = {len(results.fails)}")
144+
145+
146+
if __name__ == "__main_":
147+
app()

src/mega/cli/__init__.py

Lines changed: 0 additions & 149 deletions
This file was deleted.

src/mega/cli/app.py

Lines changed: 0 additions & 59 deletions
This file was deleted.

0 commit comments

Comments
 (0)