cloudinary 1.44.4__tar.gz → 1.45.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (76) hide show
  1. {cloudinary-1.44.4/cloudinary.egg-info → cloudinary-1.45.0}/PKG-INFO +1 -1
  2. {cloudinary-1.44.4 → cloudinary-1.45.0}/cloudinary/__init__.py +1 -1
  3. {cloudinary-1.44.4 → cloudinary-1.45.0}/cloudinary/api_client/call_account_api.py +28 -7
  4. {cloudinary-1.44.4 → cloudinary-1.45.0}/cloudinary/api_client/execute_request.py +5 -5
  5. {cloudinary-1.44.4 → cloudinary-1.45.0}/cloudinary/provisioning/__init__.py +2 -1
  6. {cloudinary-1.44.4 → cloudinary-1.45.0}/cloudinary/provisioning/account.py +41 -1
  7. {cloudinary-1.44.4 → cloudinary-1.45.0/cloudinary.egg-info}/PKG-INFO +1 -1
  8. {cloudinary-1.44.4 → cloudinary-1.45.0}/pyproject.toml +1 -1
  9. {cloudinary-1.44.4 → cloudinary-1.45.0}/setup.py +1 -1
  10. {cloudinary-1.44.4 → cloudinary-1.45.0}/test/test_provisioning_api.py +92 -1
  11. {cloudinary-1.44.4 → cloudinary-1.45.0}/LICENSE.txt +0 -0
  12. {cloudinary-1.44.4 → cloudinary-1.45.0}/MANIFEST.in +0 -0
  13. {cloudinary-1.44.4 → cloudinary-1.45.0}/README.md +0 -0
  14. {cloudinary-1.44.4 → cloudinary-1.45.0}/cloudinary/api.py +0 -0
  15. {cloudinary-1.44.4 → cloudinary-1.45.0}/cloudinary/api_client/__init__.py +0 -0
  16. {cloudinary-1.44.4 → cloudinary-1.45.0}/cloudinary/api_client/call_api.py +0 -0
  17. {cloudinary-1.44.4 → cloudinary-1.45.0}/cloudinary/api_client/tcp_keep_alive_manager.py +0 -0
  18. {cloudinary-1.44.4 → cloudinary-1.45.0}/cloudinary/auth_token.py +0 -0
  19. {cloudinary-1.44.4 → cloudinary-1.45.0}/cloudinary/cache/__init__.py +0 -0
  20. {cloudinary-1.44.4 → cloudinary-1.45.0}/cloudinary/cache/adapter/__init__.py +0 -0
  21. {cloudinary-1.44.4 → cloudinary-1.45.0}/cloudinary/cache/adapter/cache_adapter.py +0 -0
  22. {cloudinary-1.44.4 → cloudinary-1.45.0}/cloudinary/cache/adapter/key_value_cache_adapter.py +0 -0
  23. {cloudinary-1.44.4 → cloudinary-1.45.0}/cloudinary/cache/responsive_breakpoints_cache.py +0 -0
  24. {cloudinary-1.44.4 → cloudinary-1.45.0}/cloudinary/cache/storage/__init__.py +0 -0
  25. {cloudinary-1.44.4 → cloudinary-1.45.0}/cloudinary/cache/storage/file_system_key_value_storage.py +0 -0
  26. {cloudinary-1.44.4 → cloudinary-1.45.0}/cloudinary/cache/storage/key_value_storage.py +0 -0
  27. {cloudinary-1.44.4 → cloudinary-1.45.0}/cloudinary/compat.py +0 -0
  28. {cloudinary-1.44.4 → cloudinary-1.45.0}/cloudinary/exceptions.py +0 -0
  29. {cloudinary-1.44.4 → cloudinary-1.45.0}/cloudinary/forms.py +0 -0
  30. {cloudinary-1.44.4 → cloudinary-1.45.0}/cloudinary/http_client.py +0 -0
  31. {cloudinary-1.44.4 → cloudinary-1.45.0}/cloudinary/models.py +0 -0
  32. {cloudinary-1.44.4 → cloudinary-1.45.0}/cloudinary/poster/__init__.py +0 -0
  33. {cloudinary-1.44.4 → cloudinary-1.45.0}/cloudinary/poster/encode.py +0 -0
  34. {cloudinary-1.44.4 → cloudinary-1.45.0}/cloudinary/poster/streaminghttp.py +0 -0
  35. {cloudinary-1.44.4 → cloudinary-1.45.0}/cloudinary/provisioning/account_config.py +0 -0
  36. {cloudinary-1.44.4 → cloudinary-1.45.0}/cloudinary/search.py +0 -0
  37. {cloudinary-1.44.4 → cloudinary-1.45.0}/cloudinary/search_folders.py +0 -0
  38. {cloudinary-1.44.4 → cloudinary-1.45.0}/cloudinary/static/cloudinary/html/cloudinary_cors.html +0 -0
  39. {cloudinary-1.44.4 → cloudinary-1.45.0}/cloudinary/static/cloudinary/js/canvas-to-blob.min.js +0 -0
  40. {cloudinary-1.44.4 → cloudinary-1.45.0}/cloudinary/static/cloudinary/js/jquery.cloudinary.js +0 -0
  41. {cloudinary-1.44.4 → cloudinary-1.45.0}/cloudinary/static/cloudinary/js/jquery.fileupload-image.js +0 -0
  42. {cloudinary-1.44.4 → cloudinary-1.45.0}/cloudinary/static/cloudinary/js/jquery.fileupload-process.js +0 -0
  43. {cloudinary-1.44.4 → cloudinary-1.45.0}/cloudinary/static/cloudinary/js/jquery.fileupload-validate.js +0 -0
  44. {cloudinary-1.44.4 → cloudinary-1.45.0}/cloudinary/static/cloudinary/js/jquery.fileupload.js +0 -0
  45. {cloudinary-1.44.4 → cloudinary-1.45.0}/cloudinary/static/cloudinary/js/jquery.iframe-transport.js +0 -0
  46. {cloudinary-1.44.4 → cloudinary-1.45.0}/cloudinary/static/cloudinary/js/jquery.ui.widget.js +0 -0
  47. {cloudinary-1.44.4 → cloudinary-1.45.0}/cloudinary/static/cloudinary/js/load-image.all.min.js +0 -0
  48. {cloudinary-1.44.4 → cloudinary-1.45.0}/cloudinary/templates/cloudinary_direct_upload.html +0 -0
  49. {cloudinary-1.44.4 → cloudinary-1.45.0}/cloudinary/templates/cloudinary_includes.html +0 -0
  50. {cloudinary-1.44.4 → cloudinary-1.45.0}/cloudinary/templates/cloudinary_js_config.html +0 -0
  51. {cloudinary-1.44.4 → cloudinary-1.45.0}/cloudinary/templatetags/__init__.py +0 -0
  52. {cloudinary-1.44.4 → cloudinary-1.45.0}/cloudinary/templatetags/cloudinary.py +0 -0
  53. {cloudinary-1.44.4 → cloudinary-1.45.0}/cloudinary/uploader.py +0 -0
  54. {cloudinary-1.44.4 → cloudinary-1.45.0}/cloudinary/utils.py +0 -0
  55. {cloudinary-1.44.4 → cloudinary-1.45.0}/cloudinary.egg-info/SOURCES.txt +0 -0
  56. {cloudinary-1.44.4 → cloudinary-1.45.0}/cloudinary.egg-info/dependency_links.txt +0 -0
  57. {cloudinary-1.44.4 → cloudinary-1.45.0}/cloudinary.egg-info/not-zip-safe +0 -0
  58. {cloudinary-1.44.4 → cloudinary-1.45.0}/cloudinary.egg-info/requires.txt +0 -0
  59. {cloudinary-1.44.4 → cloudinary-1.45.0}/cloudinary.egg-info/top_level.txt +0 -0
  60. {cloudinary-1.44.4 → cloudinary-1.45.0}/setup.cfg +0 -0
  61. {cloudinary-1.44.4 → cloudinary-1.45.0}/test/test_api.py +0 -0
  62. {cloudinary-1.44.4 → cloudinary-1.45.0}/test/test_api_authorization.py +0 -0
  63. {cloudinary-1.44.4 → cloudinary-1.45.0}/test/test_archive.py +0 -0
  64. {cloudinary-1.44.4 → cloudinary-1.45.0}/test/test_auth_token.py +0 -0
  65. {cloudinary-1.44.4 → cloudinary-1.45.0}/test/test_cloudinary_resource.py +0 -0
  66. {cloudinary-1.44.4 → cloudinary-1.45.0}/test/test_config.py +0 -0
  67. {cloudinary-1.44.4 → cloudinary-1.45.0}/test/test_expression_normalization.py +0 -0
  68. {cloudinary-1.44.4 → cloudinary-1.45.0}/test/test_http_client.py +0 -0
  69. {cloudinary-1.44.4 → cloudinary-1.45.0}/test/test_image.py +0 -0
  70. {cloudinary-1.44.4 → cloudinary-1.45.0}/test/test_metadata.py +0 -0
  71. {cloudinary-1.44.4 → cloudinary-1.45.0}/test/test_metadata_rules.py +0 -0
  72. {cloudinary-1.44.4 → cloudinary-1.45.0}/test/test_search.py +0 -0
  73. {cloudinary-1.44.4 → cloudinary-1.45.0}/test/test_streaming_profiles.py +0 -0
  74. {cloudinary-1.44.4 → cloudinary-1.45.0}/test/test_uploader.py +0 -0
  75. {cloudinary-1.44.4 → cloudinary-1.45.0}/test/test_utils.py +0 -0
  76. {cloudinary-1.44.4 → cloudinary-1.45.0}/test/test_video.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: cloudinary
3
- Version: 1.44.4
3
+ Version: 1.45.0
4
4
  Summary: Python and Django SDK for Cloudinary
5
5
  Author-email: Cloudinary <info@cloudinary.com>
6
6
  License: Released under the MIT license.
@@ -38,7 +38,7 @@ CL_BLANK = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAA
38
38
  URI_SCHEME = "cloudinary"
39
39
  API_VERSION = "v1_1"
40
40
 
41
- VERSION = "1.44.4"
41
+ VERSION = "1.45.0"
42
42
 
43
43
  _USER_PLATFORM_DETAILS = "; ".join((platform(), "Python {}".format(python_version())))
44
44
 
@@ -8,22 +8,43 @@ ACCOUNT_SUB_PATH = "accounts"
8
8
  _http = get_http_connector(account_config(), cloudinary.CERT_KWARGS)
9
9
 
10
10
 
11
+ # Account-scoped, authenticated call: provisioning/accounts/{account_id}/...
11
12
  def _call_account_api(method, uri, params=None, headers=None, **options):
12
- prefix = options.pop("upload_prefix",
13
- cloudinary.config().upload_prefix) or "https://api.cloudinary.com"
13
+ account_uri = [ACCOUNT_SUB_PATH, _account_id(options)] + uri
14
+ return _execute_account_request(method, account_uri, _account_auth(options),
15
+ params=params, headers=headers, **options)
16
+
17
+
18
+ # Public, unauthenticated call: provisioning/... with no account_id or credentials
19
+ def _call_public_account_api(method, uri, params=None, headers=None, **options):
20
+ return _execute_account_request(method, uri, {"anonymous": True},
21
+ params=params, headers=headers, **options)
22
+
23
+
24
+ def _account_id(options):
14
25
  account_id = options.pop("account_id", account_config().account_id)
15
26
  if not account_id:
16
27
  raise Exception("Must supply account_id")
28
+ return account_id
29
+
30
+
31
+ def _account_auth(options):
17
32
  provisioning_api_key = options.pop("provisioning_api_key", account_config().provisioning_api_key)
18
33
  if not provisioning_api_key:
19
34
  raise Exception("Must supply provisioning_api_key")
20
- provisioning_api_secret = options.pop("provisioning_api_secret",
21
- account_config().provisioning_api_secret)
35
+ provisioning_api_secret = options.pop("provisioning_api_secret", account_config().provisioning_api_secret)
22
36
  if not provisioning_api_secret:
23
37
  raise Exception("Must supply provisioning_api_secret")
24
- provisioning_api_url = "/".join(
25
- [prefix, cloudinary.API_VERSION, PROVISIONING_SUB_PATH, ACCOUNT_SUB_PATH, account_id] + uri)
26
- auth = {"key": provisioning_api_key, "secret": provisioning_api_secret}
38
+ return {"key": provisioning_api_key, "secret": provisioning_api_secret}
39
+
40
+
41
+ # Core transport: builds the provisioning URL and dispatches with the resolved auth.
42
+ # The API version can be overridden via the "api_version" option (defaults to cloudinary.API_VERSION).
43
+ def _execute_account_request(method, uri, auth, params=None, headers=None, **options):
44
+ prefix = options.pop("upload_prefix",
45
+ cloudinary.config().upload_prefix) or "https://api.cloudinary.com"
46
+ api_version = options.pop("api_version", cloudinary.API_VERSION)
47
+ provisioning_api_url = "/".join([prefix, api_version, PROVISIONING_SUB_PATH] + uri)
27
48
 
28
49
  return execute_request(http_connector=_http,
29
50
  method=method,
@@ -41,14 +41,14 @@ class Response(dict):
41
41
 
42
42
 
43
43
  def execute_request(http_connector, method, params, headers, auth, api_url, **options):
44
- # authentication
44
+ anonymous = auth.get("anonymous")
45
45
  key = auth.get("key")
46
46
  secret = auth.get("secret")
47
47
  oauth_token = auth.get("oauth_token")
48
- req_headers = urllib3.make_headers(
49
- user_agent=cloudinary.get_user_agent()
50
- )
51
- if oauth_token:
48
+ req_headers = urllib3.make_headers(user_agent=cloudinary.get_user_agent())
49
+ if anonymous:
50
+ pass
51
+ elif oauth_token:
52
52
  req_headers["authorization"] = "Bearer {}".format(oauth_token)
53
53
  else:
54
54
  req_headers.update(urllib3.make_headers(basic_auth="{0}:{1}".format(key, secret)))
@@ -1,5 +1,6 @@
1
1
  from .account_config import AccountConfig, account_config, reset_config
2
- from .account import (sub_accounts, create_sub_account, delete_sub_account, sub_account, update_sub_account,
2
+ from .account import (create_agent_account,
3
+ sub_accounts, create_sub_account, delete_sub_account, sub_account, update_sub_account,
3
4
  user_groups, create_user_group, update_user_group, delete_user_group, user_group,
4
5
  add_user_to_group, remove_user_from_group, user_group_users, user_in_user_groups,
5
6
  users, create_user, delete_user, user, update_user, access_keys, generate_access_key,
@@ -1,6 +1,7 @@
1
- from cloudinary.api_client.call_account_api import _call_account_api
1
+ from cloudinary.api_client.call_account_api import _call_account_api, _call_public_account_api, ACCOUNT_SUB_PATH
2
2
  from cloudinary.utils import encode_list
3
3
 
4
+ AGENTS_SUB_PATH = "agents"
4
5
  SUB_ACCOUNTS_SUB_PATH = "sub_accounts"
5
6
  USERS_SUB_PATH = "users"
6
7
  USER_GROUPS_SUB_PATH = "user_groups"
@@ -20,6 +21,45 @@ class Role(object):
20
21
  MEDIA_LIBRARY_USER = "media_library_user"
21
22
 
22
23
 
24
+ def create_agent_account(email, agent_framework, agent_llm_model, agent_goal, sdk_framework=None, **options):
25
+ """
26
+ Create a Cloudinary account on behalf of a human, intended for use by AI agents.
27
+
28
+ Creates a Free-plan account with a single, initially disabled product environment and sends a
29
+ verification email so the human can set a password and activate the account. The returned
30
+ credentials are inert until the email is verified.
31
+
32
+ This endpoint is public and unauthenticated, and is rate-limited per IP address.
33
+
34
+ :param email: The email address of the human on whose behalf the account is created.
35
+ A verification email is sent to this address.
36
+ :type email: str
37
+ :param agent_framework: The name of the agent framework used to create the account.
38
+ Must be between 2 and 100 characters.
39
+ :type agent_framework: str
40
+ :param agent_llm_model: The LLM model powering the agent. Must be between 2 and 100 characters.
41
+ :type agent_llm_model: str
42
+ :param agent_goal: A short description of what the agent is trying to achieve.
43
+ Must be between 2 and 300 characters.
44
+ :type agent_goal: str
45
+ :param sdk_framework: The Cloudinary SDK framework the agent intends to use.
46
+ Must be between 2 and 100 characters.
47
+ :type sdk_framework: str, optional
48
+ :param options: Generic advanced options dict, see online documentation
49
+ :type options: dict, optional
50
+ :return: The created agent account, including inert credentials for its
51
+ single product environment
52
+ :rtype: dict
53
+ """
54
+ uri = [AGENTS_SUB_PATH, ACCOUNT_SUB_PATH]
55
+ params = {"email": email,
56
+ "agent_framework": agent_framework,
57
+ "agent_llm_model": agent_llm_model,
58
+ "agent_goal": agent_goal,
59
+ "sdk_framework": sdk_framework}
60
+ return _call_public_account_api("POST", uri, params=params, **options)
61
+
62
+
23
63
  def sub_accounts(enabled=None, ids=None, prefix=None, **options):
24
64
  """
25
65
  List all sub accounts
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: cloudinary
3
- Version: 1.44.4
3
+ Version: 1.45.0
4
4
  Summary: Python and Django SDK for Cloudinary
5
5
  Author-email: Cloudinary <info@cloudinary.com>
6
6
  License: Released under the MIT license.
@@ -1,7 +1,7 @@
1
1
  [project]
2
2
  name = "cloudinary"
3
3
  description = "Python and Django SDK for Cloudinary"
4
- version = "1.44.4"
4
+ version = "1.45.0"
5
5
 
6
6
  authors = [{ name = "Cloudinary", email = "info@cloudinary.com" }]
7
7
  license = { file = "LICENSE.txt" }
@@ -7,7 +7,7 @@ if version_info[0] >= 3:
7
7
  else:
8
8
  # Following code is legacy (Python 2.7 compatibility) and will be removed in the future!
9
9
  # TODO: Remove in next major update (when dropping Python 2.7 compatibility)
10
- version = "1.44.4"
10
+ version = "1.45.0"
11
11
 
12
12
  with open('README.md') as file:
13
13
  long_description = file.read()
@@ -1,3 +1,4 @@
1
+ import json
1
2
  import unittest
2
3
  from datetime import datetime
3
4
 
@@ -8,7 +9,8 @@ import cloudinary.provisioning.account
8
9
  from cloudinary.provisioning import account_config, reset_config
9
10
  from cloudinary.exceptions import AuthorizationRequired, NotFound
10
11
 
11
- from test.helper_test import UNIQUE_SUB_ACCOUNT_ID, UNIQUE_TEST_ID
12
+ from test.helper_test import (UNIQUE_SUB_ACCOUNT_ID, UNIQUE_TEST_ID, URLLIB3_REQUEST, patch, api_response_mock,
13
+ get_uri, get_method, get_params, get_headers)
12
14
 
13
15
  disable_warnings()
14
16
 
@@ -85,6 +87,7 @@ class AccountApiTest(unittest.TestCase):
85
87
  @unittest.skipUnless(cloudinary.provisioning.account_config().provisioning_api_secret,
86
88
  "requires provisioning_api_key/provisioning_api_secret")
87
89
  def test_get_all_sub_accounts(self):
90
+
88
91
  res = cloudinary.provisioning.sub_accounts(True)
89
92
 
90
93
  sub_account_by_id = [sub_account for sub_account in res["sub_accounts"]
@@ -264,5 +267,93 @@ class AccountApiTest(unittest.TestCase):
264
267
  self.assertEqual("ok", named_key_del_res["message"])
265
268
 
266
269
 
270
+ class CreateAgentAccountTest(unittest.TestCase):
271
+ """
272
+ The create agent account endpoint is public, unauthenticated and rate limited per IP,
273
+ so it is verified against a mocked transport rather than the live API.
274
+ """
275
+
276
+ def test_create_agent_account(self):
277
+ with patch(URLLIB3_REQUEST) as mocker:
278
+ mocker.return_value = api_response_mock()
279
+ cloudinary.provisioning.create_agent_account(
280
+ "jane@example.com",
281
+ agent_framework="langchain",
282
+ agent_llm_model="claude-opus-4-8",
283
+ agent_goal="Build a product image gallery",
284
+ sdk_framework="python",
285
+ )
286
+
287
+ self.assertEqual("POST", get_method(mocker))
288
+ self.assertTrue(get_uri(mocker).endswith("/provisioning/agents/accounts"))
289
+
290
+ params = get_params(mocker)
291
+ self.assertEqual("jane@example.com", params["email"])
292
+ self.assertEqual("langchain", params["agent_framework"])
293
+ self.assertEqual("claude-opus-4-8", params["agent_llm_model"])
294
+ self.assertEqual("Build a product image gallery", params["agent_goal"])
295
+ self.assertEqual("python", params["sdk_framework"])
296
+
297
+ def test_create_agent_account_is_unauthenticated(self):
298
+ with patch(URLLIB3_REQUEST) as mocker:
299
+ mocker.return_value = api_response_mock()
300
+ cloudinary.provisioning.create_agent_account(
301
+ "jane@example.com",
302
+ agent_framework="langchain",
303
+ agent_llm_model="claude-opus-4-8",
304
+ agent_goal="Build a product image gallery",
305
+ )
306
+
307
+ # The endpoint is public - no authorization header must be sent.
308
+ headers = get_headers(mocker)
309
+ self.assertNotIn("authorization", {k.lower() for k in headers})
310
+
311
+ def test_create_agent_account_omits_unset_sdk_framework(self):
312
+ with patch(URLLIB3_REQUEST) as mocker:
313
+ mocker.return_value = api_response_mock()
314
+ cloudinary.provisioning.create_agent_account(
315
+ "jane@example.com",
316
+ agent_framework="langchain",
317
+ agent_llm_model="claude-opus-4-8",
318
+ agent_goal="Build a product image gallery",
319
+ )
320
+
321
+ self.assertNotIn("sdk_framework", get_params(mocker))
322
+
323
+ def test_create_agent_account_parses_response(self):
324
+ body = json.dumps({
325
+ "external_id": "0aaaaa1bbbbb2ccccc3ddddd4eeeee5f",
326
+ "email": "jane@example.com",
327
+ "plan_name": "free",
328
+ "product_environments": [{
329
+ "external_id": "abcde1fghij2klmno3pqrst4uvwxy5z",
330
+ "cloud_name": "product1",
331
+ "api_key": "123456789012345",
332
+ "api_secret": "asdf1JKL2xyz3ABc4s3c5reT01DfaKez",
333
+ "api_environment_variable":
334
+ "CLOUDINARY_URL=cloudinary://123456789012345:asdf1JKL2xyz3ABc4s3c5reT01DfaKez@product1",
335
+ }],
336
+ "guidance": "A verification email has been sent to the supplied email address.",
337
+ })
338
+ with patch(URLLIB3_REQUEST) as mocker:
339
+ mocker.return_value = api_response_mock(body)
340
+ res = cloudinary.provisioning.create_agent_account(
341
+ "jane@example.com",
342
+ agent_framework="langchain",
343
+ agent_llm_model="claude-opus-4-8",
344
+ agent_goal="Build a product image gallery",
345
+ )
346
+
347
+ self.assertEqual("free", res["plan_name"])
348
+ self.assertEqual("jane@example.com", res["email"])
349
+ self.assertEqual(1, len(res["product_environments"]))
350
+ product_environment = res["product_environments"][0]
351
+ self.assertEqual("product1", product_environment["cloud_name"])
352
+ self.assertEqual("123456789012345", product_environment["api_key"])
353
+ self.assertEqual("asdf1JKL2xyz3ABc4s3c5reT01DfaKez", product_environment["api_secret"])
354
+ self.assertIn("CLOUDINARY_URL=cloudinary://", product_environment["api_environment_variable"])
355
+ self.assertIn("guidance", res)
356
+
357
+
267
358
  if __name__ == '__main__':
268
359
  unittest.main()
File without changes
File without changes
File without changes
File without changes