Skip to content

allow temporary credential overrides with api calls for multi-cloud c…#446

Open
ojhilt wants to merge 1 commit into
cloudinary:masterfrom
ojhilt:per-call-config
Open

allow temporary credential overrides with api calls for multi-cloud c…#446
ojhilt wants to merge 1 commit into
cloudinary:masterfrom
ojhilt:per-call-config

Conversation

@ojhilt

@ojhilt ojhilt commented Jul 20, 2026

Copy link
Copy Markdown

…odebases

Brief Summary of Changes


Summary

Adds a cloudinary_config keyword argument to all SDK API functions (Admin API, Upload API, Provisioning API). When provided, its values are used as configuration overrides for that single call only. The global cloudinary.config() singleton is never mutated.

Closes #445


Motivation

The current SDK configuration model uses a module-level singleton (cloudinary._config). Any call to cloudinary.config(cloud_name=..., api_key=..., api_secret=...) overwrites this singleton in place. In a multi-threaded or async server handling requests for multiple Cloudinary accounts, concurrent requests can read each other's credentials, causing assets to be uploaded to the wrong account or authentication errors.


How it works

A new helper function consume_cloudinary_config(options) is added to cloudinary/utils.py. When called, it:

  1. Pops the cloudinary_config dict from the options dict (so it is never forwarded to the HTTP layer).
  2. Applies each key from cloudinary_config to options via setdefault — meaning explicit per-call options always win, and the global config is the final fallback.

This function is called at the entry point of every low-level API dispatch function:

Module Function(s)
cloudinary/api_client/call_api.py _call_api
cloudinary/api_client/call_account_api.py _call_account_api, _call_public_account_api, _execute_account_request
cloudinary/uploader.py upload, upload_large, upload_large_part, call_api, call_cacheable_api, _upload_large_part_with_auth_retry
cloudinary/utils.py sign_request, build_distribution_domain, base_api_url, build_upload_params

Usage

Upload API:

cloudinary.uploader.upload(
    "my_picture.jpg",
    cloudinary_config={
        "cloud_name": "tenant_cloud",
        "api_key":    "tenant_key",
        "api_secret": "tenant_secret",
    },
)

Admin API:

cloudinary.api.ping(cloudinary_config={
    "cloud_name": "tenant_cloud",
    "api_key":    "tenant_key",
    "api_secret": "tenant_secret",
})

Provisioning API:

cloudinary.provisioning.sub_accounts(cloudinary_config={
    "account_id":              "my_account_id",
    "provisioning_api_key":    "prov_key",
    "provisioning_api_secret": "prov_secret",
})

Any field accepted by cloudinary.config() (e.g. upload_prefix, secure, oauth_token) can be passed this way.


Changes

File Change
cloudinary/utils.py Add CLOUDINARY_CONFIG_OPTION constant and consume_cloudinary_config() helper; call it in sign_request, build_distribution_domain, base_api_url, build_upload_params
cloudinary/api_client/call_api.py Call consume_cloudinary_config at entry of _call_api
cloudinary/api_client/call_account_api.py Call consume_cloudinary_config at entry of each account API dispatch function
cloudinary/uploader.py Call consume_cloudinary_config at entry of upload and call helper functions
test/test_api_authorization.py Add test_temporary_config_admin_api and test_temporary_config_upload_api
test/test_provisioning_api.py Add test_temporary_config_account_api
README.md Document per-call configuration with examples

Testing

All new tests use the mocked urllib3 transport so no live credentials are required:

  • test_temporary_config_admin_api — verifies credentials and cloud_name from cloudinary_config are used in the Admin API request; asserts global config is unchanged after the call.
  • test_temporary_config_upload_api — verifies credentials, cloud_name, and upload_preset from cloudinary_config are applied in the Upload API request; asserts global config is unchanged.
  • test_temporary_config_account_api — verifies account_id, provisioning_api_key, provisioning_api_secret, and upload_prefix from cloudinary_config are applied in the Provisioning API request; asserts global account config is unchanged.
test/test_api_authorization.py::ApiAuthorizationTest::test_temporary_config_admin_api   PASSED
test/test_api_authorization.py::ApiAuthorizationTest::test_temporary_config_upload_api  PASSED
test/test_provisioning_api.py::AccountApiTemporaryConfigTest::test_temporary_config_account_api  PASSED

Backwards compatibility

Fully backwards compatible. The cloudinary_config key is only consumed when explicitly provided. All existing call signatures are unchanged. Global config behaviour is identical when cloudinary_config is not passed.

What does this PR address?

Are tests included?

  • Yes
  • No

Checklist:

  • My code follows the code style of this project.
  • My change requires a change to the documentation.
  • I ran the full test suite before pushing the changes and all the tests pass.

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.

cloudinary.config() is global state — per-call account configuration causes race conditions in multi-tenant servers

1 participant