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
20 changes: 6 additions & 14 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,26 +2,18 @@

## _v2.6.0_

### **Date: 15-June-2026**
### **Date: 22-June-2026**

- Dynamic endpoint resolution via new `Endpoint` class.
- Region-to-URL mapping is now loaded from a bundled `regions.json` (sourced from `artifacts.contentstack.com`) instead of hardcoded `if/elif` chains.
- Added `Endpoint.get_contentstack_endpoint(region, service, omit_https)` — resolves any supported region to its `contentDelivery`, `contentManagement`, or other service URL.
- Added `contentstack.get_contentstack_endpoint()` module-level proxy.
- `Stack` now auto-resolves `host` and `live_preview` management host via `Endpoint` on initialization.
- Added `scripts/download_regions.py` to pre-populate `regions.json` at install time.
- Runtime fallback: if `regions.json` is absent, the SDK downloads it live on first use.
## _v2.5.3_

### **Date: 08-June-2026**

- Fixed security issues.

## _v2.5.2_

### **Date: 18-May-2026**

- Bumped urllib3 in development requirements to address reported vulnerabilities.
- Bundled `contentstack/assets/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.
- Added `refresh_regions()` utility to programmatically download the latest regions manifest from the Contentstack CDN and overwrite the bundled `assets/regions.json` (`from contentstack import refresh_regions`).
- Added `python3 -m contentstack.region_refresh` CLI command for refreshing the registry after `pip install` (source-tree script `scripts/download_regions.py` is for contributors only).

## _v2.5.1_

Expand Down
4 changes: 3 additions & 1 deletion contentstack/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from .https_connection import HTTPSConnection
from contentstack.stack import Stack
from .utility import Utils
from .region_refresh import refresh_regions

__all__ = (
"Entry",
Expand All @@ -18,7 +19,8 @@
"Endpoint",
"HTTPSConnection",
"Stack",
"Utils"
"Utils",
"refresh_regions",
)


Expand Down
81 changes: 81 additions & 0 deletions contentstack/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/assets/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 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__), "assets", "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 assets 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/assets/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())
2 changes: 1 addition & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ filelock~=3.20.1
pluggy~=1.5.0
six~=1.16.0
packaging>=24.0
pytest==7.3.1
pytest>=8.1.0
dill~=0.3.8
pytz==2024.1
Babel==2.14.0
Expand Down
15 changes: 15 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,25 @@

try:
from setuptools import setup, find_packages
from setuptools.command.build_py import build_py
except ImportError:
from distutils.core import setup

package = "contentstack"


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.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()

def get_version(package):
"""
Return package version as listed in `__version__` in `init.py`.
Expand All @@ -32,6 +46,7 @@ def get_version(package):
]

setup(
cmdclass={"build_py": BuildPyWithRegions},
title="contentstack-python",
name="Contentstack",
status="Active",
Expand Down
Loading