Config file support and other fixes

- Write JSON every 100 iterations instead of 1000 iterations
- Write plan.json with indent=2
This commit is contained in:
taoky 2024-08-06 14:41:51 +08:00
parent a6358aad8d
commit c8a11d3d1f
3 changed files with 55 additions and 9 deletions

View File

@ -2,6 +2,8 @@
Shadowmire syncs PyPI (or plain HTTP(S) PyPI mirrors using Shadowmire) with a lightweight and easy approach. Shadowmire syncs PyPI (or plain HTTP(S) PyPI mirrors using Shadowmire) with a lightweight and easy approach.
Requires Python 3.11+.
## Docs ## Docs
### Background ### Background
@ -89,6 +91,12 @@ Verify command could be used if you believe that something is wrong (inconsisten
Verify command accepts same arguments as sync. Verify command accepts same arguments as sync.
If you don't like appending a long argument list, you could use `--config` ([example](./config.example.toml)):
```shell
./shadowmire.py --config config.toml sync
```
Also, if you need debugging, you could use `do-update` and `do-remove` command to operate on a single package. Also, if you need debugging, you could use `do-update` and `do-remove` command to operate on a single package.
## Acknowledgements ## Acknowledgements

9
config.example.toml Normal file
View File

@ -0,0 +1,9 @@
[options]
sync_packages = true
# shadowmire_upstream = http://example.com/pypi/web/
exclude = [
"[a-z]"
]
prerelease_exclude = [
".+"
]

View File

@ -15,6 +15,7 @@ from contextlib import contextmanager
import sqlite3 import sqlite3
from concurrent.futures import ThreadPoolExecutor, as_completed from concurrent.futures import ThreadPoolExecutor, as_completed
import signal import signal
import tomllib
import requests import requests
import click import click
from tqdm import tqdm from tqdm import tqdm
@ -481,7 +482,7 @@ class SyncBase:
logger.warning( logger.warning(
"%s generated an exception", package_name, exc_info=True "%s generated an exception", package_name, exc_info=True
) )
if idx % 1000 == 0: if idx % 100 == 0:
self.local_db.dump_json() self.local_db.dump_json()
except (ExitProgramException, KeyboardInterrupt): except (ExitProgramException, KeyboardInterrupt):
logger.info("Get ExitProgramException or KeyboardInterrupt, exiting...") logger.info("Get ExitProgramException or KeyboardInterrupt, exiting...")
@ -704,8 +705,8 @@ class SyncPlainHTTP(SyncBase):
package_simple_path = self.simple_dir / package_name package_simple_path = self.simple_dir / package_name
package_simple_path.mkdir(exist_ok=True) package_simple_path.mkdir(exist_ok=True)
if self.sync_packages: if self.sync_packages:
existing_hrefs = get_existing_hrefs(package_simple_path) hrefs = get_existing_hrefs(package_simple_path)
existing_hrefs = [] if existing_hrefs is None else existing_hrefs existing_hrefs = [] if hrefs is None else hrefs
# Download JSON meta # Download JSON meta
file_url = urljoin(self.upstream, f"/json/{package_name}") file_url = urljoin(self.upstream, f"/json/{package_name}")
success, resp = download( success, resp = download(
@ -781,9 +782,9 @@ def get_local_serial(package_meta_path: Path) -> Optional[int]:
def sync_shared_args(func): def sync_shared_args(func):
shared_options = [ shared_options = [
click.option( click.option(
"--sync-packages", "--sync-packages/--no-sync-packages",
is_flag=True, default=False,
help="Sync packages instead of just indexes", help="Sync packages instead of just indexes, by default it's --no-sync-packages",
), ),
click.option( click.option(
"--shadowmire-upstream", "--shadowmire-upstream",
@ -805,7 +806,33 @@ def sync_shared_args(func):
return func return func
def read_config(
ctx: click.Context, param: click.Option, filename: Optional[str]
) -> None:
if filename is None:
return
with open(filename, "rb") as f:
data = tomllib.load(f)
try:
options = dict(data["options"])
except KeyError:
options = {}
ctx.default_map = {
"sync": options,
"verify": options,
"do-update": options,
"do-remove": options,
}
@click.group() @click.group()
@click.option(
"--config",
type=click.Path(dir_okay=False),
help="Read option defaults from specified TOML file",
callback=read_config,
expose_value=False,
)
@click.pass_context @click.pass_context
def cli(ctx: click.Context) -> None: def cli(ctx: click.Context) -> None:
log_level = logging.DEBUG if os.environ.get("DEBUG") else logging.INFO log_level = logging.DEBUG if os.environ.get("DEBUG") else logging.INFO
@ -869,7 +896,7 @@ def sync(
plan = syncer.determine_sync_plan(local, excludes) plan = syncer.determine_sync_plan(local, excludes)
# save plan for debugging # save plan for debugging
with overwrite(basedir / "plan.json") as f: with overwrite(basedir / "plan.json") as f:
json.dump(plan, f, default=vars) json.dump(plan, f, default=vars, indent=2)
syncer.do_sync_plan(plan, prerelease_excludes) syncer.do_sync_plan(plan, prerelease_excludes)
syncer.finalize() syncer.finalize()
@ -922,8 +949,10 @@ def verify(
for package_name in plan.remove: for package_name in plan.remove:
# We only take the plan.remove part here # We only take the plan.remove part here
syncer.do_remove(package_name) syncer.do_remove(package_name)
logger.info("make sure all local indexes are valid, and (if --sync-packages) have valid local package files") logger.info(
"make sure all local indexes are valid, and (if --sync-packages) have valid local package files"
)
syncer.check_and_update(list(local_names)) syncer.check_and_update(list(local_names))
syncer.finalize() syncer.finalize()