From 5c4f30ef2cb94ea63e589e62443870b0026019e6 Mon Sep 17 00:00:00 2001 From: FZ2000 Date: Thu, 30 Jul 2026 22:35:33 -0700 Subject: [PATCH] Publish to PyPI as `da-sync`, not `da-cli` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit PyPI refuses `da-cli`: it is too similar to the unrelated `dacli` project, which the two differing only by a separator is precisely what its typosquat guard exists to catch. Worth recording, because it invalidates the check I had been relying on: `https://pypi.org/pypi/da-cli/json` returns **404**. The name is not taken. It is still rejected. Availability and acceptability are separate questions on PyPI, and only the pending-publisher form answers the second — so `tools/check_discoverability.py`-style 404 probes cannot clear a name on their own. `da-sync` instead: seven characters, says what the tool does, reads naturally with the `da` command, and differs from `dacli` by more than a separator, which is the specific thing that tripped the guard. THREE NAMES, THREE JOBS — this is the part worth being explicit about, because they are now deliberately different and that looks like a mistake if you do not know why: distribution (PyPI) da-sync what you `pip install` import package dacli what `import` resolves; unchanged console script da what you type; unchanged repository da-cli unchanged Only the first changed. Nothing about the installed tool moves. Verified by building and installing the wheel into a clean virtualenv rather than reasoning about it: built da_sync-0.1.0-py3-none-any.whl, da_sync-0.1.0.tar.gz da --version -> da-cli 0.1.0 import dacli -> ok, version 0.1.0 metadata -> name=da-sync version=0.1.0 The existing `dacli` on PyPI installs a top-level `dacli/` directory and a `dacli` console script. So our console script does not collide, but the import package would if both were installed into the same environment — pip does not detect two distributions writing the same directory. That is accepted deliberately: this is an application, not a library, nobody imports it, the README recommends pipx (which isolates per tool), and the two audiences barely overlap. The README's install section now names `da-sync` and warns about both neighbouring names rather than only one. tools/check_version_sync.py matches both `da-cli` and `da-sync` when it scans prose for a version that disagrees with the package — the first is still the HTTP User-Agent and the repository name, the second is now the distribution. --- README.md | 18 +++++++++++------- pyproject.toml | 10 ++++++++-- tools/check_version_sync.py | 9 +++++---- 3 files changed, 24 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index d8382eb..e1e9494 100644 --- a/README.md +++ b/README.md @@ -105,15 +105,19 @@ cd ~/Documents/da-cli ./install.sh # copies da + the dacli package to ~/.local/share/da-cli/ and symlinks ~/.local/bin/da → that copy ``` -**Option B — pip install.** Not available: **da-cli is not published to -PyPI**, and the name is currently unregistered. Use Option A. +**Option B — pip install.** Not published yet. Use Option A for now. -Do not `pip install da-cli` on the strength of this README. Until the name -appears here as published, anything under it on PyPI is not this project — -and since this package installs a `da` command onto your `PATH` and handles -DeviantArt OAuth tokens, installing an impostor is not a harmless mistake. +When it is published the distribution will be named **`da-sync`**, not +`da-cli`: -Note also that `dacli` (no hyphen) on PyPI is an unrelated project. +```bash +pipx install da-sync # the command is still `da` +``` + +Two names on PyPI are close to this project and are **not** it: `dacli` +(an unrelated data-engineering tool) and `da-cli` (unregistered). Since +this package installs a `da` command onto your `PATH` and handles +DeviantArt OAuth tokens, check the name before installing anything. Python 3.10+ required (uses `argparse.BooleanOptionalAction` and `X | None` syntax). No third-party runtime dependencies. diff --git a/pyproject.toml b/pyproject.toml index b0a0ad4..e94caa9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,11 @@ [project] -name = "da-cli" +# The PyPI distribution name. NOT the import name (that is `dacli`) and +# not the command (that is `da`). It is `da-sync` rather than `da-cli` +# because PyPI refuses `da-cli` as too similar to the unrelated `dacli` +# project — the two differ only by a separator, which is precisely what +# its typosquat guard exists to catch. Note the name being free is not +# sufficient: `da-cli` returns 404 and is still rejected. +name = "da-sync" description = "Sync DeviantArt galleries to local folders — zero-dependency Python CLI with OAuth 2.1 PKCE, a SQLite index, and scheduled macOS syncs." readme = "README.md" license = "MIT" @@ -68,7 +74,7 @@ Issues = "https://github.com/FZ2000/da-cli/issues" Changelog = "https://github.com/FZ2000/da-cli/blob/main/CHANGELOG.md" Documentation = "https://github.com/FZ2000/da-cli#readme" -# `pip install da-cli` exposes a `da` script on PATH. This is the +# `pip install da-sync` exposes a `da` script on PATH. This is the # pip-installable counterpart of the `da` shim in the repo root; the # shim still exists for the git-clone-and-symlink install path # (see install.sh) used in the README. diff --git a/tools/check_version_sync.py b/tools/check_version_sync.py index 51ee29f..92273d7 100644 --- a/tools/check_version_sync.py +++ b/tools/check_version_sync.py @@ -85,9 +85,10 @@ def problems() -> list[str]: f"docs/reference/cli.md says version {found.group(1)}; run tools/gen_cli_docs.py" ) - # Any other file quoting a DIFFERENT release version in prose. Scoped to - # `da-cli ` and `v` so it cannot trip on the pinned versions - # of third-party tools, which are unrelated and legitimately differ. + # Any other file quoting a DIFFERENT release version in prose. Matches + # both names this project answers to — `da-cli` is the HTTP User-Agent + # and the repo, `da-sync` is the PyPI distribution — and nothing else, + # so it cannot trip on the pinned versions of third-party tools. tracked = subprocess.run( ["git", "-C", str(REPO), "ls-files"], capture_output=True, text=True, check=False ).stdout.split() @@ -109,7 +110,7 @@ def problems() -> list[str]: for i, line in enumerate(body.splitlines(), 1): out.extend( f"{rel}:{i}: quotes da-cli {other}, but the package is {VERSION}" - for other in re.findall(r"\bda-cli[ /]v?(\d+\.\d+\.\d+)\b", line) + for other in re.findall(r"\b(?:da-cli|da-sync)[ /]v?(\d+\.\d+\.\d+)\b", line) if other != VERSION ) return out