pos3console-script entry point withls,download,uploadsubcommands (#10).pos3 ls <prefix> [-r] [--profile NAME]lists objects, one fulls3://URL per line on stdout.pos3 download <url> [--local PATH] [--delete] [--exclude PATTERN]... [--profile NAME]prints only the resulting local path to stdout; progress and logs go to stderr, sodata_dir=$(pos3 download s3://bucket/dataset/)is safe.pos3 upload <url> [--local PATH] [--delete] [--exclude PATTERN]... [--profile NAME]is one-shot (no background loop or interval). Source defaults to the cache pathpos3 downloadwould have produced; errors if the source doesn't exist.--deletedefaults OFF for bothdownloadandupload(the Python API defaults toTrue; the CLI is more conservative for interactive use).--profileis supported alongside the URL forms3://<profile>@bucket/...; the URL form wins on conflict, matching the Python precedence.-n/--dry-runondownloadanduploadprints the planned per-file actions to stdout (inaws s3 sync --dryrunstyle) and performs no transfers, no deletes, and no directory creation.downloadanduploadrequire ans3://URL. The Python API's local-path passthrough still works in code; the CLI rejects non-S3 inputs with a clear error so a typo can't silently succeed.lsis unchanged and still accepts both forms.
pos3.TransferPlandataclass plus module-levelpos3.plan_download(remote, ...)andpos3.plan_upload(remote, ...)wrappers: compute the set of(source, destination)copies and target deletes a real call would perform, without performing any of them. Same calling pattern aspos3.download/pos3.upload— use them inside awith pos3.mirror():block. The CLI's-n/--dry-runis implemented on top of these.TransferErrorandTransferPlanare now inpos3.__all__.
- Per-object transfer failures now raise
pos3.TransferErrorinstead of being logged and swallowed. Previously, if any worker in a download or upload batch failed, the error was sent to the logger and the call returned normally —data_dir = pos3.download(...)could return a path to a partial cache, andpos3 downloadexited 0 after a failed S3 GET. Both now propagate. The new exception exposes.operationand.failures(list of the underlying per-worker exceptions). The CLI catches it and exits 1 with the failure on stderr. Background interval syncs (upload(..., interval=N)) are best-effort: aTransferErrorfrom one tick is logged and the daemon continues so the next interval can retry. Only the final sync on context exit and any one-shot call propagate. Cleanup on error — when themirror()body is unwinding with an exception, aTransferErrorfrom thesync_on_error=Truecleanup sync is logged but swallowed, so the original application exception remains the visible cause. pos3.mirror()no longer creates the cache root directory eagerly on context entry. The leaf directory is still created on demand when a file is actually downloaded, so the visible behavior ofdownload()is unchanged. Dry-run andplan_*paths are now genuinely side-effect free on the local filesystem.
- Explicit per-path S3 profile selection via the URL userinfo slot:
s3://<profile>@bucket/key(#8). A profile in the URL takes precedence over theprofile=argument and works for every pos3-powered CLI without code or env-var changes. - Name-keyed local profile registry auto-loaded from
~/.config/pos3/profiles.toml(override withPOS3_PROFILES_FILE). Non-secret config (endpoint/region/local_name/public) is kept separate from a secretcredentials_file. - Profiles with explicit credentials build their own isolated
boto3.Session, never reading or mutating the user's ambient AWS configuration. - Unknown profile names (in URL or argument) are a hard error with no silent fallback to the default credential chain.
- Python 3.11+ is now required (
requires-python = ">=3.11"). The TOML profile registry uses the standard-librarytomllib; the previously declared 3.9/3.10 support was never exercised by CI. - Profile logic (the
Profiledataclass, registry loading, resolution, and client creation) moved into the internalpos3.profilesmodule. The public API (pos3.Profile,pos3.register_profile) is unchanged.
- Fixed S3 key construction for directory markers being inconsistent across code paths. Consolidated all key building into a single
_make_s3_key(prefix, info)helper, ensuring trailing/is always appended for directories. Previously, the upload-copies path relied on a separate fixup in_put_to_s3, while the delete path had its own inline fix — a pattern that caused the v0.2.1 bug.
- Fixed S3 prefix matching bug where paths like
s3://bucket/data/would incorrectly match adjacent paths likes3://bucket/data_backup/. The_list_s3_objects()function now ensures directory prefixes always end with/to prevent spurious matches at path boundaries.
- Profile system for S3-compatible endpoints (MinIO, Nebius, etc.) (#4)
pos3.Profiledataclass for endpoint configurationpos3.register_profile()for named profile registrationprofileparameter ondownload(),upload(),sync(),ls()default_profileparameter onmirror()andwith_mirror()- Support for anonymous/public bucket access via
public=True - Multiple profiles can be used simultaneously in the same context
- Cache path isolation per profile via
local_name
- Initial extraction of
pos3from thepositroniccodebase as a standalone library. pos3.mirror()context manager for seamlessly syncing S3 files to local storage.- Drop-in compatibility for third-party scripts (e.g., OpenCV, Pandas) that require local file paths.
pos3.download(): Fetch S3 files to local cache with mirroring logic.pos3.upload(): Register local outputs for automatic background and exit-time synchronous upload.pos3.sync(): Bi-directional helper for resume-and-update workflows (download inputs -> run -> upload outputs).pos3.ls(): List files in S3 or local paths.- Thread-safe, differential transfer logic (only syncs changes).
- CLI and Python API support.