Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,18 @@
---
## v1.10.0

#### Date: 08 June 2026
#### Date: 22 June 2026

- Dynamic region endpoint resolution via the Contentstack Regions Registry (`regions.json`).
- Added `Endpoint` class with 3-tier resolution: in-memory cache → bundled `data/regions.json` → live CDN download.
- Exposed `contentstack_management.get_contentstack_endpoint(region, service, omit_https)` module-level proxy.
- `Client` now resolves the `contentManagement` endpoint from the registry instead of a hardcoded host pattern.
- Added `scripts/download_regions.py` to refresh the bundled registry file.
- Bundled `contentstack_management/data/regions.json` included in `package_data` — always present after `pip install`.
- `setup.py` auto-refreshes `regions.json` at build time via a custom `BuildPyWithRegions` command; network failures warn but never block the build.
- Runtime fallback: if `regions.json` is absent, the SDK downloads it live on the first `Endpoint` call.
- New regions and services require no SDK code changes — registry update is sufficient.
- Added `refresh_regions()` utility to programmatically download the latest regions manifest from the Contentstack CDN and overwrite the bundled `data/regions.json` (`from contentstack_management import refresh_regions`).
- Added `python3 -m contentstack_management.region_refresh` CLI command for refreshing the registry after `pip install` (source-tree script `scripts/download_regions.py` is for contributors only).

---
## v1.9.0
Expand Down
4 changes: 3 additions & 1 deletion contentstack_management/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@
from .variants.variants import Variants
from .oauth.oauth_handler import OAuthHandler
from .oauth.oauth_interceptor import OAuthInterceptor
from .region_refresh import refresh_regions


__all__ = (
Expand Down Expand Up @@ -77,7 +78,8 @@
"VariantGroup",
"Variants",
"OAuthHandler",
"OAuthInterceptor"
"OAuthInterceptor",
"refresh_regions",
)

def get_contentstack_endpoint(region='us', service='', omit_https=False):
Expand Down
81 changes: 81 additions & 0 deletions contentstack_management/region_refresh.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
"""
Utility to pull the latest regions.json from the Contentstack CDN and
overwrite the bundled copy at contentstack_management/data/regions.json.

Exposed as a package-level function so tooling and CI pipelines can call it
programmatically instead of invoking the script directly:

from contentstack_management import refresh_regions
refresh_regions()
"""

import json
import os
import sys
import urllib.request

_REGIONS_URL = "https://artifacts.contentstack.com/regions.json"
_ASSET_PATH = os.path.join(os.path.dirname(__file__), "data", "regions.json")


def refresh_regions(
url: str = _REGIONS_URL,
dest: str = _ASSET_PATH,
*,
timeout: int = 30,
silent: bool = False,
) -> dict:
"""
Download the latest regions manifest from the Contentstack CDN and write
it to the bundled data file so all consumers get the update.

@param url - URL to fetch regions.json from (defaults to Contentstack CDN)
@param dest - Destination file path (defaults to contentstack_management/data/regions.json)
@param timeout - HTTP request timeout in seconds
@param silent - Suppress progress output when True
@returns The parsed regions dict on success
@raises RuntimeError on download failure, invalid JSON, or unexpected schema
"""
dest = os.path.normpath(dest)

if not silent:
print(f"Fetching {url} ...")

try:
with urllib.request.urlopen(url, timeout=timeout) as resp:
data = resp.read().decode("utf-8")
except Exception as exc:
raise RuntimeError(f"Could not download regions.json: {exc}") from exc

try:
decoded = json.loads(data)
except json.JSONDecodeError as exc:
raise RuntimeError(f"Downloaded content is not valid JSON: {exc}") from exc

if not isinstance(decoded, dict) or "regions" not in decoded:
raise RuntimeError("Downloaded JSON does not contain a 'regions' key.")

os.makedirs(os.path.dirname(dest), exist_ok=True)
with open(dest, "w", encoding="utf-8") as fh:
json.dump(decoded, fh, indent=2, ensure_ascii=False)
fh.write("\n")

region_count = len(decoded["regions"])
if not silent:
print(f"OK: Wrote {region_count} regions to {dest}")

return decoded


def _cli_main() -> int:
"""Entry point kept for backward compatibility with the scripts/ invocation."""
try:
refresh_regions()
return 0
except RuntimeError as exc:
print(f"ERROR: {exc}", file=sys.stderr)
return 1


if __name__ == "__main__":
sys.exit(_cli_main())
16 changes: 16 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,21 @@
import os
import re
import sys
from setuptools import setup, find_packages
from setuptools.command.build_py import build_py


class BuildPyWithRegions(build_py):
"""Fetch latest regions.json from Contentstack CDN before packaging."""

def run(self):
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
try:
from contentstack_management.region_refresh import refresh_regions
refresh_regions()
except Exception as exc:
print(f"WARNING: Could not refresh regions.json: {exc}", file=sys.stderr)
super().run()

with open("README.md", "r") as f:
long_description = f.read()
Expand Down Expand Up @@ -33,6 +48,7 @@ def get_author_email(package):
init_py, re.MULTILINE).group(1)

setup(
cmdclass={"build_py": BuildPyWithRegions},
name="contentstack-management",
version=get_version(package),
packages=find_packages(exclude=['tests']),
Expand Down
Loading