Skip to content

Add multiple selectable config profiles to get_interface - #121

Open
candytaco wants to merge 5 commits into
mainfrom
claude/keen-goldberg-pvVND
Open

candytaco wants to merge 5 commits into
mainfrom
claude/keen-goldberg-pvVND

Conversation

@candytaco

@candytaco candytaco commented Jun 5, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Add in multiple profiles in the config fig to specify different connections, and allows user to instantiate client objects based on the saved profile. The changes are backwards-compatible with existing config files.

cloud = cottoncandy.get_interface(profile='mylab')

A profile is a single [profile:NAME] section in the config file that can have any of the following entries:

  • access_key
  • secret_key
  • endpoint_url
  • default_bucket
  • signature_version
  • force_bucket_creation
  • backend
  • and (for backend = gdrive) secrets/credentials.

Anything a profile omits falls back to the base [login]/[basic]/[gdrive] sections, so this maintins compatibility with existing configs.

Profiles can inherit from another with the entry inherits; a child only specifies the keys it overrides:

[profile:mylab]
access_key = LABACCESSKEY
secret_key = LABSECRETKEY
endpoint_url = https://s3.example.edu/
default_bucket = lab-shared

[profile:mylab-scratch]
inherits = mylab
default_bucket = lab-scratch

lab-scratch will then use the same access key, secret key, and endpoint as mylab

Resolution / precedence for when configs are available at multiple places

In order of decreasing precedence:

  1. profile section entries
  2. profile parent entries (if any)
  3. default base section entries
  4. environment variables or other disk config files

Changes

  • cottoncandy/options.py: add logic to read profiles from config file
  • cottoncandy/__init__.py: get_interface() / get_browser() adds new argument profile= to allow user to specify config at instantiation
  • cottoncandy/defaults.cfg: adds in profiles
  • cottoncandy/tests/test_options.py: add unit tests
  • README.md: new "Profiles" section.

Testing

tests all pass

https://claude.ai/code/session_01UEkty8EbGpn3mWcwtcSwdw


Generated by Claude Code

cottoncandy previously supported a single set of connection settings
(keys, endpoint, bucket) split across the [login]/[basic]/[gdrive]
config sections. This adds named profiles so users can switch between
accounts/endpoints/buckets at instantiation time:

    cci = cc.get_interface(profile='mylab')

- options.py: add get_profile()/list_profiles() that resolve a
  [profile:NAME] section over the base sections, with `inherits` for
  profile inheritance (multi-level, with cycle detection).
- __init__.py: get_interface()/get_browser() accept profile=; the
  credential/bucket/endpoint/backend arguments default to None and are
  filled from the resolved profile, so explicit arguments still win.
- defaults.cfg: document profiles and add an explicit default backend key.
- tests: unit tests for profile resolution, inheritance, error cases, and
  get_interface precedence (no network).
- README: document profiles and inheritance.

Fully backward compatible: omitting profile= reproduces the current
behavior and existing configuration files keep working unchanged.

https://claude.ai/code/session_01UEkty8EbGpn3mWcwtcSwdw
Copilot AI review requested due to automatic review settings June 5, 2026 01:02

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request introduces configuration profiles to cottoncandy, allowing users to define named profiles with inheritance to easily switch between different accounts, endpoints, or buckets. The review feedback highlights several robust error-handling improvements regarding missing configuration values. Specifically, the reviewer recommends handling None values gracefully when checking for missing S3 credentials in get_interface and get_browser, providing fallback defaults for Google Drive secrets and credentials to avoid TypeError in os.path.join, and safely handling None values before passing them to string2bool.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment thread cottoncandy/__init__.py Outdated
Comment thread cottoncandy/__init__.py
Comment on lines +86 to +87
ACCESS_KEY = os.path.join(options.userdir, settings['secrets'])
SECRET_KEY = os.path.join(options.userdir, settings['credentials'])

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

high

If settings['secrets'] or settings['credentials'] is None (which can happen if they are missing from the configuration or profile), calling os.path.join with a None argument will raise a TypeError: join() argument must be str or bytes, not 'NoneType'. We should provide fallback defaults to prevent runtime crashes.

Suggested change
ACCESS_KEY = os.path.join(options.userdir, settings['secrets'])
SECRET_KEY = os.path.join(options.userdir, settings['credentials'])
secrets_file = settings['secrets'] or 'client_secrets.json'
creds_file = settings['credentials'] or 'credentials.txt'
ACCESS_KEY = os.path.join(options.userdir, secrets_file)
SECRET_KEY = os.path.join(options.userdir, creds_file)
References
  1. If a configuration key is missing and its absence implies that a dependent operation should be skipped, handle the missing key gracefully (e.g., by using a fallback value) to avoid execution errors.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

this shouldn't happen given these keys exist in the default config

Comment thread cottoncandy/__init__.py Outdated
Comment thread cottoncandy/__init__.py
Comment on lines +78 to +79
if force_bucket_creation is None:
force_bucket_creation = string2bool(settings['force_bucket_creation'])

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

If settings['force_bucket_creation'] is None (e.g., if it is missing from the configuration or profile), passing None to string2bool may raise an AttributeError if string2bool expects a string and calls .lower() on it. We should handle None gracefully by falling back to False.

Suggested change
if force_bucket_creation is None:
force_bucket_creation = string2bool(settings['force_bucket_creation'])
if force_bucket_creation is None:
val = settings['force_bucket_creation']
force_bucket_creation = string2bool(val) if val is not None else False
References
  1. If a configuration key is missing and its absence implies that a dependent operation should be skipped, handle the missing key gracefully (e.g., by using a fallback value) to avoid execution errors.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

this should happen given it should be in the default config

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR introduces named configuration profiles ([profile:NAME]) so callers can select connection identity (credentials/endpoint/bucket/backend) at get_interface() instantiation time, with optional multi-level inheritance and fallbacks to the existing base [login]/[basic]/[gdrive] sections.

Changes:

  • Add profile discovery and resolution (including inheritance + cycle detection) in cottoncandy.options.
  • Update get_interface() / get_browser() to accept profile= and resolve settings at call time (so explicit kwargs still override).
  • Add documentation + defaults updates and unit tests for profile behavior and precedence.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
README.md Documents the new profiles feature and inheritance syntax for end users.
cottoncandy/tests/test_options.py Adds unit tests for profile resolution, inheritance, error cases, and get_interface precedence.
cottoncandy/options.py Implements list_profiles() / get_profile() with inheritance chain resolution and cycle detection.
cottoncandy/defaults.cfg Documents profiles and sets an explicit default backend = s3 in [basic].
cottoncandy/init.py Adds profile= support to factory functions and defers config resolution to instantiation time.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread cottoncandy/__init__.py Outdated
Comment on lines +83 to +87
if ACCESS_KEY in [False, "False"] or SECRET_KEY in [False, "False"]:
ACCESS_KEY, SECRET_KEY = get_keys()
elif backend == 'gdrive':
ACCESS_KEY = os.path.join(options.userdir, options.config.get('gdrive', 'secrets'))
SECRET_KEY = os.path.join(options.userdir, options.config.get('gdrive', 'credentials'))
ACCESS_KEY = os.path.join(options.userdir, settings['secrets'])
SECRET_KEY = os.path.join(options.userdir, settings['credentials'])

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

this is the gdrive backend behavior, but also we don't really use the gdrive backend anymore

Comment thread cottoncandy/__init__.py
Comment thread cottoncandy/__init__.py Outdated
candytaco and others added 4 commits June 5, 2026 09:04
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>

This branch has not been deployed

No deployments
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.

3 participants