Skip to content
Open
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
9 changes: 5 additions & 4 deletions .github/workflows/check-version-bump.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,15 +9,15 @@ jobs:
check-version-bump:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
with:
fetch-depth: 0
- name: Validate version and changelog updates
shell: bash
run: |
set -euo pipefail

VERSION_FILE="setup.py"
VERSION_FILE="pyproject.toml"
INIT_FILE="contentstack_utils/__init__.py"
CHANGELOG_FILE="CHANGELOG.md"
BASE_SHA="${{ github.event.pull_request.base.sha }}"
Expand Down Expand Up @@ -60,15 +60,16 @@ jobs:
changed_file "$VERSION_FILE" || { echo "Version bump required in $VERSION_FILE."; exit 1; }
changed_file "$CHANGELOG_FILE" || { echo "Matching changelog update required in $CHANGELOG_FILE."; exit 1; }

head_version=$(python3 -c 'import re; c=open("setup.py").read(); m=re.search(r"version\s*=\s*[\"\x27]([^\"\x27]+)[\"\x27]", c); print(m.group(1) if m else "")')
head_version=$(python3 -c 'import re; c=open("pyproject.toml").read(); m=re.search(r"^version\s*=\s*\"([^\"]+)\"", c, re.M); print(m.group(1) if m else "")')
[ -n "$head_version" ] || { echo "::error::Could not read version from pyproject.toml."; exit 1; }
CHANGELOG_HEAD=$(sed -nE 's/^## v?([^[:space:]]+).*/\1/p' "$CHANGELOG_FILE" | head -1)

[ -n "$CHANGELOG_HEAD" ] || { echo "::error::Could not find a top changelog heading like '## vX.Y.Z' in $CHANGELOG_FILE."; exit 1; }
[ "$CHANGELOG_HEAD" = "$head_version" ] || { echo "::error::$CHANGELOG_FILE top version ($CHANGELOG_HEAD) does not match project version ($head_version)."; exit 1; }

if [ -f "$INIT_FILE" ]; then
init_head_version=$(python3 -c 'import re; c=open("contentstack_utils/__init__.py").read(); m=re.search(r"__version__\s*=\s*[\"\x27]([^\"\x27]+)[\"\x27]", c); print(m.group(1) if m else "")')
[ "$head_version" = "$init_head_version" ] || { echo "Version mismatch: setup.py ($head_version) must match $INIT_FILE ($init_head_version)."; exit 1; }
[ "$head_version" = "$init_head_version" ] || { echo "Version mismatch: pyproject.toml ($head_version) must match $INIT_FILE ($init_head_version)."; exit 1; }
fi

latest_tag=$(git tag --list 'v*' --sort=-version:refname | sed -n '1p')
Expand Down
53 changes: 53 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
name: Test

on:
pull_request:
push:
branches: [master, development]

permissions:
contents: read

jobs:
test:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
python-version:
- "3.6"
- "3.7"
- "3.8"
- "3.9"
- "3.10"
- "3.11"
- "3.12"
- "3.13"
steps:
- uses: actions/checkout@v6

- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v6
with:
python-version: ${{ matrix.python-version }}

- name: Install package and test dependencies
run: |
python -m pip install --upgrade "pip<25" "setuptools>=61" wheel
python -m pip install .
if python -c "import sys; sys.exit(0 if sys.version_info >= (3, 7) else 1)"; then
python -m pip install "pytest>=7,<9"
else
python -m pip install "pytest>=6.2,<7"
fi

- name: Fetch regions manifest for endpoint tests
run: |
python -c "from contentstack_utils.region_refresh import refresh_regions; refresh_regions(silent=True)"

- name: Verify import without undeclared deps
run: |
python -c "from contentstack_utils import Utils, Options; print('import ok')"

- name: Run tests
run: pytest tests/
23 changes: 23 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,28 @@
# Changelog

## v1.6.1 (2026-07-09)

### Bug fix: Declare runtime dependency on lxml

- Added `lxml` to `install_requires` with environment markers: `>=6.1.0` on
Python 3.8+ (matching dev `requirements.txt`), `>=4.9.0,<5` on 3.6–3.7.
`contentstack_utils.utils` imports `lxml.etree` at module load time, but
previous releases shipped with an empty `install_requires`, so
`pip install contentstack-utils` did not install lxml.

### Packaging and CI

- Migrated project metadata to `pyproject.toml` (PEP 621); kept a minimal
`setup.py` only for the `BuildPyWithRegions` build hook.
- Moved dev/test dependencies to `[project.optional-dependencies] dev`
(install with `pip install -e ".[dev]"`).
- Removed deprecated `setup_requires` / `tests_require` from setuptools config.
- Added a GitHub Actions test workflow covering Python 3.6–3.13.
- Extended Trove classifiers for Python 3.10–3.13 (still supports 3.6+).
- Configured pytest via `[tool.pytest]` in `pyproject.toml` (replaces deprecated
`[tool.pytest.ini_options]`).
- Added a **Development** section to the README for repository contributors.

## v1.6.0 (2026-06-22)

### New feature: Multi-region endpoint resolution
Expand Down
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -125,3 +125,12 @@ if result is not None and 'entries' in result:
option = Option()
GQL.json_to_html(item, ['paragraph_text'], option)
```

## Development

For contributors working on this repository:

```bash
pip install -e ".[dev]"
pytest
```
2 changes: 1 addition & 1 deletion contentstack_utils/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,6 @@
__title__ = 'contentstack_utils'
__author__ = 'contentstack'
__status__ = 'debug'
__version__ = '1.6.0'
__version__ = '1.6.1'
__endpoint__ = 'cdn.contentstack.io'
__contact__ = 'support@contentstack.com'
57 changes: 57 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
[build-system]
requires = ["setuptools>=61", "wheel"]
build-backend = "setuptools.build_meta"

[project]
name = "contentstack-utils"
version = "1.6.1"
description = "Utility package for Contentstack headless CMS with an API-first approach."
readme = "README.md"
license = { text = "MIT" }
authors = [{ name = "contentstack" }]
requires-python = ">=3.6"
dependencies = [
# lxml 6.x requires Python 3.8+; keep 4.x for 3.6–3.7 (requires-python>=3.6).
"lxml>=4.9.0,<5; python_version<'3.8'",
"lxml>=6.1.0; python_version>='3.8'",
]
classifiers = [
"License :: OSI Approved :: MIT License",
"Operating System :: OS Independent",
"Intended Audience :: Developers",
"Natural Language :: English",
"Programming Language :: Python :: 3",
"Programming Language :: Python :: 3.6",
"Programming Language :: Python :: 3.7",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
]
keywords = ["contentstack", "cms", "headless", "utilities"]
urls = { Homepage = "https://github.com/contentstack/contentstack-utils-python" }

[project.optional-dependencies]
dev = [
"pytest>=7.0.0",
"pytest-cov>=4.0.0",
"pytest-html>=4.2.0",
"ruff>=0.11.5",
"black>=24.3.0",
"flake8>=6.0.0",
"isort>=5.0.0",
]

[tool.setuptools.packages.find]
where = ["."]
include = ["contentstack_utils*"]

[tool.setuptools.package-data]
contentstack_utils = ["assets/regions.json"]

[tool.pytest]
testpaths = ["tests"]
python_files = ["test_*.py"]
addopts = "-ra"
45 changes: 9 additions & 36 deletions setup.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
"""Minimal setup.py for custom build hooks.

Project metadata lives in pyproject.toml (PEP 621). This file only registers
BuildPyWithRegions so sdist/wheel builds refresh regions.json from the CDN.
"""

import os
import sys

from setuptools import setup, find_packages
from setuptools import setup
from setuptools.command.build_py import build_py


Expand All @@ -12,45 +18,12 @@ def run(self):
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
try:
from contentstack_utils.region_refresh import refresh_regions

refresh_regions()
except Exception as exc:
# Never block a build over a network failure — warn and continue.
print(f"WARNING: Could not refresh regions.json: {exc}", file=sys.stderr)
super().run()


with open(os.path.join(os.path.dirname(__file__), 'README.md')) as readme:
long_description = readme.read()

setup(
name='contentstack_utils',
packages=find_packages(),
package_data={
"contentstack_utils": ["assets/regions.json"],
},
description="contentstack_utils is a Utility package for Contentstack headless CMS with an API-first approach.",
author='contentstack',
long_description=long_description,
long_description_content_type="text/markdown",
url="https://github.com/contentstack/contentstack-utils-python",
license='MIT',
version='1.6.0',
install_requires=[

],
setup_requires=['pytest-runner'],
tests_require=['pytest==4.4.1'],
test_suite='tests',
cmdclass={"build_py": BuildPyWithRegions},
classifiers=[
"License :: OSI Approved :: MIT License",
"Operating System :: OS Independent",
'Intended Audience :: Developers',
'Natural Language :: English',
'Programming Language :: Python :: 3.6',
'Programming Language :: Python :: 3.7',
'Programming Language :: Python :: 3.8',
'Programming Language :: Python :: 3.9',
],
python_requires='>=3.6',
)
setup(cmdclass={"build_py": BuildPyWithRegions})