Skip to content

Add profile registry and URL-based profile selection - #9

Merged
vertix merged 9 commits into
mainfrom
claude/implement-issue-8-JQUlr
May 20, 2026
Merged

Add profile registry and URL-based profile selection#9
vertix merged 9 commits into
mainfrom
claude/implement-issue-8-JQUlr

Conversation

@vertix

@vertix vertix commented May 20, 2026

Copy link
Copy Markdown
Contributor

Closes #8.

Summary

This PR adds support for named S3 profiles with a local registry file and URL-based profile selection, enabling users to work with S3-compatible endpoints without environment variables or code changes.

Key Changes

  • New pos3.profiles module: Extracted profile management logic into a dedicated internal module with support for:

    • Profile registry auto-loaded from ~/.config/pos3/profiles.toml (configurable via POS3_PROFILES_FILE or XDG_CONFIG_HOME)
    • Lazy loading of profiles on first use with thread-safe initialization
    • Credentials file support (AWS-style INI format) for keeping secrets separate from config
    • Isolated boto3.Session creation for profiles with explicit credentials
  • URL-based profile selection: S3 URLs can now specify a profile in the userinfo slot (s3://<profile>@bucket/key):

    • Profile name is extracted and takes precedence over the profile= argument
    • Unknown profiles are hard errors with no silent fallback
    • The profile selector is stripped before passing to boto3
  • Enhanced Profile dataclass: Added support for explicit AWS credentials (access_key, secret_key, session_token) that enable isolated session creation without reading ambient AWS configuration

  • Updated _parse_s3_url and _normalize_s3_url: Now strip the userinfo slot (profile selector) from URLs before processing bucket/key

  • Updated _effective_profile method: Now resolves URL-based profile selection with proper precedence (URL > argument > context default)

  • Public API re-exports: Profile, register_profile, and related functions are re-exported from pos3/__init__.py for backward compatibility

Implementation Details

  • Profile registry uses tomllib (Python 3.11+), requiring a minimum Python version bump from 3.9 to 3.11
  • Registry loading is guarded by a lock to ensure thread-safe single initialization per process
  • Programmatically registered profiles take precedence over registry file entries
  • Credentials are excluded from Profile.__repr__() to prevent accidental logging of secrets
  • Each profile with explicit credentials creates its own isolated boto3 session, never reading or mutating the user's ambient AWS configuration

Testing

Added comprehensive test coverage for:

  • URL profile extraction and parsing
  • Registry loading and lazy initialization
  • Credentials file handling
  • Profile precedence (URL > argument > default)
  • Error handling for unknown profiles and missing configuration

https://claude.ai/code/session_011QGQRbcxj9d47BRWkWAWM1

claude added 3 commits May 19, 2026 20:00
Support `s3://<profile>@bucket/key` to select an isolated, name-keyed
profile directly in the URL, so pos3-powered CLIs can target custom
S3-compatible endpoints with scoped credentials without touching global
AWS state or threading a profile= argument through every tool.

- URL userinfo profile is parsed in the _parse_s3_url chokepoint and
  takes precedence over the profile= argument.
- Profiles auto-load from ~/.config/pos3/profiles.toml (overridable via
  POS3_PROFILES_FILE / XDG_CONFIG_HOME), with the secret credentials
  file kept separate from the non-secret endpoint/region config.
- Credentialed profiles build their own isolated boto3.Session.
- Unknown profiles are a hard error with no silent default-chain fallback.
- Version bump to 0.3.0 so downstream tools can pin the new behavior.

https://claude.ai/code/session_011QGQRbcxj9d47BRWkWAWM1
CI only ever runs 3.11 and release.yml already uses stdlib tomllib, so
the >=3.9 claim was nominal and untested. Drop the conditional tomli
dependency, import tomllib directly, and align requires-python and the
ruff target-version with what is actually supported.

https://claude.ai/code/session_011QGQRbcxj9d47BRWkWAWM1
Move the Profile dataclass, registry loading, resolution, and isolated
client creation out of __init__.py into a self-contained pos3.profiles
module (no dependency back on the mirror engine). __init__.py is now
just the sync logic. Public API (pos3.Profile, pos3.register_profile)
is unchanged via re-export. Also document the Python 3.11+ requirement
in the release notes.

https://claude.ai/code/session_011QGQRbcxj9d47BRWkWAWM1

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 13476dbcd5

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread pos3/profiles.py
Comment thread pos3/profiles.py Outdated
- Require both aws_access_key_id and aws_secret_access_key in a
  credentials_file. A half-populated section previously silently fell
  back to the ambient AWS credential chain in _create_s3_client,
  defeating the point of isolated per-profile credentials.
- Build registry profiles atomically and set _REGISTRY_LOADED only
  after a successful load, so a malformed entry doesn't leave the
  process in a permanent "loaded with no profiles" state.

Adds regression tests for both.

https://claude.ai/code/session_011QGQRbcxj9d47BRWkWAWM1
@vertix

vertix commented May 20, 2026

Copy link
Copy Markdown
Contributor Author

@codex review

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0d8681cba2

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread pos3/profiles.py Outdated
A relative path like credentials_file = "acme.creds" previously resolved
against the process CWD, so running pos3 from a different directory
would fail with "not found or unreadable" even though the creds file
sat next to profiles.toml. Resolve relative paths against the registry
file's parent instead, which is the location users naturally expect.
Absolute and ~-expanded paths are unchanged.

https://claude.ai/code/session_011QGQRbcxj9d47BRWkWAWM1

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c0c8377252

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread pos3/profiles.py Outdated
s3://@bucket/key or s3://:token@bucket/key (which a CLI template
variable can produce when the profile name expands to empty) previously
slipped through _url_profile as "no URL profile" and silently fell back
to the argument/default profile, violating the explicit no-silent-
fallback contract for URL-based selection. Treat any '@' in the netloc
without a profile name as a hard error.

https://claude.ai/code/session_011QGQRbcxj9d47BRWkWAWM1

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: be68460e27

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread pos3/profiles.py Outdated
Falling back to parser.sections()[0] would silently bind a profile to
an unrelated INI section, so a typo in the section header (e.g. [acne]
instead of [acme]) could route operations to the wrong AWS account.
Only [<profile_name>] and [default] are accepted now; anything else
raises with the list of sections found. Same no-silent-fallback rule
as the earlier credentials/empty-selector guards.

https://claude.ai/code/session_011QGQRbcxj9d47BRWkWAWM1

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6ddc4d2a93

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread pos3/profiles.py
Comment thread pos3/profiles.py
- Programmatic register_profile() must take precedence over a registry
  entry regardless of which loaded first. Previously, lazy registry
  load could populate _PROFILES before user code, causing a later
  register_profile() with different config to raise. Split storage:
  code registrations live in _PROFILES (consulted first); registry
  entries live in _REGISTRY_PROFILES (fallback). Both override the
  registry, neither overrides another code registration.
- A Profile constructed with only one of access_key/secret_key
  previously slipped through _create_s3_client and fell back to the
  ambient credential chain. Validate the pair in Profile.__post_init__
  so any wrong-shape Profile fails at construction, no matter the
  caller path.

https://claude.ai/code/session_011QGQRbcxj9d47BRWkWAWM1

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: a5f777e794

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread pos3/profiles.py Outdated
A forced _load_profile_registry(..., force=True) previously merged new
entries into _REGISTRY_PROFILES, so profiles that were removed or
renamed in the updated profiles.toml stayed resolvable with their old
endpoint/credential config. Treat a forced reload as "re-read the
source of truth": clear and replace, including when the file is now
absent. The clear runs only after the new mapping is successfully
built, so a malformed reload leaves the previous snapshot intact.

https://claude.ai/code/session_011QGQRbcxj9d47BRWkWAWM1
@vertix
vertix merged commit d2dac27 into main May 20, 2026
2 checks passed
@vertix
vertix deleted the claude/implement-issue-8-JQUlr branch May 20, 2026 09:46
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Explicit per-path S3 profile selection for custom/isolated endpoints

2 participants