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
135 changes: 135 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
name: Release

on:
push:
tags: ['v*']

permissions: {}

jobs:
lint:
name: Lint
runs-on: ubuntu-24.04
permissions:
contents: read
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: "3.14"
- uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ hashFiles('setup.py') }}
restore-keys: |
${{ runner.os }}-pip-
${{ runner.os }}-
- run: |
pip install flake8
flake8 ./chartmogul

test:
name: Test (Python ${{ matrix.python-version }})
runs-on: ubuntu-24.04
permissions:
contents: read
strategy:
fail-fast: true
matrix:
python-version: ["3.10", "3.11", "3.12", "3.13", "3.14"]
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: ${{ matrix.python-version }}
- uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
with:
path: ~/.cache/pip
key: ${{ runner.os }}-pip-${{ hashFiles('setup.py') }}
restore-keys: |
${{ runner.os }}-pip-
${{ runner.os }}-
- run: |
python -m pip install --upgrade pip setuptools wheel
pip install -e .[testing]
- run: python -m unittest

build:
name: Build
needs: [lint, test]
runs-on: ubuntu-24.04
permissions:
contents: read
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false

- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: "3.14"

- name: Verify version matches tag
run: |
TAG_VERSION="${GITHUB_REF_NAME#v}"
PKG_VERSION=$(python -c "import re; print(re.search(r\"^__version__\s*=\s*['\\\"]([^'\\\"]*)['\\\"]\" , open('chartmogul/version.py').read(), re.MULTILINE).group(1))")
if [ "$TAG_VERSION" != "$PKG_VERSION" ]; then
echo "::error::Tag version ($TAG_VERSION) does not match package version ($PKG_VERSION)"
exit 1
fi

- name: Build sdist and wheel
run: |
python -m pip install --upgrade build
python -m build

- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: dist
path: dist/
if-no-files-found: error
retention-days: 5

publish:
name: Publish to PyPI
needs: [build]
runs-on: ubuntu-24.04
permissions:
id-token: write
environment:
name: pypi
url: https://pypi.org/project/chartmogul/
steps:
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: dist
path: dist/

- uses: pypa/gh-action-pypi-publish@cef221092ed1bacb1cc03d23a2d87d1d172e277b # v1.14.0
with:
attestations: true

release:
name: GitHub Release
needs: [build]
runs-on: ubuntu-24.04
permissions:
contents: write
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false

- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
name: dist
path: dist/

- name: Create GitHub Release
env:
GH_TOKEN: ${{ github.token }}
run: gh release create "$GITHUB_REF_NAME" dist/* --generate-notes
6 changes: 3 additions & 3 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name: Run specs and generate Code Climate report
name: Test
on:
push:
branches: [ main ]
Expand Down Expand Up @@ -54,7 +54,7 @@ jobs:
${{ runner.os }}-
- name: Install dependencies
run: |
python -m pip install --upgrade pip setuptools wheel coverage
python -m pip install --upgrade pip setuptools wheel
pip install -e .[testing]
- name: Run tests
run: coverage run -m unittest && coverage xml -i --include='chartmogul/*'
run: python -m unittest
87 changes: 1 addition & 86 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,88 +1,3 @@
# Changelog

All notable changes to this project will be documented in this file.

The format is based on [Keep a Changelog],
and this project adheres to [Semantic Versioning].

[Keep a Changelog]: https://keepachangelog.com/en/1.0.0/
[Semantic Versioning]: https://semver.org/spec/v2.0.0.html

## [4.9.0] - 2026-03-16
- Add `external_id` key to Contact model

## [4.8.0] - 2026-01-06
- Mention filters (CFL) param for metrics api
- Add Customer.subscriptions
- Update CustomerSubscription.list_imported to deprecated
- Add disabled, disabled_at, edit_history_summary and errors fields to Invoice

## [4.7.0] - 2025-12-22
- Add fetching additional DataSource fields

## [4.6.4] - 2025-11-24
- Add Subscription Set ID key to Activity

## [4.6.3] - 2025-09-01
- Remove future dependency to resolve vulnerability issues

## [4.6.2] - 2025-07-09
- Update Marshmallow dependency to use >=3.24.0

## [4.6.1] - 2025-05-19
- Fixed Tasks API schema issue
- Unify requirements in a single place
- Support for Python 3.13.

## [4.6.0] - 2025-04-25
- Adds support for Tasks (https://dev.chartmogul.com/reference/tasks)

## [4.5.1] - 2025-04-07
- Update urllib3 dependency to use >=2.2.3 to allow for future minor updates

## [4.5.0] - 2025-03-18
- Adds support for disconnecting subscriptions
- Adds support for transaction fees to transactions

## [4.4.0] - 2024-10-24
- Adds support for unmerging customers

## [4.3.2] - 2024-06-26
- Remove VCR dependencies
- Replaced unit tests that depended on VCR using `request_mock`
- Updated urllib3 to latest secure version

## [4.3.1] - 2024-06-20
- Update the urllib3 dependency to a secure version

## [4.3.0] - 2024-03-25
- Adds support for Opportunities (https://dev.chartmogul.com/reference/opportunities)

## [4.2.1] - 2024-01-15
- Fix customer website_url, add missing allow_none=True

## [4.2.0] - 2024-01-08
- Add support for customer website_url

## [4.1.1] - 2023-12-21
- Fix missing customer_uuid when creating a note from a customer

## [4.1.0] - 2023-12-20
- Support customer notes

## [4.0.0] - 2023-10-04

### Added
- v4.0.0 upgrade instructions.
- Support for Python 3.12.

### Removed
- Support for old pagination using `page` query params.
- Deprecated `imp` module.
- Support for Python 3.7.

## [3.1.3] - 2023-09-27

### Added
- Support for cursor based pagination to `.all()` endpoints.
- Changelog.
Release notes are auto-generated from merged pull request titles and published on the [GitHub Releases page](https://github.com/chartmogul/chartmogul-python/releases).
22 changes: 13 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<p align='center'><code>chartmogul-python</code> provides convenient Python bindings for <a href='https://dev.chartmogul.com'>ChartMogul's API</a>.</p>
<p align='center'>
<a href="https://badge.fury.io/py/chartmogul"><img src="https://badge.fury.io/py/chartmogul.svg" alt="PyPI version" height="18"></a>
<a href='https://travis-ci.org/chartmogul/chartmogul-python'><img src='https://travis-ci.org/chartmogul/chartmogul-python.svg?branch=main' alt='Build Status'/></a>
<a href='https://github.com/chartmogul/chartmogul-python/actions/workflows/test.yml'><img src='https://github.com/chartmogul/chartmogul-python/actions/workflows/test.yml/badge.svg?branch=main' alt='Build Status'/></a>
</p>
<hr>

Expand All @@ -22,6 +22,8 @@
|
<b><a href='#contributing'>Contributing</a></b>
|
<b><a href='#security'>Security</a></b>
|
<b><a href='#license'>License</a></b>
</p>
<hr>
Expand Down Expand Up @@ -470,15 +472,17 @@ Bug reports and pull requests are welcome on GitHub at https://github.com/chartm

## Releasing

Make sure that:
1. you have prepared `~/.pypirc` with credentials,
2. a higher version has been set in `chartmogul/__init__.py`,
3. Run tests `python3 -m unittest`
4. Build package `python3 setup.py sdist`
4. release works `twine upload --repository-url https://test.pypi.org/legacy/ dist/*`,
5. release to production `twine upload dist/*`,
See [RELEASING.md](RELEASING.md) for the full release process and security details.

## Security

### Verifying Releases

All releases of this library are published as [immutable GitHub Releases](https://github.com/chartmogul/chartmogul-python/releases) with protected tags and as a package on [PyPI](https://pypi.org/project/chartmogul/).

[Read full HOWTO](http://peterdowns.com/posts/first-time-with-pypi.html)
To maximize supply chain security:
- **Commit your lock file** to version control — both `uv.lock` and `requirements.txt` record package hashes automatically
- **Use [uv](https://docs.astral.sh/uv/)** for hash-verified, reproducible installs

## License

Expand Down
85 changes: 85 additions & 0 deletions RELEASING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
# Releasing chartmogul-python

## Prerequisites

- You must have push access to the repository
- `git`, `gh`, `jq`, and `python3` must be installed
- Tags matching `v*` are protected by GitHub tag protection rulesets
- Releases are immutable once published (GitHub repository setting)
- PyPI [trusted publishing](https://docs.pypi.org/trusted-publishers/) must be configured for the package (see [Repository Settings](#repository-settings-admin))

## Release Process

Run the release script from the repository root:

```sh
bin/release.sh <patch|minor|major>
```

The script will:

1. Verify prerequisites and that CI is green on `main`
2. Show any open PRs targeting `main` and ask for confirmation
3. Show PRs merged since the last tag (what's being released) and ask for confirmation
4. Bump the version in `chartmogul/version.py`
5. Create a release branch, commit, push, and open a PR
6. Wait for the PR to be merged (poll every 10s)
7. Tag the merge commit and push the tag
8. Wait for the [release workflow](.github/workflows/release.yml) to complete, which will:
- Run lint and the full test suite across Python 3.10-3.14
- Verify that `chartmogul/version.py` version matches the tag
- Build sdist and wheel
- Create a GitHub Release with auto-generated release notes and attached distribution files
- Publish to PyPI via [OIDC trusted publishing](https://docs.pypi.org/trusted-publishers/) with [build attestations](https://docs.pypi.org/attestations/)
9. Print links to the GitHub Release and PyPI package

## Changelog

Release notes are auto-generated from merged PR titles by the [release workflow](.github/workflows/release.yml). To ensure useful changelogs:

- Use clear, descriptive PR titles (e.g., "Add External ID field to Contact model")
- Prefix breaking changes with `BREAKING:` so they stand out in release notes
- After the release is created, review and edit the notes on the [Releases page](https://github.com/chartmogul/chartmogul-python/releases) if needed

## Pre-release Versions

For pre-release versions, use a semver pre-release suffix:

```sh
git tag v4.X.Y-rc1
git push origin v4.X.Y-rc1
```

These will be automatically marked as pre-releases on GitHub.

## Security

### Repository Protections

- **Immutable releases**: Once a GitHub Release is published, its tag cannot be moved or deleted, and release assets cannot be modified
- **Tag protection rulesets**: `v*` tags cannot be deleted or force-pushed

### PyPI

- Publishing uses [OIDC trusted publishing](https://docs.pypi.org/trusted-publishers/) — no long-lived API tokens are stored in the repository. GitHub Actions authenticates directly with PyPI via short-lived OIDC tokens.
- Once a package version is published to PyPI, [it cannot be republished](https://pypi.org/help/#file-name-reuse) with different contents
- Packages are published with [build attestations](https://docs.pypi.org/attestations/) (SLSA provenance), linking each version to the specific GitHub Actions run that built it
- Users can verify package integrity using pip's hash-checking mode (`--require-hashes`)
- Pinning versions in `requirements.txt` with hashes ensures reproducible installs

### What This Protects Against

- A compromised maintainer account cannot modify or delete existing releases
- No long-lived API tokens exist that could be leaked or stolen
- Tags cannot be moved to point to different commits after publication
- The PyPI registry provides an independent immutability layer beyond GitHub
- Build attestations allow anyone to verify a package was built from this repository by GitHub Actions

### Repository Settings (Admin)

These settings must be configured by a repository admin:

1. **Immutable Releases**: Settings > General > Releases > Enable "Immutable releases"
2. **Tag Protection Ruleset**: Settings > Rules > Rulesets > New ruleset targeting tags matching `v*` with deletion, force-push, and update prevention
3. **GitHub Actions Environment**: Settings > Environments > New environment named `pypi`
4. **PyPI Trusted Publishing**: On pypi.org, go to [chartmogul settings](https://pypi.org/manage/project/chartmogul/settings/publishing/) and configure a trusted publisher with: repository `chartmogul/chartmogul-python`, workflow `release.yml`, environment `pypi`
Loading