From 82a104396fa784a7c02024cc27d508ce151e68c5 Mon Sep 17 00:00:00 2001 From: Erdem Tuna Date: Tue, 7 Apr 2026 20:54:56 +0300 Subject: [PATCH 1/8] Initialize PAW workflow for Consolidate Pyproject Metadata Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../471-consolidate-pyproject/WorkShaping.md | 115 ++++++++++++++++++ .../WorkflowContext.md | 41 +++++++ 2 files changed, 156 insertions(+) create mode 100644 .paw/work/471-consolidate-pyproject/WorkShaping.md create mode 100644 .paw/work/471-consolidate-pyproject/WorkflowContext.md diff --git a/.paw/work/471-consolidate-pyproject/WorkShaping.md b/.paw/work/471-consolidate-pyproject/WorkShaping.md new file mode 100644 index 0000000..a951ac9 --- /dev/null +++ b/.paw/work/471-consolidate-pyproject/WorkShaping.md @@ -0,0 +1,115 @@ +# Work Shaping: Consolidate Package Metadata into pyproject.toml (PEP 621) + +**Issue**: [#471](https://github.com/Adyen/adyen-python-api-library/issues/471) +**Origin**: Follow-up from PR #468 which fixed `pydantic>=2.0` missing from `setup.py` + +## Problem Statement + +Package metadata is duplicated across `setup.py` and `pyproject.toml`, leading to drift. The `pydantic>=2.0` dependency was added to `pyproject.toml` but missed in `setup.py` (PR #468). The root cause is having two sources of truth for the same information. + +**Who benefits**: Maintainers (fewer places to update), contributors (clear canonical location), and users (consistent dependency resolution regardless of install method). + +## Work Breakdown + +### Core Work + +1. **Add `[tool.setuptools.packages.find]` to `pyproject.toml`** + - Include `Adyen*`, exclude `tests` and `tests.*` + - This replaces the `find_packages()` call in `setup.py` + +2. **Add `[project.urls]` to `pyproject.toml`** + - Homepage, Repository, Issues links + - Replaces the `url=` parameter lost from `setup.py` + +3. **Bump `build-system.requires` to `setuptools>=61.0.0`** + - Required for PEP 621 (`[project]` table) support + - Released March 2022, well-established; pip build isolation installs it regardless + +4. **Simplify `setup.py` to minimal shim** + ```python + from setuptools import setup + setup() + ``` + - All metadata now comes from `pyproject.toml` + - Kept (not removed) because release automation (`release-automation-action`) targets it as a version file + +5. **Update Makefile `install` target** + - Change from hardcoded `pip install requests pycurl mock coveralls ruff` + - To `pip install -e ".[test,dev,requests,pycurl]"` using pyproject.toml extras + - Single source of truth for dependencies + +### Out of Scope (Explicit Decisions) + +- **`VERSION` file**: Left as-is. Not used by release automation, not hurting anything. +- **`setup.cfg`**: Left as-is. Contains some legacy config, but narrowing scope to the issue's intent. +- **`Adyen/settings.py`**: Must keep `LIB_VERSION` — used at runtime for API headers. Cannot be eliminated without changing how the client reads version. +- **Removing `setup.py` entirely**: Too disruptive to release automation config; minimal shim achieves the deduplication goal. + +## Edge Cases & Expected Handling + +| Scenario | Handling | +|----------|----------| +| `pip install .` (modern pip) | Reads `pyproject.toml` directly — works | +| `python setup.py install` (legacy) | Shim delegates to setuptools which reads `pyproject.toml` — works with `setuptools>=61` | +| `python setup.py sdist/bdist_wheel` (legacy) | Same delegation — works | +| Release automation bumps version | Action updates `setup.py pyproject.toml Adyen/settings.py` — `setup.py` shim has no version to bump, but action should handle this gracefully (it searches for version patterns) | +| `adyen-sdk-automation` regenerates code | May regenerate `setup.py` with full metadata — see Risk Assessment | + +## Architecture Sketch + +**Before** (metadata flow): +``` +setup.py ──────────┐ + ├──> pip install (picks one, inconsistency possible) +pyproject.toml ────┘ +``` + +**After** (metadata flow): +``` +pyproject.toml ──────> pip install (single source) + │ +setup.py (shim) ─────> delegates to pyproject.toml via setuptools +``` + +**Version still lives in 3 files** (release automation manages sync): +- `pyproject.toml` → packaging +- `Adyen/settings.py` → runtime API headers +- `setup.py` → will have no version (shim only) + +## Critical Analysis + +**Value**: High. Prevents the exact class of bug that prompted PR #468. Low effort, high confidence. + +**Build vs Modify**: Pure modification — no new tooling or infrastructure. Standard Python packaging modernization. + +**Tradeoff**: Keeping `setup.py` as a shim is slightly redundant, but avoids touching release automation config (owned by Adyen, not us). + +## Codebase Fit + +- `pyproject.toml` already has a comprehensive `[project]` section — this work just makes it authoritative +- The library follows standard Python packaging conventions +- No custom build steps that would conflict with pure `pyproject.toml` metadata + +## Risk Assessment + +1. **Release automation compatibility**: The `release-automation-action` looks for version patterns in `setup.py`. With the shim having no `version=`, the action may fail or skip it. This needs testing, or the workflow's `version-files` should drop `setup.py`. + - **Mitigation**: After changes, verify that `release-automation-action` handles a version-less `setup.py` gracefully, or update `release.yml` to remove `setup.py` from `version-files`. + +2. **`adyen-sdk-automation` code generation**: If the automation repo regenerates `setup.py` with full metadata, it would undo this consolidation. + - **Mitigation**: Document this in the PR. The automation templates may need a corresponding update in `adyen-sdk-automation`. + +3. **Editable installs**: `pip install -e .` with `pyproject.toml` + setuptools works fine with `setuptools>=61`. + +## Open Questions for Downstream Stages + +1. Does `release-automation-action` handle a `setup.py` with no `version=` string? If not, should `setup.py` be removed from `version-files` in `release.yml`? +2. Does `adyen-sdk-automation` generate `setup.py`? If so, the template needs updating too. +3. Should `coveralls` be added to pyproject.toml extras since the Makefile currently installs it but it's not in any extras group? + +## Session Notes + +- **Scope kept tight**: User chose to leave `VERSION` file and `setup.cfg` alone +- **Makefile update included**: User wants `make install` to use pyproject.toml extras instead of hardcoded deps +- **Minimal shim over removal**: Avoids disrupting release automation owned by Adyen +- **`setuptools>=61.0.0`** confirmed as the version floor (matches issue proposal) +- **Project URLs to be added**: Homepage, Repository, Issues links in `[project.urls]` diff --git a/.paw/work/471-consolidate-pyproject/WorkflowContext.md b/.paw/work/471-consolidate-pyproject/WorkflowContext.md new file mode 100644 index 0000000..cd71b22 --- /dev/null +++ b/.paw/work/471-consolidate-pyproject/WorkflowContext.md @@ -0,0 +1,41 @@ +# WorkflowContext + +Work Title: Consolidate Pyproject Metadata +Work ID: 471-consolidate-pyproject +Base Branch: main +Target Branch: feature/471-consolidate-pyproject +Execution Mode: current-checkout +Repository Identity: github.com/adyen/adyen-python-api-library@76fe8dd0ab9bc1501dc1be87a1806633d04cbb89 +Execution Binding: none +Workflow Mode: full +Review Strategy: local +Review Policy: milestones +Session Policy: continuous +Final Agent Review: enabled +Final Review Mode: single-model +Final Review Interactive: smart +Final Review Models: claude-opus-4.6 +Final Review Specialists: all +Final Review Interaction Mode: parallel +Final Review Specialist Models: none +Final Review Perspectives: auto +Final Review Perspective Cap: 2 +Implementation Model: none +Plan Generation Mode: single-model +Plan Generation Models: none +Planning Docs Review: enabled +Planning Review Mode: multi-model +Planning Review Interactive: smart +Planning Review Models: gpt-5.4, claude-opus-4.6, gpt-5.3-codex +Planning Review Specialists: all +Planning Review Interaction Mode: parallel +Planning Review Specialist Models: none +Planning Review Perspectives: auto +Planning Review Perspective Cap: 2 +Custom Workflow Instructions: none +Initial Prompt: Consolidate package metadata into pyproject.toml (PEP 621) — eliminate duplication between setup.py and pyproject.toml +Issue URL: https://github.com/Adyen/adyen-python-api-library/issues/471 +Remote: origin +Artifact Lifecycle: commit-and-clean +Artifact Paths: auto-derived +Additional Inputs: none From afa5b8b809c54d8e19fe63fe4fb183dea3f1ddc7 Mon Sep 17 00:00:00 2001 From: Erdem Tuna Date: Tue, 7 Apr 2026 21:02:18 +0300 Subject: [PATCH 2/8] Add spec research and specification for pyproject consolidation Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .paw/work/471-consolidate-pyproject/Spec.md | 133 ++++++ .../471-consolidate-pyproject/SpecResearch.md | 448 ++++++++++++++++++ 2 files changed, 581 insertions(+) create mode 100644 .paw/work/471-consolidate-pyproject/Spec.md create mode 100644 .paw/work/471-consolidate-pyproject/SpecResearch.md diff --git a/.paw/work/471-consolidate-pyproject/Spec.md b/.paw/work/471-consolidate-pyproject/Spec.md new file mode 100644 index 0000000..21205fe --- /dev/null +++ b/.paw/work/471-consolidate-pyproject/Spec.md @@ -0,0 +1,133 @@ +# Feature Specification: Consolidate Package Metadata into pyproject.toml + +**Branch**: feature/471-consolidate-pyproject | **Created**: 2026-04-07 | **Status**: Draft +**Input Brief**: Eliminate metadata duplication between setup.py and pyproject.toml by making pyproject.toml the single source of truth (PEP 621) + +## Overview + +The Adyen Python API Library currently maintains package metadata in two places: `setup.py` and `pyproject.toml`. This duplication has already caused a real bug — the `pydantic>=2.0` dependency was added to `pyproject.toml` but missed in `setup.py` (fixed in PR #468), meaning users installing via certain code paths could get a broken install without pydantic. + +This specification defines the consolidation of all package metadata into `pyproject.toml` as the single authoritative source, following PEP 621 — the Python standard for declaring project metadata in `pyproject.toml`. The existing `setup.py` will be reduced to a minimal shim that delegates entirely to setuptools, which reads metadata from `pyproject.toml`. Supporting files (CI workflows, Makefile) will be updated to reflect the new single source of truth. + +The primary beneficiaries are library maintainers, who will no longer need to keep two files in sync when adding dependencies, updating metadata, or bumping versions. Contributors benefit from a clear canonical location for package configuration. End users benefit from consistent dependency resolution regardless of their installation method. + +## Objectives + +- Eliminate metadata duplication that caused the pydantic dependency bug (Rationale: prevents an entire class of sync-related bugs) +- Establish pyproject.toml as the single source of truth for all package metadata (Rationale: aligns with PEP 621 and modern Python packaging standards) +- Ensure the development setup command (`make install`) installs dependencies from the same source as production builds (Rationale: catches dependency mismatches early) +- Keep CI/CD workflows accurate by removing stale references to setup.py as a metadata source (Rationale: prevents misleading cache keys and no-op version bumps) + +## User Scenarios & Testing + +### User Story P1 – Maintainer adds a new dependency + +Narrative: A maintainer needs to add a new runtime dependency to the library. They add it to `pyproject.toml` under `dependencies` and are confident this single change is sufficient for all install paths — pip install, editable install, tox, and CI builds. + +Independent Test: Add a dependency to `pyproject.toml` `dependencies`, run `pip install .`, and verify the dependency is installed. + +Acceptance Scenarios: +1. Given `pyproject.toml` lists `pydantic>=2.0` in `dependencies`, When a user runs `pip install .`, Then pydantic is installed as a dependency +2. Given `pyproject.toml` lists a new dependency, When setup.py is not updated, Then the dependency is still correctly installed (because setup.py is a shim) +3. Given a maintainer edits only `pyproject.toml`, When `python -m build` runs, Then the built wheel includes the correct dependency metadata + +### User Story P2 – Developer sets up local environment + +Narrative: A developer clones the repository and runs `make install` to set up their local development environment. All test, dev, and runtime dependencies are installed from pyproject.toml extras, and the project is available in editable mode. + +Independent Test: Run `make install` in a fresh virtualenv and verify the project is importable and test dependencies are available. + +Acceptance Scenarios: +1. Given a clean virtualenv, When the developer runs `make install`, Then the Adyen package is installed in editable mode with test and dev dependencies +2. Given `make install` has been run, When the developer runs `make tests`, Then tests execute successfully with all required dependencies present + +### User Story P3 – Release automation bumps version + +Narrative: The release automation action triggers a version bump. It updates version strings in `pyproject.toml` and `Adyen/settings.py` without attempting to modify `setup.py` (which no longer contains a version). + +Independent Test: Simulate a version bump by replacing the version string in `pyproject.toml` and `Adyen/settings.py`, build the package, and verify the new version appears in the built distribution metadata. + +Acceptance Scenarios: +1. Given the release workflow runs, When it processes `version-files`, Then it updates `pyproject.toml` and `Adyen/settings.py` (not `setup.py`) +2. Given setup.py contains only `from setuptools import setup; setup()`, When the release action's perl regex runs against it, Then no error occurs (regex finds no match, which is a no-op) + +### Edge Cases + +- **Legacy pip install**: Users with older pip versions that invoke `python setup.py install` — the minimal shim delegates to setuptools which reads pyproject.toml (requires setuptools ≥ 61.0.0 installed, ensured by build-system.requires) +- **Editable install**: `pip install -e .` works with the shim + pyproject.toml metadata via PEP 660 (setuptools ≥ 64) or legacy mode (shim present) +- **tox environments**: tox.ini installs deps directly (not via extras) — unaffected by this change, continues to work as-is +- **setup.py with no version**: The release action's perl substitution is a no-op on a file with no version string — no error, no change + +## Requirements + +### Functional Requirements + +- FR-001: pyproject.toml `[project]` table is the sole source of package metadata (name, version, description, dependencies, classifiers, etc.) (Stories: P1, P3) +- FR-002: setup.py contains only `from setuptools import setup; setup()` with no metadata arguments (Stories: P1, P2) +- FR-003: pyproject.toml includes `[tool.setuptools.packages.find]` configuration for package discovery (Stories: P1) +- FR-004: pyproject.toml includes `[project.urls]` with Homepage, Repository, and Issues links (Stories: P1) +- FR-005: `build-system.requires` specifies `setuptools>=61.0.0` (minimum for PEP 621 support) (Stories: P1, P2) +- FR-006: `make install` installs the project in editable mode using pyproject.toml extras (Stories: P2) +- FR-007: Release workflow `version-files` no longer includes setup.py (Stories: P3) +- FR-008: CI lint workflow cache key uses pyproject.toml hash instead of setup.py hash (Stories: P2) + +### Cross-Cutting / Non-Functional + +- All existing tests pass without modification after the consolidation +- Package builds (`python -m build`) produce identical metadata to the current release (minus the improved long_description from README) +- No breaking change for users who `pip install Adyen` from PyPI + +## Success Criteria + +- SC-001: `pip install .` in a clean environment installs `pydantic>=2.0` as a dependency (FR-001) +- SC-002: `pip install -e ".[requests,test,dev]"` succeeds and all extras are available (FR-001, FR-003, FR-006) +- SC-003: `python -m build --sdist --wheel` succeeds and the built metadata shows correct name, version, dependencies, and URLs (FR-001, FR-003, FR-004, FR-005) +- SC-004: `make install && make tests` succeeds with all tests passing (FR-006) +- SC-005: setup.py contains exactly two lines: an import and a setup() call with no arguments (FR-002) +- SC-006: `grep -c 'version' setup.py` returns 0 — no version string in setup.py (FR-002, FR-007) + +## Assumptions + +- The `release-automation-action` perl-based version substitution gracefully handles files where no match is found (confirmed: perl `s///` with no match is a no-op) +- The `adyen-sdk-automation` code generator does not generate or modify `setup.py` (confirmed by SpecResearch — templates only generate service files) +- Keeping `wheel` in `build-system.requires` alongside setuptools (harmless, improves compatibility) +- The `coveralls` package previously installed by `make install` is not required for local development (it was a CI-only tool); dropping it from `make install` is acceptable + +## Scope + +In Scope: +- Simplify setup.py to minimal shim +- Add `[tool.setuptools.packages.find]` to pyproject.toml +- Add `[project.urls]` to pyproject.toml +- Bump `build-system.requires` to `setuptools>=61.0.0` +- Update Makefile `install` target to use pyproject.toml extras +- Update `release.yml` to remove setup.py from version-files +- Update `lint.yml` cache key from setup.py to pyproject.toml + +Out of Scope: +- Removing setup.py entirely (kept as shim for backward compatibility) +- Modifying `VERSION` file +- Modifying `setup.cfg` +- Modifying `Adyen/settings.py` +- Updating `tox.ini` to use pyproject.toml extras (future improvement) +- Adding `coveralls` to pyproject.toml extras + +## Dependencies + +- setuptools ≥ 61.0.0 (for PEP 621 `[project]` table support) — released March 2022, widely available +- Adyen release-automation-action v1.4.0 (must handle version-files update) + +## Risks & Mitigations + +- **Release automation breaks with updated version-files**: The perl substitution is a simple find-and-replace that operates on the file list. Removing setup.py from the list is a safe reduction. Mitigation: The VERSION file (source of truth for the action) and Adyen/settings.py remain in the list unchanged. +- **Code generation overwrites setup.py**: Confirmed by research that adyen-sdk-automation does NOT touch setup.py or pyproject.toml. Mitigation: None needed; risk is eliminated. +- **Legacy build tools fail**: Some very old tools may not support pyproject.toml. Mitigation: The minimal setup.py shim ensures backward compatibility with any tool that invokes `python setup.py install`. + +## References + +- Issue: https://github.com/Adyen/adyen-python-api-library/issues/471 +- PR #468: Fixed pydantic dependency in pyproject.toml (the bug that motivated this issue) +- PEP 621: https://peps.python.org/pep-0621/ +- Python Packaging Authority migration guide: https://packaging.python.org/en/latest/guides/modernize-setup-py-project/ +- Research: .paw/work/471-consolidate-pyproject/SpecResearch.md +- Work Shaping: .paw/work/471-consolidate-pyproject/WorkShaping.md diff --git a/.paw/work/471-consolidate-pyproject/SpecResearch.md b/.paw/work/471-consolidate-pyproject/SpecResearch.md new file mode 100644 index 0000000..d761a3c --- /dev/null +++ b/.paw/work/471-consolidate-pyproject/SpecResearch.md @@ -0,0 +1,448 @@ +# Spec Research: Consolidate Pyproject Metadata + +## 1. Python Packaging Standards + +### PEP 621 — `[project]` Table Fields + +PEP 621 standardises project metadata in `pyproject.toml`. The only **required** field is `name`. Key available fields: + +| Field | Type | Notes | +|---|---|---| +| `name` | string | **Required** | +| `version` | string | Static, or declared `dynamic` | +| `description` | string | One-line summary | +| `readme` | string / table | Path to README; setuptools auto-sets `long_description` and `long_description_content_type` | +| `requires-python` | string | e.g. `">=3.8"` | +| `license` | SPDX / table | e.g. `{text = "MIT"}` or SPDX expression | +| `authors` | list of tables | `[{name = "...", email = "..."}]` | +| `maintainers` | list of tables | Same structure | +| `keywords` | list of strings | | +| `classifiers` | list of strings | Trove classifiers | +| `urls` | table | `[project.urls]` section | +| `dependencies` | list of strings | PEP 508 format | +| `optional-dependencies` | table of lists | `[project.optional-dependencies]` | +| `dynamic` | list of strings | Fields resolved at build time | + +### setuptools ≥ 61.0.0 Behaviour + +- **First version** of setuptools to read PEP 621 `[project]` metadata from `pyproject.toml`. +- When both `setup.py` and `pyproject.toml` define metadata, **`pyproject.toml` takes precedence** for fields defined in `[project]`. Any remaining setup.py kwargs are merged in. +- With a **minimal shim** (`from setuptools import setup; setup()`), setuptools will read all metadata from `pyproject.toml`. The shim adds no conflicting metadata. + +### `[tool.setuptools.packages.find]` Syntax + +```toml +[tool.setuptools.packages.find] +include = ["Adyen*"] # glob patterns (NOT regex) +exclude = ["test*", "tests*"] # glob patterns +``` + +- `where` (default `["."]`): directories to search. +- `include`: if set, **only** packages matching these globs are included. +- `exclude`: packages matching these globs are excluded from the result. +- `namespaces` (default `true`): set `false` to require `__init__.py`. + +### `readme` Field Behaviour + +When `readme = "README.md"` is in `[project]`: +- setuptools sets `long_description` from the file content. +- `long_description_content_type` is auto-detected from the file extension (`.md` → `text/markdown`). +- This **replaces** the current `setup.py` hardcoded string `"A Python client library for accessing Adyen APIs"` with the full README content — **an improvement**. + +### Minimal `setup.py` Shim Compatibility + +- **pip ≥ 21.3 + setuptools ≥ 64**: PEP 660 editable installs (`pip install -e .`) work **without any setup.py**. +- **Older pip**: a `setup.py` shim (`from setuptools import setup; setup()`) is needed for `pip install -e .`. +- The shim pattern is **explicitly recommended** by the [Python Packaging Authority migration guide](https://packaging.python.org/en/latest/guides/modernize-setup-py-project/). +- Keeping the shim ensures compatibility with legacy environments, CI/CD tools, and the `adyen-sdk-automation` build system. + +### Edge Case: Editable Installs + +With `setuptools>=61.0` specified in `build-system.requires`: +- `pip install -e .` works with `pyproject.toml`-only metadata + a minimal shim. +- The `[tool.setuptools.packages.find]` section ensures correct package discovery during editable installs. +- The `lint.yml` workflow already uses `pip install -e ".[dev]"` — this will continue to work. + +--- + +## 2. Current Codebase State + +### `setup.py` — Current State + +```python +from setuptools import find_packages, setup + +setup( + name="Adyen", + packages=find_packages(include=["Adyen*"], exclude=["tests", "tests.*"]), + version="15.0.0", + maintainer="Adyen", + maintainer_email="support@adyen.com", + description="Adyen Python Api", + long_description="A Python client library for accessing Adyen APIs", + author="Adyen", + author_email="support@adyen.com", + url="https://github.com/Adyen/adyen-python-api-library", + keywords=["payments", "adyen", "fintech"], + python_requires=">=3.8", + install_requires=[], + extras_require={ + "requests": ["requests>=2.25.0"], + "pycurl": ["pycurl>=7.43.0"], + "test": ["pytest>=7.0.0", "pytest-cov>=4.0.0", "mock>=4.0.0", "requests>=2.25.0"], + "dev": ["ruff>=0.4.4", "pre-commit>=3.0.0"], + }, + classifiers=[...], +) +``` + +**Key observations:** +- `install_requires=[]` — **empty**. Missing `pydantic>=2.0` which IS in `pyproject.toml`. This is the bug described in issue #471 (fixed in PR #468 for `pyproject.toml` only). +- `long_description` is a static string, not README content. +- `url` is defined but no `project_urls` dict (which maps to `[project.urls]`). +- `find_packages(include=["Adyen*"], exclude=["tests", "tests.*"])` — note exclude uses `"tests"` and `"tests.*"` but the actual test directory is named `test` (singular). + +**What gets removed:** Everything except `from setuptools import setup; setup()`. + +### `pyproject.toml` — Current State + +```toml +[build-system] +requires = ["setuptools>=45", "wheel"] +build-backend = "setuptools.build_meta" + +[project] +name = "Adyen" +version = "15.0.0" +description = "Adyen Python Api" +readme = "README.md" +requires-python = ">=3.8" +authors = [{name = "Adyen", email = "support@adyen.com"}] +maintainers = [{name = "Adyen", email = "support@adyen.com"}] +keywords = ["payments", "adyen", "fintech"] +dependencies = ["pydantic>=2.0"] +classifiers = [...] + +[project.optional-dependencies] +requests = ["requests>=2.25.0"] +pycurl = ["pycurl>=7.43.0"] +test = ["pytest>=7.0.0", "pytest-cov>=4.0.0", "mock>=4.0.0", "requests>=2.25.0"] +dev = ["ruff>=0.4.4", "pre-commit>=3.0.0"] + +[tool.ruff] +... + +[tool.coverage.run] +source = ["Adyen/"] +``` + +**What's already there:** Most metadata is already present and correct. + +**What needs adding/changing:** +1. `build-system.requires` → bump from `"setuptools>=45"` to `"setuptools>=61.0.0"` +2. Add `[tool.setuptools.packages.find]` section +3. Add `[project.urls]` section +4. Optionally remove `"wheel"` from `build-system.requires` (modern pip/setuptools include it automatically, but keeping it is harmless) + +### `setup.cfg` — Current State (Out of Scope) + +```ini +[bdist_wheel] + +[metadata] +description_file = README.md + +[egg_info] +tag_build = +tag_date = 0 +tag_svn_revision = 0 + +[coverage:run] +source = + Adyen/ +``` + +**Awareness notes:** +- The `[metadata] description_file` is a legacy setuptools-specific field. With `readme = "README.md"` in `pyproject.toml`, this becomes redundant but harmless. +- The `[coverage:run]` section duplicates `[tool.coverage.run]` in `pyproject.toml`. Out of scope but noted. +- Not modifying this file per the work shaping decision. + +### `Makefile` — Current Install Target + +```makefile +install: + @pip install requests pycurl mock coveralls ruff +``` + +**Issues:** +- Hardcodes individual package names instead of using the project's dependency groups. +- Doesn't install the project itself. +- Doesn't use `pyproject.toml`-defined extras. +- Includes `coveralls` which isn't in any extras group. + +**Proposed replacement:** +```makefile +install: + @pip install -e ".[requests,test,dev]" +``` + +This: +- Installs the project in editable mode +- Pulls in `requests`, `test`, and `dev` extras from `pyproject.toml` +- Does NOT include `pycurl` (requires system-level `libcurl` — handled by tox separately) +- Does NOT include `coveralls` (not in any extras; consider adding to `test` extras or keeping separate) + +### `Adyen/settings.py` — Runtime Version (Out of Scope) + +```python +LIB_NAME = "adyen-python-api-library" +LIB_VERSION = "15.0.0" +``` + +Used by `Adyen/client.py` for: +- HTTP headers: `adyen-library-name` and `adyen-library-version` +- User-Agent suffix +- These are **runtime values** sent to Adyen APIs, not packaging metadata. +- The release-automation-action bumps this file via the perl regex substitution. +- Out of scope per work shaping decision. + +### `VERSION` File — Current Content (Out of Scope) + +``` +15.0.0 +``` + +- The release-automation-action reads this via `cat VERSION` to get the current version. +- Used as the source of truth for the release automation. +- Out of scope per work shaping decision. + +### `.github/workflows/release.yml` + +```yaml +- name: Bump + run: | + perl -i -pe 's/${{steps.current-version.outputs.current-version}}/${{steps.release.outputs.next-version}}/' VERSION ${{ inputs.version-files }} +``` + +With `version-files: setup.py pyproject.toml Adyen/settings.py`: +- The perl command does a **literal string substitution** of the old version with the new version across all listed files. +- For `setup.py`, it currently matches `version="15.0.0"` on line 6. +- **After our change**, `setup.py` will be `from setuptools import setup\nsetup()` — **no version string present**. The perl command will simply not match anything in `setup.py`, which is harmless (perl `s///` with no match is a no-op). +- However, keeping `setup.py` in `version-files` is misleading. It should be **removed** from `version-files`. + +**⚠️ REQUIRED CHANGE:** `version-files` line must be updated to remove `setup.py`: +```yaml +version-files: pyproject.toml Adyen/settings.py +``` + +### `.github/workflows/pypipublish.yml` + +```yaml +- name: Install pypa/build + run: python -m pip install build --user +- name: Build a binary wheel and a source tarball + run: python -m build --sdist --wheel --outdir dist/ . +``` + +- Uses `python -m build` which reads `pyproject.toml` for metadata. +- **No changes needed.** This workflow already works with `pyproject.toml` as the metadata source. + +### `.github/workflows/python-ci.yml` + +```yaml +- name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install tox + sudo apt-get update + sudo apt install libcurl4-openssl-dev +- name: Test with tox + run: tox +``` + +- Installs `tox` and runs tests. Does not reference `setup.py` directly. +- **No changes needed.** + +### `.github/workflows/lint.yml` + +```yaml +key: ${{ runner.os }}-pip-${{ hashFiles('**/setup.py') }} +``` + +- Uses `hashFiles('**/setup.py')` for pip cache key. +- **⚠️ SHOULD UPDATE** to `hashFiles('**/pyproject.toml')` since `pyproject.toml` is now the source of truth for dependencies. After our change, `setup.py` will never change (it's a static shim), making it useless as a cache key. + +Also in `lint.yml`: +```yaml +- name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install -e ".[dev]" +``` + +- Already uses `pip install -e ".[dev]"` — this works perfectly with pyproject.toml extras. **No changes needed** for this part. + +### `tox.ini` + +```ini +[testenv] +deps = + mock + requests: requests + pycurl: pycurl +commands = + make tests +``` + +- Does **not** use extras from pyproject.toml — installs deps directly. +- **No changes proposed** in this issue. Could be a future improvement to use `pip install .[test,requests]` etc. + +--- + +## 3. Risk Analysis + +### 3.1 Release Automation — `version-files` with Minimal setup.py + +**Mechanism:** The `Adyen/release-automation-action@v1.4.0` (pinned to SHA `3e5694d...`): +1. Reads current version from `cat VERSION` +2. Computes next version from PR labels +3. Runs: `perl -i -pe 's/OLD_VERSION/NEW_VERSION/' VERSION ${{ inputs.version-files }}` +4. Creates a PR with the bumped files + +**Risk assessment:** +- If `setup.py` contains only `from setuptools import setup\nsetup()`, the perl regex `s/15.0.0/16.0.0/` will find **no match** — this is a **no-op**, not an error. Perl `s///` with no match simply leaves the file unchanged. +- **Impact: NONE functionally**, but the file will be needlessly processed. +- **Recommendation:** Remove `setup.py` from `version-files` in `release.yml` to keep the workflow clean and avoid confusion. +- **Updated line:** `version-files: pyproject.toml Adyen/settings.py` + +### 3.2 SDK Automation (Code Generation) + +**Finding:** The `adyen-sdk-automation` repo's `python/build.gradle.kts` does **NOT** generate or modify `setup.py`. It only: +- Generates service files into `repo/Adyen/services//` +- Copies `__init__.py` files for services +- Uses mustache templates for API classes only + +The local `Makefile` generator section (lines 20-101) also only generates into `Adyen/services/` — no `setup.py` involvement. + +**Templates directory** (`templates/`): Contains only API mustache templates (`api-single.mustache`, `api-small.mustache`, etc.) and `config.yaml`. **No setup.py template exists.** + +**Risk: NONE.** The code generation process is completely independent of packaging metadata. + +### 3.3 Files Referencing setup.py + +| File | Reference | Impact | +|---|---|---| +| `.github/workflows/release.yml:38` | `version-files: setup.py pyproject.toml Adyen/settings.py` | Must remove `setup.py` | +| `.github/workflows/lint.yml:30` | `hashFiles('**/setup.py')` | Should update to `hashFiles('**/pyproject.toml')` | +| `setup.py` itself | `from setuptools import find_packages, setup` | Simplify to `from setuptools import setup; setup()` | + +No other files import from or reference `setup.py`. + +### 3.4 Dependency Discrepancy + +**Current state:** +- `pyproject.toml` has `dependencies = ["pydantic>=2.0"]` +- `setup.py` has `install_requires=[]` (empty!) + +**After consolidation:** `pyproject.toml` is authoritative. The empty `install_requires` in the shim `setup.py` is harmless because modern setuptools reads `dependencies` from `[project]` and does **not merge** `install_requires` from `setup()` when `[project].dependencies` is defined. + +**Risk: NONE after consolidation.** Actually resolves the discrepancy. + +### 3.5 Package Discovery + +**Current `setup.py`:** `find_packages(include=["Adyen*"], exclude=["tests", "tests.*"])` +- Note: The exclude pattern says `"tests"` (plural) but the actual directory is `test` (singular). The exclude pattern was **never matching** the real test directory anyway. +- The `include=["Adyen*"]` pattern is the effective filter. + +**Proposed `pyproject.toml`:** +```toml +[tool.setuptools.packages.find] +include = ["Adyen*"] +exclude = ["test*"] +``` + +The `test` directory has a `__init__.py`, making it a discoverable package. But `include = ["Adyen*"]` alone is sufficient to exclude it, since `test` doesn't match `Adyen*`. Adding `exclude = ["test*"]` is a belt-and-suspenders approach. + +### 3.6 Version Synchronisation Across Files + +After this change, version appears in these files (all still bumped by the release action): +- `VERSION` — source of truth for release action (`cat VERSION`) +- `pyproject.toml` — `version = "15.0.0"` in `[project]` +- `Adyen/settings.py` — `LIB_VERSION = "15.0.0"` for runtime headers + +The `setup.py` shim will **no longer contain a version string**, which is correct because `pyproject.toml` is the authoritative source. + +--- + +## 4. Ecosystem Patterns + +### Other Adyen Libraries + +A GitHub code search for `pyproject.toml` + `[tool.setuptools.packages.find]` or `[project.urls]` across the Adyen org returned **no results**. This Python library appears to be the **first Adyen SDK** to adopt PEP 621 consolidation. + +The `release-automation-action` is used across multiple Adyen repos but its `version-files` input is always a space-separated list of files — the action is agnostic to file format and uses simple string replacement. + +### Industry Standard + +The pattern of `pyproject.toml` as single source of truth with a minimal `setup.py` shim is **widely adopted** in the Python ecosystem: +- The Python Packaging Authority has an [official migration guide](https://packaging.python.org/en/latest/guides/modernize-setup-py-project/) +- Major projects (Django, Flask, requests, etc.) have migrated or are migrating +- `setuptools>=61.0` has been available since March 2022 — well-established + +--- + +## 5. Key Findings & Recommendations + +### Critical Actions (Must Do) + +1. **Simplify `setup.py`** to `from setuptools import setup; setup()` — removes all duplicate metadata. + +2. **Add `[tool.setuptools.packages.find]`** to `pyproject.toml`: + ```toml + [tool.setuptools.packages.find] + include = ["Adyen*"] + exclude = ["test*"] + ``` + +3. **Bump `build-system.requires`** from `"setuptools>=45"` to `"setuptools>=61.0.0"`. + +4. **Add `[project.urls]`**: + ```toml + [project.urls] + Homepage = "https://github.com/Adyen/adyen-python-api-library" + Repository = "https://github.com/Adyen/adyen-python-api-library" + Issues = "https://github.com/Adyen/adyen-python-api-library/issues" + ``` + +5. **Update `Makefile` install target**: + ```makefile + install: + @pip install -e ".[requests,test,dev]" + ``` + +6. **Update `release.yml`** — remove `setup.py` from `version-files`: + ```yaml + version-files: pyproject.toml Adyen/settings.py + ``` + +7. **Update `lint.yml`** — change cache key from `hashFiles('**/setup.py')` to `hashFiles('**/pyproject.toml')`. + +### Findings That Align with Work Shaping + +- All shaping decisions are sound and feasible. +- The minimal shim approach is the recommended Python packaging migration pattern. +- No contradictions found with any shaping decision. + +### Additional Findings (Not Contradictions, but Worth Noting) + +1. **`setup.cfg` redundancy:** The `[metadata] description_file = README.md` and `[coverage:run]` sections duplicate what's in `pyproject.toml`. Out of scope per shaping, but worth a follow-up issue. + +2. **`install_requires` bug was real:** `setup.py` had `install_requires=[]` while `pyproject.toml` had `dependencies = ["pydantic>=2.0"]`. The consolidation fully resolves this class of bug. + +3. **Existing `setup.py` exclude pattern was wrong:** `exclude=["tests", "tests.*"]` never matched the `test/` directory. The new `pyproject.toml` config with `include = ["Adyen*"]` is more correct. + +4. **`coveralls` in old Makefile:** The current `Makefile` installs `coveralls` but it's not in any extras group. The new install target drops it. If coverage upload is needed, it should be added to `test` extras in a follow-up. + +5. **`wheel` in build-system.requires:** `"wheel"` can optionally be removed from `build-system.requires` since modern pip (≥21.0) includes it. However, keeping it is harmless and improves compatibility. **Recommendation: keep it.** + +6. **tox.ini doesn't use extras:** The `tox.ini` installs deps manually rather than via `pip install .[test]`. This is out of scope but could be a future improvement. From 1b4594269677b42c985a21d59a360b815f9da635 Mon Sep 17 00:00:00 2001 From: Erdem Tuna Date: Tue, 7 Apr 2026 21:22:32 +0300 Subject: [PATCH 3/8] Add code research and implementation plan MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - CodeResearch.md: file:line mapping of all 5 target files plus verified-clean files - ImplementationPlan.md: 2 phases (metadata consolidation + documentation) - Single implementation phase — all changes are tightly coupled Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../471-consolidate-pyproject/CodeResearch.md | 351 ++++++++++++++++++ .../ImplementationPlan.md | 118 ++++++ 2 files changed, 469 insertions(+) create mode 100644 .paw/work/471-consolidate-pyproject/CodeResearch.md create mode 100644 .paw/work/471-consolidate-pyproject/ImplementationPlan.md diff --git a/.paw/work/471-consolidate-pyproject/CodeResearch.md b/.paw/work/471-consolidate-pyproject/CodeResearch.md new file mode 100644 index 0000000..5f121f7 --- /dev/null +++ b/.paw/work/471-consolidate-pyproject/CodeResearch.md @@ -0,0 +1,351 @@ +# Code Research: Consolidate pyproject.toml (PEP 621) + +```yaml +date: 2025-07-21 +git_commit: afa5b8b809c54d8e19fe63fe4fb183dea3f1ddc7 +branch: feature/471-consolidate-pyproject +repository: /home/erdemtuna/workspace/personal/adyen-python-api-library +topic: Consolidate package metadata into pyproject.toml, eliminate setup.py duplication +tags: [pyproject.toml, PEP-621, setup.py, packaging, CI, release-automation] +status: complete +``` + +## Research Question + +Where does package metadata currently live, what references exist to `setup.py` across the codebase, and what exact lines must change to make `pyproject.toml` the sole source of truth? + +## Summary + +Package metadata is **fully duplicated** between `setup.py` (45 lines) and `pyproject.toml` (81 lines). The `pyproject.toml` already contains a complete `[project]` table with all metadata, optional-dependencies, and tool configs. The only things **missing** from `pyproject.toml` are `[tool.setuptools.packages.find]` and `[project.urls]`. Only **two files** outside setup.py reference it: `release.yml:38` (version-files) and `lint.yml:30` (cache key). No tests verify packaging metadata. The `Makefile install` target uses raw `pip install` commands (not extras) — it needs updating per FR-006. + +## Documentation System + +| Item | Status | +|------|--------| +| `docs/` directory | **Does not exist** | +| Sphinx / mkdocs | **Not present** | +| `README.md` | Exists (18,814 bytes) — referenced by `pyproject.toml:9` as `readme = "README.md"` | +| `CONTRIBUTING.md` | Exists (887 bytes) | +| `CODE_OF_CONDUCT.md` | Exists (3,349 bytes) | +| `LICENSE.md` | Exists (1,062 bytes) — MIT License | + +## Verification Commands + +| Command | Purpose | Source | +|---------|---------|--------| +| `make tests` | Run unit tests via `python -m unittest discover -s test -p '*Test.py'` | Makefile:14 | +| `make lint` | Run ruff linter: `ruff check Adyen test` | Makefile:5 | +| `make install` | Install deps: `pip install requests pycurl mock coveralls ruff` | Makefile:2 | +| `tox` | Run full matrix (py38-py314, pycurl/requests/urllib, lint) | tox.ini:2 | +| `pip install -e ".[dev]"` | Install with dev extras (used in lint.yml:37) | .github/workflows/lint.yml:37 | +| `python -m build --sdist --wheel` | Build distributions (used in pypipublish.yml:33-38) | .github/workflows/pypipublish.yml:33-38 | +| `ruff check Adyen test` | Direct ruff invocation | Makefile:5, lint.yml:41 | +| `ruff format --check Adyen test` | Formatter check | lint.yml:45 | + +## Detailed Findings + +--- + +### 1. `setup.py` (45 lines) — IN SCOPE, MUST MODIFY + +**Full content analysis:** + +| Lines | Content | Notes | +|-------|---------|-------| +| 1 | `from setuptools import find_packages, setup` | Imports `find_packages` — not needed in shim | +| 3-45 | `setup(...)` with all kwargs | **All of this becomes the shim** | +| 4 | `name="Adyen"` | Duplicated at pyproject.toml:6 | +| 5 | `packages=find_packages(include=["Adyen*"], exclude=["tests", "tests.*"])` | Replaced by `[tool.setuptools.packages.find]` in pyproject.toml | +| 6 | `version="15.0.0"` | Duplicated at pyproject.toml:7, Adyen/settings.py:2, VERSION:1 | +| 7-8 | `maintainer`/`maintainer_email` | Duplicated at pyproject.toml:14-16 | +| 9-10 | `description`/`long_description` | Duplicated at pyproject.toml:8-9 (pyproject uses README.md) | +| 11-12 | `author`/`author_email` | Duplicated at pyproject.toml:11-13 | +| 13 | `url="https://github.com/Adyen/adyen-python-api-library"` | **NOT in pyproject.toml** — needs `[project.urls]` | +| 14 | `keywords=["payments", "adyen", "fintech"]` | Duplicated at pyproject.toml:17 | +| 15 | `python_requires=">=3.8"` | Duplicated at pyproject.toml:10 | +| 16 | `install_requires=[]` | **Empty!** pyproject.toml:18 has `["pydantic>=2.0"]` — this is the known discrepancy | +| 17-29 | `extras_require={...}` | Duplicated at pyproject.toml:34-46 | +| 31-44 | `classifiers=[...]` | Duplicated at pyproject.toml:19-32 | + +**Target state:** Replace entire file with: +```python +from setuptools import setup +setup() +``` + +**Integration points:** +- `.github/workflows/release.yml:38` — `version-files: setup.py pyproject.toml Adyen/settings.py` +- `.github/workflows/lint.yml:30` — `hashFiles('**/setup.py')` +- No Python code imports from `setup.py` +- `adyen-sdk-automation` does **not** generate `setup.py` (confirmed in SpecResearch) + +--- + +### 2. `pyproject.toml` (81 lines) — IN SCOPE, MUST MODIFY + +**Current sections:** + +| Lines | Section | Status | +|-------|---------|--------| +| 1-3 | `[build-system]` | Exists. `requires = ["setuptools>=45", "wheel"]`. **Must update** `setuptools>=45` → `setuptools>=61.0.0` per FR-005 | +| 5-32 | `[project]` | Complete metadata. Already has name, version, description, readme, requires-python, authors, maintainers, keywords, dependencies, classifiers | +| 34-46 | `[project.optional-dependencies]` | Complete. Has requests, pycurl, test, dev extras | +| 48-60 | `[tool.ruff]` | Exists. Out of scope | +| 62-75 | `[tool.ruff.lint]` | Exists. Out of scope | +| 77-78 | `[tool.ruff.lint.isort]` | Exists. Out of scope | +| 80-81 | `[tool.coverage.run]` | Exists. Out of scope | + +**Missing sections (must add):** + +1. **`[tool.setuptools.packages.find]`** — FR-003. Replaces `setup.py:5` `find_packages(include=["Adyen*"], exclude=["tests", "tests.*"])`. Should be: + ```toml + [tool.setuptools.packages.find] + include = ["Adyen*"] + ``` + +2. **`[project.urls]`** — FR-004. Replaces `setup.py:13` `url=...`. Should be: + ```toml + [project.urls] + Homepage = "https://github.com/Adyen/adyen-python-api-library" + Repository = "https://github.com/Adyen/adyen-python-api-library" + Issues = "https://github.com/Adyen/adyen-python-api-library/issues" + ``` + +**Must change:** +- Line 2: `requires = ["setuptools>=45", "wheel"]` → `requires = ["setuptools>=61.0.0", "wheel"]` + +**Integration points:** +- `.github/workflows/release.yml:38` — listed in `version-files` (stays) +- `.github/workflows/lint.yml:30` — will become the new cache key hash source +- `.github/workflows/lint.yml:37` — `pip install -e ".[dev]"` reads extras from here +- `tox.ini` — tox installs the package, reads metadata from here +- `.github/workflows/pypipublish.yml:33-38` — `python -m build` reads from here + +--- + +### 3. `Makefile` (103 lines) — IN SCOPE, MUST MODIFY + +**Relevant targets:** + +| Lines | Target | Current Command | Issue | +|-------|--------|-----------------|-------| +| 1-2 | `install` | `pip install requests pycurl mock coveralls ruff` | **FR-006**: Should use `pip install -e ".[test,dev,requests,pycurl]"` or equivalent extras-based install | +| 4-5 | `lint` | `ruff check Adyen test` | No change needed | +| 7-8 | `lint-fix` | `ruff check --fix Adyen test` | No change needed | +| 10-11 | `format` | `ruff format Adyen test` | No change needed | +| 13-14 | `tests` | `python -m unittest discover -s test -p '*Test.py'` | No change needed | +| 16-17 | `coverage` | `coverage run -m unittest discover...` | No change needed | +| 20-101 | Generator targets | OpenAPI code gen | Out of scope | + +**Integration points:** +- `tox.ini:13` — `commands = make tests` +- `.github/workflows/lint.yml` — does **not** use `make install`; uses `pip install -e ".[dev]"` directly + +--- + +### 4. `.github/workflows/release.yml` (43 lines) — IN SCOPE, MUST MODIFY + +**Key line:** +- **Line 38**: `version-files: setup.py pyproject.toml Adyen/settings.py` + +This is the `Adyen/release-automation-action@v1.4.0` parameter that lists files to apply version bumps via perl regex substitution. The action reads the current version from the `VERSION` file (or detects it), then runs `perl -i -pe"s/$old/$new/g"` on each listed file. + +**FR-007 change:** Remove `setup.py` from this line → `version-files: pyproject.toml Adyen/settings.py` + +**Context:** The shim `setup.py` will have no version string. While perl's `s///` is a no-op on no-match (harmless), keeping `setup.py` in the list is misleading. + +--- + +### 5. `.github/workflows/lint.yml` (61 lines) — IN SCOPE, MUST MODIFY + +**Key line:** +- **Line 30**: `key: ${{ runner.os }}-pip-${{ hashFiles('**/setup.py') }}` + +**FR-008 change:** Update to `hashFiles('**/pyproject.toml')` since after consolidation, `setup.py` is a static 2-line shim that never changes — useless as a cache invalidation key. + +**Other notable lines (no changes needed):** +- Line 37: `pip install -e ".[dev]"` — already uses pyproject.toml extras. No change. +- Line 41: `ruff check Adyen test` — no change. +- Line 45: `ruff format --check Adyen test` — no change. + +--- + +### 6. `.github/workflows/python-ci.yml` (44 lines) — VERIFICATION ONLY + +**No `setup.py` references found.** This workflow: +- Line 38: `pip install tox` — installs tox +- Line 43: `tox` — runs tox which invokes `make tests` + +Confirmed clean. No changes needed. + +--- + +### 7. `.github/workflows/pypipublish.yml` (49 lines) — VERIFICATION ONLY + +**No `setup.py` references found.** This workflow: +- Line 29-30: `pip install build` — installs build tool +- Line 33-38: `python -m build --sdist --wheel --outdir dist/ .` — builds from pyproject.toml + +Confirmed clean. No changes needed. + +--- + +### 8. `tox.ini` (20 lines) — OUT OF SCOPE, VERIFIED + +**Content analysis:** +- Line 2: `envlist = py{38,39,310,311,312,313,314}-{pycurl,requests,urllib},lint` +- Lines 8-11: `deps = mock; requests: requests; pycurl: pycurl` — installs deps per variant +- Line 13: `commands = make tests` +- Lines 15-20: lint env uses `ruff>=0.4.4` + +**No references to `setup.py` or `pyproject.toml`.** Tox implicitly installs the package using the build backend, which reads pyproject.toml. No changes needed. + +--- + +### 9. `setup.cfg` (14 lines) — OUT OF SCOPE, VERIFIED + +**Content:** +- Line 1: `[bdist_wheel]` (empty section) +- Lines 3-4: `[metadata]` with `description_file = README.md` +- Lines 6-9: `[egg_info]` with tag settings +- Lines 11-14: `[coverage:run]` with `source = Adyen/` (duplicated in pyproject.toml:80-81) + +**No version or metadata that conflicts.** The `description_file = README.md` is a legacy setuptools config, but with `pyproject.toml` having `readme = "README.md"`, this is redundant but harmless. Out of scope per spec. + +--- + +### 10. `Adyen/settings.py` (2 lines) — OUT OF SCOPE, AWARENESS ONLY + +```python +LIB_NAME = "adyen-python-api-library" # line 1 +LIB_VERSION = "15.0.0" # line 2 +``` + +**Used by:** +- `Adyen/client.py:123` — `self.LIB_VERSION = settings.LIB_VERSION` +- `Adyen/client.py:124` — `self.USER_AGENT_SUFFIX = settings.LIB_NAME + "/"` +- `Adyen/client.py:387-388` — used in HTTP headers +- `Adyen/client.py:435` — passed to `lib_version` + +**Stays in `release.yml` version-files.** No changes needed to this file. + +--- + +### 11. `VERSION` (1 line) — OUT OF SCOPE, AWARENESS ONLY + +Content: `15.0.0` (line 1, with trailing newline) + +This is the canonical version source read by the release automation action. Not listed in `version-files` — the action reads it separately to determine the current version. Out of scope. + +--- + +## Test Coverage + +### Test Structure + +The `test/` directory contains 24 test files, all named `*Test.py`. They test API service functionality via mocked HTTP responses (`test/mocks/` directory). + +**No tests exist for:** +- Package metadata correctness +- Import structure / packaging +- Version consistency across files +- `setup.py` behavior + +**Relevant test observations:** +- `test/BaseTest.py` — Base test class, imports `Adyen` package +- All tests use `import Adyen` and `from Adyen import settings` — these import paths are unaffected by the consolidation +- `test/methodNamesTests/checkoutTest.py` — generated test for method names + +**Conclusion:** No existing tests will break from the consolidation. The changes are purely in build/packaging configuration files and CI workflows. The Python test suite tests runtime API behavior, not packaging metadata. + +--- + +## Code References (Consolidated) + +### Files that MUST change (in scope): + +| File | Line(s) | What changes | +|------|---------|--------------| +| `setup.py` | 1-45 (entire file) | Replace with 2-line shim | +| `pyproject.toml` | 2 | `setuptools>=45` → `setuptools>=61.0.0` | +| `pyproject.toml` | after line 32 | Add `[project.urls]` section | +| `pyproject.toml` | after line 46 | Add `[tool.setuptools.packages.find]` section | +| `Makefile` | 2 | Change install to use pyproject.toml extras | +| `.github/workflows/release.yml` | 38 | Remove `setup.py` from `version-files` | +| `.github/workflows/lint.yml` | 30 | Change `hashFiles('**/setup.py')` → `hashFiles('**/pyproject.toml')` | + +### Files verified clean (no changes needed): + +| File | Reason | +|------|--------| +| `.github/workflows/python-ci.yml` | No setup.py references | +| `.github/workflows/pypipublish.yml` | No setup.py references | +| `tox.ini` | No setup.py references; implicitly reads pyproject.toml | +| `setup.cfg` | Legacy config, out of scope | +| `Adyen/settings.py` | Runtime version, out of scope | +| `VERSION` | Release automation source, out of scope | + +### All `setup.py` references in non-PAW files: + +| Location | Reference | Action | +|----------|-----------|--------| +| `setup.py` (itself) | Full metadata file | Replace with shim | +| `.github/workflows/release.yml:38` | `version-files: setup.py pyproject.toml Adyen/settings.py` | Remove `setup.py` | +| `.github/workflows/lint.yml:30` | `hashFiles('**/setup.py')` | Change to `hashFiles('**/pyproject.toml')` | + +No other files in the repository reference `setup.py`. + +--- + +## Architecture Documentation + +### Metadata Flow (Current) + +``` +VERSION ─────────────────────> release-automation-action reads current version +setup.py ────────────────────> release-automation-action bumps version (line 6) +pyproject.toml ──────────────> release-automation-action bumps version (line 7) +Adyen/settings.py ───────────> release-automation-action bumps version (line 2) + +pip install . ───> reads pyproject.toml [project] (modern path) + OR setup.py (legacy path, but pyproject.toml takes precedence when both exist) + +python -m build ─> reads pyproject.toml [build-system], then [project] + +tox ─────────────> installs package (reads pyproject.toml via build backend) + then runs `make tests` +``` + +### Metadata Flow (After Consolidation) + +``` +VERSION ─────────────────────> release-automation-action reads current version +pyproject.toml ──────────────> release-automation-action bumps version (line 7) +Adyen/settings.py ───────────> release-automation-action bumps version (line 2) + +setup.py (shim) ─────────────> delegates to setuptools, which reads pyproject.toml + (exists only for backward compat with very old pip) + +pip install . ───> reads pyproject.toml [project] (sole source) +python -m build ─> reads pyproject.toml (sole source) +tox ─────────────> same as before, pyproject.toml is authoritative +``` + +### Key Discrepancies Between Current setup.py and pyproject.toml + +| Field | setup.py | pyproject.toml | Notes | +|-------|----------|----------------|-------| +| `install_requires` / `dependencies` | `[]` (empty) | `["pydantic>=2.0"]` | **BUG** — the discrepancy that motivated this work | +| `long_description` | Hardcoded string | `readme = "README.md"` | pyproject.toml uses full README — an improvement | +| `url` | `"https://github.com/Adyen/adyen-python-api-library"` | Not present | Must add `[project.urls]` | +| Package discovery | `find_packages(include=["Adyen*"], exclude=["tests", "tests.*"])` | Not present | Must add `[tool.setuptools.packages.find]` | + +--- + +## Open Questions + +1. **`setup.cfg` cleanup**: The `[coverage:run]` section in `setup.cfg:11-14` duplicates `[tool.coverage.run]` in `pyproject.toml:80-81`. This is harmless but redundant. Not in scope per spec, but flagged for awareness. + +2. **`coveralls` in Makefile**: The current `make install` installs `coveralls` which is not in any pyproject.toml extras group. If `make install` switches to extras-based install, `coveralls` would need to be added to a group or installed separately. Note: `coveralls` is not used in any CI workflow or test command visible in the repo — it may be vestigial. + +3. **`wheel` in build-system.requires**: The current `requires = ["setuptools>=45", "wheel"]` includes `wheel`. Modern setuptools (≥61) does not need `wheel` in build-system.requires (it's handled internally). However, the spec does not call for removing it, so it should stay. diff --git a/.paw/work/471-consolidate-pyproject/ImplementationPlan.md b/.paw/work/471-consolidate-pyproject/ImplementationPlan.md new file mode 100644 index 0000000..f68bf61 --- /dev/null +++ b/.paw/work/471-consolidate-pyproject/ImplementationPlan.md @@ -0,0 +1,118 @@ +# Consolidate Package Metadata into pyproject.toml — Implementation Plan + +## Overview + +Eliminate metadata duplication between `setup.py` and `pyproject.toml` by making `pyproject.toml` the single source of truth (PEP 621). Replace `setup.py` with a minimal shim, add missing sections to `pyproject.toml`, update the Makefile install target, and fix CI workflow references to `setup.py`. + +## Current State Analysis + +Package metadata is fully duplicated between `setup.py` (45 lines) and `pyproject.toml` (81 lines). The duplication already caused a real bug: `install_requires=[]` in `setup.py` while `pyproject.toml` correctly has `dependencies = ["pydantic>=2.0"]` (PR #468). + +`pyproject.toml` is nearly complete — it only lacks `[tool.setuptools.packages.find]` and `[project.urls]` sections. The `build-system.requires` needs a bump from `setuptools>=45` to `setuptools>=61.0.0` for PEP 621 support. + +Two CI workflows reference `setup.py`: `release.yml:38` (version-files) and `lint.yml:30` (cache key hash). The `Makefile:2` install target hardcodes individual packages instead of using pyproject.toml extras. + +No tests verify packaging metadata — all existing tests cover API runtime behavior. + +## Desired End State + +- `pyproject.toml` is the sole, authoritative source of all package metadata +- `setup.py` is a 2-line shim: `from setuptools import setup` / `setup()` +- `make install` uses pyproject.toml extras for development setup +- CI workflows reference `pyproject.toml` instead of `setup.py` where applicable +- All existing tests continue to pass without modification +- `pip install .`, `pip install -e ".[requests,test,dev]"`, and `python -m build` all work correctly + +**Verification approach**: `make install && make tests` in a clean virtualenv; `make lint`; manual inspection of file contents. + +## What We're NOT Doing + +- Removing `setup.py` entirely (kept as shim for backward compatibility) +- Modifying `VERSION` file +- Modifying `setup.cfg` +- Modifying `Adyen/settings.py` +- Updating `tox.ini` to use pyproject.toml extras +- Adding `coveralls` to any extras group +- Removing `wheel` from `build-system.requires` + +## Phase Status + +- [ ] **Phase 1: Metadata Consolidation** — Consolidate all metadata into pyproject.toml and update all dependent files +- [ ] **Phase 2: Documentation** — Create Docs.md technical reference + +## Phase Candidates + + + +--- + +## Phase 1: Metadata Consolidation + +All five file changes are tightly coupled and must be applied atomically — the pyproject.toml additions and setup.py simplification are interdependent. + +### Changes Required: + +- **`setup.py`** (lines 1-45): Replace entire file with minimal shim: + ```python + from setuptools import setup + setup() + ``` + This removes all duplicate metadata (name, version, description, author, dependencies, classifiers, etc.). The shim delegates entirely to setuptools which reads `[project]` from `pyproject.toml`. (FR-002) + +- **`pyproject.toml`**: + - **Line 2**: Bump `requires = ["setuptools>=45", "wheel"]` → `requires = ["setuptools>=61.0.0", "wheel"]` (FR-005) + - **After line 32** (after classifiers closing bracket): Add `[project.urls]` section with Homepage, Repository, Issues (FR-004) + - **After `[project.optional-dependencies]` section** (after line 46): Add `[tool.setuptools.packages.find]` section with `include = ["Adyen*"]` (FR-003) + + The `[project.urls]` placement follows PEP 621 convention — URLs go after the main `[project]` table. The `[tool.setuptools.packages.find]` section goes before `[tool.ruff]` since it's a setuptools concern. + +- **`Makefile`** (line 2): Change install target from `@pip install requests pycurl mock coveralls ruff` to `@pip install -e ".[requests,test,dev]"` (FR-006) + + This installs the project in editable mode with requests, test, and dev extras from pyproject.toml. Intentionally excludes pycurl (requires system libcurl, handled by tox) and drops coveralls (not in any extras; CI-only). + +- **`.github/workflows/release.yml`** (line 38): Change `version-files: setup.py pyproject.toml Adyen/settings.py` → `version-files: pyproject.toml Adyen/settings.py` (FR-007) + + The shim setup.py has no version string, so keeping it in the list is misleading (though harmless — perl `s///` is a no-op on no match). + +- **`.github/workflows/lint.yml`** (line 30): Change `hashFiles('**/setup.py')` → `hashFiles('**/pyproject.toml')` (FR-008) + + After consolidation, `setup.py` is a static 2-line shim that never changes — useless as a cache invalidation key. + +- **Tests**: No test changes needed. All 24 test files test API runtime behavior via mocked HTTP responses. The import paths (`import Adyen`, `from Adyen import settings`) are unaffected. + +### Success Criteria: + +#### Automated Verification: +- [ ] Tests pass: `make tests` +- [ ] Lint passes: `make lint` +- [ ] Editable install works: `pip install -e ".[requests,test,dev]"` succeeds +- [ ] Package build works: `python -m build --sdist --wheel` (if `build` package available) + +#### Manual Verification: +- [ ] `setup.py` contains exactly 2 lines (import + setup call), no metadata arguments +- [ ] `grep -c 'version' setup.py` returns 0 (SC-006) +- [ ] `pyproject.toml` has `[project.urls]`, `[tool.setuptools.packages.find]`, and `setuptools>=61.0.0` +- [ ] `release.yml` version-files does not include `setup.py` +- [ ] `lint.yml` cache key uses `hashFiles('**/pyproject.toml')` + +--- + +## Phase 2: Documentation + +### Changes Required: + +- **`.paw/work/471-consolidate-pyproject/Docs.md`**: Technical reference documenting the consolidation — what changed, why, verification approach, and impact on maintainer workflows. Load `paw-docs-guidance` for template. + +- **No project documentation updates**: This is an internal packaging improvement. The README already describes the library correctly. No user-facing APIs change. No CHANGELOG entry needed (internal chore, not a feature/fix users interact with). + +### Success Criteria: +- [ ] Docs.md accurately captures the as-built state +- [ ] Content is consistent with actual implementation + +--- + +## References + +- Issue: https://github.com/Adyen/adyen-python-api-library/issues/471 +- Spec: `.paw/work/471-consolidate-pyproject/Spec.md` +- Research: `.paw/work/471-consolidate-pyproject/SpecResearch.md`, `.paw/work/471-consolidate-pyproject/CodeResearch.md` From 081a7869e3b68410d6e83728f6c8d2eae1d46649 Mon Sep 17 00:00:00 2001 From: Erdem Tuna Date: Tue, 7 Apr 2026 21:26:15 +0300 Subject: [PATCH 4/8] Strengthen plan success criteria per multi-model review Address feedback from 3-model plan review (GPT-5.4, Claude Opus, GPT-5.3-Codex): - Add explicit SC-001 validation (pip install . + pydantic import) - Add SC-003 built metadata inspection - Remove conditional on build package availability - Make SC-004 explicit (make install && make tests) - Add FR traceability tags to all criteria - Add Phase 2 dependency declaration Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../ImplementationPlan.md | 21 +++++++++++-------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/.paw/work/471-consolidate-pyproject/ImplementationPlan.md b/.paw/work/471-consolidate-pyproject/ImplementationPlan.md index f68bf61..76f189b 100644 --- a/.paw/work/471-consolidate-pyproject/ImplementationPlan.md +++ b/.paw/work/471-consolidate-pyproject/ImplementationPlan.md @@ -48,7 +48,7 @@ No tests verify packaging metadata — all existing tests cover API runtime beha ## Phase 1: Metadata Consolidation -All five file changes are tightly coupled and must be applied atomically — the pyproject.toml additions and setup.py simplification are interdependent. +All five file changes are tightly coupled and must be applied atomically — the pyproject.toml additions and setup.py simplification are interdependent. Covers FR-001 through FR-008. ### Changes Required: @@ -83,22 +83,25 @@ All five file changes are tightly coupled and must be applied atomically — the ### Success Criteria: #### Automated Verification: -- [ ] Tests pass: `make tests` +- [ ] `pip install .` in clean env installs pydantic: `pip install . && python -c "import pydantic"` (SC-001) +- [ ] Editable install with extras: `pip install -e ".[requests,test,dev]"` succeeds and extras available (SC-002) +- [ ] Package build + metadata: `python -m build --sdist --wheel` succeeds; inspect built metadata for correct name, version, dependencies, URLs (SC-003) +- [ ] Full dev workflow: `make install && make tests` succeeds with all tests passing (SC-004) - [ ] Lint passes: `make lint` -- [ ] Editable install works: `pip install -e ".[requests,test,dev]"` succeeds -- [ ] Package build works: `python -m build --sdist --wheel` (if `build` package available) #### Manual Verification: -- [ ] `setup.py` contains exactly 2 lines (import + setup call), no metadata arguments -- [ ] `grep -c 'version' setup.py` returns 0 (SC-006) -- [ ] `pyproject.toml` has `[project.urls]`, `[tool.setuptools.packages.find]`, and `setuptools>=61.0.0` -- [ ] `release.yml` version-files does not include `setup.py` -- [ ] `lint.yml` cache key uses `hashFiles('**/pyproject.toml')` +- [ ] `setup.py` contains exactly 2 lines (import + setup call), no metadata arguments (SC-005) +- [ ] `grep -c 'version' setup.py` returns 0 — no version string in setup.py (SC-006) +- [ ] `pyproject.toml` has `[project.urls]`, `[tool.setuptools.packages.find]`, and `setuptools>=61.0.0` (FR-003, FR-004, FR-005) +- [ ] `release.yml` version-files does not include `setup.py` (FR-007) +- [ ] `lint.yml` cache key uses `hashFiles('**/pyproject.toml')` (FR-008) --- ## Phase 2: Documentation +> Depends on: Phase 1 + ### Changes Required: - **`.paw/work/471-consolidate-pyproject/Docs.md`**: Technical reference documenting the consolidation — what changed, why, verification approach, and impact on maintainer workflows. Load `paw-docs-guidance` for template. From 62fecd7585bcb801e3ad532d5d3dccefbecc8134 Mon Sep 17 00:00:00 2001 From: Erdem Tuna Date: Tue, 7 Apr 2026 21:35:17 +0300 Subject: [PATCH 5/8] Fix spec per cross-artifact review findings - S3: Fix P3 acceptance scenario to match FR-007 (setup.py removed from version-files) - S5: Fix edge case factual error (build-system.requires doesn't apply to python setup.py install) - S4: Reword NFR 'identical metadata' to allow intentional improvements (URLs, long_description) - S1: Add pycurl exclusion assumption (requires system libcurl, tested via tox) Cross-artifact review: 3-model (GPT-5.4, Claude Opus, GPT-5.3-Codex) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .paw/work/471-consolidate-pyproject/Spec.md | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/.paw/work/471-consolidate-pyproject/Spec.md b/.paw/work/471-consolidate-pyproject/Spec.md index 21205fe..b9150ed 100644 --- a/.paw/work/471-consolidate-pyproject/Spec.md +++ b/.paw/work/471-consolidate-pyproject/Spec.md @@ -48,12 +48,12 @@ Narrative: The release automation action triggers a version bump. It updates ver Independent Test: Simulate a version bump by replacing the version string in `pyproject.toml` and `Adyen/settings.py`, build the package, and verify the new version appears in the built distribution metadata. Acceptance Scenarios: -1. Given the release workflow runs, When it processes `version-files`, Then it updates `pyproject.toml` and `Adyen/settings.py` (not `setup.py`) -2. Given setup.py contains only `from setuptools import setup; setup()`, When the release action's perl regex runs against it, Then no error occurs (regex finds no match, which is a no-op) +1. Given the release workflow runs, When it processes `version-files`, Then it updates `pyproject.toml` and `Adyen/settings.py` only (setup.py is not in the list) +2. Given setup.py is not listed in `version-files`, When the release action runs, Then setup.py is not processed at all ### Edge Cases -- **Legacy pip install**: Users with older pip versions that invoke `python setup.py install` — the minimal shim delegates to setuptools which reads pyproject.toml (requires setuptools ≥ 61.0.0 installed, ensured by build-system.requires) +- **Legacy pip install**: Users with older pip versions that invoke `python setup.py install` — the minimal shim delegates to setuptools which reads pyproject.toml. This path requires setuptools ≥ 61.0.0 to be already installed in the user's environment (note: `build-system.requires` only applies via PEP 517 frontends like `pip install`, not direct `python setup.py` invocation). The risk is low — setuptools 61.0.0 was released in March 2022 and is widely available. - **Editable install**: `pip install -e .` works with the shim + pyproject.toml metadata via PEP 660 (setuptools ≥ 64) or legacy mode (shim present) - **tox environments**: tox.ini installs deps directly (not via extras) — unaffected by this change, continues to work as-is - **setup.py with no version**: The release action's perl substitution is a no-op on a file with no version string — no error, no change @@ -74,7 +74,7 @@ Acceptance Scenarios: ### Cross-Cutting / Non-Functional - All existing tests pass without modification after the consolidation -- Package builds (`python -m build`) produce identical metadata to the current release (minus the improved long_description from README) +- Package builds (`python -m build`) produce equivalent core metadata (name, version, dependencies, classifiers) to the current release. The `long_description` (full README) and `project-urls` fields are intentionally improved. - No breaking change for users who `pip install Adyen` from PyPI ## Success Criteria @@ -92,6 +92,7 @@ Acceptance Scenarios: - The `adyen-sdk-automation` code generator does not generate or modify `setup.py` (confirmed by SpecResearch — templates only generate service files) - Keeping `wheel` in `build-system.requires` alongside setuptools (harmless, improves compatibility) - The `coveralls` package previously installed by `make install` is not required for local development (it was a CI-only tool); dropping it from `make install` is acceptable +- Excluding `pycurl` from `make install` is acceptable — it requires system-level libcurl, often fails on developer machines without it, and is adequately tested via tox's pycurl variant ## Scope From 7b70aa50a9edbfbecc7c3d4cf5b56e8ac7eb2e0f Mon Sep 17 00:00:00 2001 From: Erdem Tuna Date: Tue, 7 Apr 2026 21:53:04 +0300 Subject: [PATCH 6/8] chore: consolidate package metadata into pyproject.toml (PEP 621) Make pyproject.toml the single source of truth for all package metadata, eliminating the duplication between setup.py and pyproject.toml that caused the pydantic dependency bug (PR #468). Changes: - Simplify setup.py to minimal 2-line shim (FR-002) - Bump build-system.requires to setuptools>=61.0.0 (FR-005) - Add [project.urls] with Homepage/Repository/Issues (FR-004) - Add [tool.setuptools.packages.find] with include=["Adyen*"] (FR-003) - Update Makefile install to use pyproject.toml extras (FR-006) - Remove setup.py from release.yml version-files (FR-007) - Update lint.yml cache key to hash pyproject.toml (FR-008) Resolves #471 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .github/workflows/lint.yml | 2 +- .github/workflows/release.yml | 2 +- Makefile | 2 +- pyproject.toml | 10 +++++++- setup.py | 47 ++--------------------------------- 5 files changed, 14 insertions(+), 49 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 284d1d8..cfc0f82 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -27,7 +27,7 @@ jobs: uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4 with: path: ~/.cache/pip - key: ${{ runner.os }}-pip-${{ hashFiles('**/setup.py') }} + key: ${{ runner.os }}-pip-${{ hashFiles('**/pyproject.toml') }} restore-keys: | ${{ runner.os }}-pip- diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index a3a0842..09244cb 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -35,7 +35,7 @@ jobs: with: token: ${{ secrets.ADYEN_AUTOMATION_BOT_ACCESS_TOKEN }} develop-branch: main - version-files: setup.py pyproject.toml Adyen/settings.py + version-files: pyproject.toml Adyen/settings.py release-title: Adyen Python API Library pre-release: ${{ inputs.pre-release || false }} github-release: ${{ inputs.github-release || false }} diff --git a/Makefile b/Makefile index 054ffaa..8d1a9c8 100644 --- a/Makefile +++ b/Makefile @@ -1,5 +1,5 @@ install: - @pip install requests pycurl mock coveralls ruff + @pip install -e ".[requests,test,dev]" lint: @ruff check Adyen test diff --git a/pyproject.toml b/pyproject.toml index 410b005..e8dbd84 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,5 @@ [build-system] -requires = ["setuptools>=45", "wheel"] +requires = ["setuptools>=61.0.0", "wheel"] build-backend = "setuptools.build_meta" [project] @@ -31,6 +31,11 @@ classifiers = [ "Programming Language :: Python :: 3.14", ] +[project.urls] +Homepage = "https://github.com/Adyen/adyen-python-api-library" +Repository = "https://github.com/Adyen/adyen-python-api-library" +Issues = "https://github.com/Adyen/adyen-python-api-library/issues" + [project.optional-dependencies] requests = ["requests>=2.25.0"] pycurl = ["pycurl>=7.43.0"] @@ -45,6 +50,9 @@ dev = [ "pre-commit>=3.0.0", ] +[tool.setuptools.packages.find] +include = ["Adyen*"] + [tool.ruff] line-length = 100 target-version = "py38" diff --git a/setup.py b/setup.py index 1640681..8bf1ba9 100644 --- a/setup.py +++ b/setup.py @@ -1,45 +1,2 @@ -from setuptools import find_packages, setup - -setup( - name="Adyen", - packages=find_packages(include=["Adyen*"], exclude=["tests", "tests.*"]), - version="15.0.0", - maintainer="Adyen", - maintainer_email="support@adyen.com", - description="Adyen Python Api", - long_description="A Python client library for accessing Adyen APIs", - author="Adyen", - author_email="support@adyen.com", - url="https://github.com/Adyen/adyen-python-api-library", - keywords=["payments", "adyen", "fintech"], - python_requires=">=3.8", - install_requires=[], - extras_require={ - "requests": ["requests>=2.25.0"], - "pycurl": ["pycurl>=7.43.0"], - "test": [ - "pytest>=7.0.0", - "pytest-cov>=4.0.0", - "mock>=4.0.0", - "requests>=2.25.0", - ], - "dev": [ - "ruff>=0.4.4", - "pre-commit>=3.0.0", - ], - }, - classifiers=[ - "Development Status :: 5 - Production/Stable", - "Intended Audience :: Developers", - "Topic :: Software Development :: Libraries", - "License :: OSI Approved :: MIT License", - "Programming Language :: Python :: 3", - "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", - "Programming Language :: Python :: 3.14", - ], -) +from setuptools import setup +setup() From fcad968071ac738a0e7b0df800a1eaf453ad3125 Mon Sep 17 00:00:00 2001 From: Erdem Tuna Date: Tue, 7 Apr 2026 21:55:38 +0300 Subject: [PATCH 7/8] Add technical documentation (Phase 2) - Docs.md captures as-built state: what changed, why, verification approach - Update ImplementationPlan.md phase status to complete Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .paw/work/471-consolidate-pyproject/Docs.md | 71 +++++++++++++++++++ .../ImplementationPlan.md | 4 +- 2 files changed, 73 insertions(+), 2 deletions(-) create mode 100644 .paw/work/471-consolidate-pyproject/Docs.md diff --git a/.paw/work/471-consolidate-pyproject/Docs.md b/.paw/work/471-consolidate-pyproject/Docs.md new file mode 100644 index 0000000..c0f3edb --- /dev/null +++ b/.paw/work/471-consolidate-pyproject/Docs.md @@ -0,0 +1,71 @@ +# Technical Documentation: Consolidate Package Metadata into pyproject.toml + +**Date**: 2025-07-21 | **Issue**: [#471](https://github.com/Adyen/adyen-python-api-library/issues/471) | **Branch**: feature/471-consolidate-pyproject + +## Summary + +This change eliminates metadata duplication between `setup.py` and `pyproject.toml` by making `pyproject.toml` the single source of truth for all package metadata, following PEP 621. The existing `setup.py` is reduced to a minimal 2-line shim that delegates entirely to setuptools. + +## Motivation + +Package metadata was duplicated across two files, which caused a real bug: `pydantic>=2.0` was listed in `pyproject.toml` dependencies but missing from `setup.py`'s `install_requires` (fixed in PR #468). This consolidation prevents that entire class of sync-related bugs. + +## Changes Made + +### 1. `setup.py` → Minimal Shim + +**Before**: 45-line file with full metadata (name, version, description, author, classifiers, extras, etc.) +**After**: 2-line shim — `from setuptools import setup` / `setup()` + +The shim exists for backward compatibility with legacy build tools that invoke `python setup.py install` directly. Modern pip reads metadata from `pyproject.toml` via PEP 517. + +### 2. `pyproject.toml` — Three Additions + +| Section | Purpose | +|---------|---------| +| `[project.urls]` | Homepage, Repository, Issues links (previously only in setup.py `url` field) | +| `[tool.setuptools.packages.find]` | Package discovery with `include = ["Adyen*"]` (replaces setup.py `find_packages()`) | +| `build-system.requires` bump | `setuptools>=45` → `setuptools>=61.0.0` (minimum for PEP 621 support) | + +### 3. `Makefile` — Install Target + +**Before**: `pip install requests pycurl mock coveralls ruff` (hardcoded individual packages) +**After**: `pip install -e ".[requests,test,dev]"` (editable install using pyproject.toml extras) + +Pycurl is intentionally excluded (requires system libcurl; tested via tox). Coveralls is dropped (CI-only, not in any extras group). + +### 4. CI Workflows + +| File | Change | +|------|--------| +| `release.yml` | Removed `setup.py` from `version-files` (shim has no version to bump) | +| `lint.yml` | Cache key changed from `hashFiles('**/setup.py')` to `hashFiles('**/pyproject.toml')` | + +## Version Synchronization + +After this change, version appears in three files (down from four): + +| File | Field | Bumped By | +|------|-------|-----------| +| `VERSION` | Plain text | Release automation (source of truth) | +| `pyproject.toml` | `version = "X.Y.Z"` | Release automation (perl `s///`) | +| `Adyen/settings.py` | `LIB_VERSION = "X.Y.Z"` | Release automation (perl `s///`) | + +`setup.py` no longer contains a version string. The release automation's perl substitution is a no-op on files with no match. + +## Verification + +| Command | Expected Result | +|---------|-----------------| +| `make install && make tests` | Editable install + all 162 tests pass | +| `make lint` | All checks passed | +| `pip install .` | Installs with pydantic>=2.0 as dependency | +| `python -m build --sdist --wheel` | Builds with correct metadata | +| `grep -c 'version' setup.py` | Returns 0 | + +## Out of Scope + +- `VERSION` file — unchanged, remains release automation source of truth +- `setup.cfg` — unchanged, legacy config (redundant but harmless) +- `Adyen/settings.py` — unchanged, provides runtime version for API headers +- `tox.ini` — unchanged, installs deps directly (future improvement candidate) diff --git a/.paw/work/471-consolidate-pyproject/ImplementationPlan.md b/.paw/work/471-consolidate-pyproject/ImplementationPlan.md index 76f189b..12a936c 100644 --- a/.paw/work/471-consolidate-pyproject/ImplementationPlan.md +++ b/.paw/work/471-consolidate-pyproject/ImplementationPlan.md @@ -37,8 +37,8 @@ No tests verify packaging metadata — all existing tests cover API runtime beha ## Phase Status -- [ ] **Phase 1: Metadata Consolidation** — Consolidate all metadata into pyproject.toml and update all dependent files -- [ ] **Phase 2: Documentation** — Create Docs.md technical reference +- [x] **Phase 1: Metadata Consolidation** — Consolidate all metadata into pyproject.toml and update all dependent files +- [x] **Phase 2: Documentation** — Create Docs.md technical reference ## Phase Candidates From 989f7dc310aa6dfef6fac855de3d15e92f67d76a Mon Sep 17 00:00:00 2001 From: Erdem Tuna Date: Tue, 7 Apr 2026 21:57:36 +0300 Subject: [PATCH 8/8] chore: remove PAW workflow artifacts from tracking MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Artifact lifecycle: commit-and-clean — remove .paw/ from git index before final PR. Artifacts remain on disk for reference. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .gitignore | 3 +- .../471-consolidate-pyproject/CodeResearch.md | 351 -------------- .paw/work/471-consolidate-pyproject/Docs.md | 71 --- .../ImplementationPlan.md | 121 ----- .paw/work/471-consolidate-pyproject/Spec.md | 134 ------ .../471-consolidate-pyproject/SpecResearch.md | 448 ------------------ .../471-consolidate-pyproject/WorkShaping.md | 115 ----- .../WorkflowContext.md | 41 -- 8 files changed, 2 insertions(+), 1282 deletions(-) delete mode 100644 .paw/work/471-consolidate-pyproject/CodeResearch.md delete mode 100644 .paw/work/471-consolidate-pyproject/Docs.md delete mode 100644 .paw/work/471-consolidate-pyproject/ImplementationPlan.md delete mode 100644 .paw/work/471-consolidate-pyproject/Spec.md delete mode 100644 .paw/work/471-consolidate-pyproject/SpecResearch.md delete mode 100644 .paw/work/471-consolidate-pyproject/WorkShaping.md delete mode 100644 .paw/work/471-consolidate-pyproject/WorkflowContext.md diff --git a/.gitignore b/.gitignore index ed819ce..9f1f798 100644 --- a/.gitignore +++ b/.gitignore @@ -10,4 +10,5 @@ venv/ .env test.py build/ -*.egg-info/ \ No newline at end of file +*.egg-info/ +.paw/ diff --git a/.paw/work/471-consolidate-pyproject/CodeResearch.md b/.paw/work/471-consolidate-pyproject/CodeResearch.md deleted file mode 100644 index 5f121f7..0000000 --- a/.paw/work/471-consolidate-pyproject/CodeResearch.md +++ /dev/null @@ -1,351 +0,0 @@ -# Code Research: Consolidate pyproject.toml (PEP 621) - -```yaml -date: 2025-07-21 -git_commit: afa5b8b809c54d8e19fe63fe4fb183dea3f1ddc7 -branch: feature/471-consolidate-pyproject -repository: /home/erdemtuna/workspace/personal/adyen-python-api-library -topic: Consolidate package metadata into pyproject.toml, eliminate setup.py duplication -tags: [pyproject.toml, PEP-621, setup.py, packaging, CI, release-automation] -status: complete -``` - -## Research Question - -Where does package metadata currently live, what references exist to `setup.py` across the codebase, and what exact lines must change to make `pyproject.toml` the sole source of truth? - -## Summary - -Package metadata is **fully duplicated** between `setup.py` (45 lines) and `pyproject.toml` (81 lines). The `pyproject.toml` already contains a complete `[project]` table with all metadata, optional-dependencies, and tool configs. The only things **missing** from `pyproject.toml` are `[tool.setuptools.packages.find]` and `[project.urls]`. Only **two files** outside setup.py reference it: `release.yml:38` (version-files) and `lint.yml:30` (cache key). No tests verify packaging metadata. The `Makefile install` target uses raw `pip install` commands (not extras) — it needs updating per FR-006. - -## Documentation System - -| Item | Status | -|------|--------| -| `docs/` directory | **Does not exist** | -| Sphinx / mkdocs | **Not present** | -| `README.md` | Exists (18,814 bytes) — referenced by `pyproject.toml:9` as `readme = "README.md"` | -| `CONTRIBUTING.md` | Exists (887 bytes) | -| `CODE_OF_CONDUCT.md` | Exists (3,349 bytes) | -| `LICENSE.md` | Exists (1,062 bytes) — MIT License | - -## Verification Commands - -| Command | Purpose | Source | -|---------|---------|--------| -| `make tests` | Run unit tests via `python -m unittest discover -s test -p '*Test.py'` | Makefile:14 | -| `make lint` | Run ruff linter: `ruff check Adyen test` | Makefile:5 | -| `make install` | Install deps: `pip install requests pycurl mock coveralls ruff` | Makefile:2 | -| `tox` | Run full matrix (py38-py314, pycurl/requests/urllib, lint) | tox.ini:2 | -| `pip install -e ".[dev]"` | Install with dev extras (used in lint.yml:37) | .github/workflows/lint.yml:37 | -| `python -m build --sdist --wheel` | Build distributions (used in pypipublish.yml:33-38) | .github/workflows/pypipublish.yml:33-38 | -| `ruff check Adyen test` | Direct ruff invocation | Makefile:5, lint.yml:41 | -| `ruff format --check Adyen test` | Formatter check | lint.yml:45 | - -## Detailed Findings - ---- - -### 1. `setup.py` (45 lines) — IN SCOPE, MUST MODIFY - -**Full content analysis:** - -| Lines | Content | Notes | -|-------|---------|-------| -| 1 | `from setuptools import find_packages, setup` | Imports `find_packages` — not needed in shim | -| 3-45 | `setup(...)` with all kwargs | **All of this becomes the shim** | -| 4 | `name="Adyen"` | Duplicated at pyproject.toml:6 | -| 5 | `packages=find_packages(include=["Adyen*"], exclude=["tests", "tests.*"])` | Replaced by `[tool.setuptools.packages.find]` in pyproject.toml | -| 6 | `version="15.0.0"` | Duplicated at pyproject.toml:7, Adyen/settings.py:2, VERSION:1 | -| 7-8 | `maintainer`/`maintainer_email` | Duplicated at pyproject.toml:14-16 | -| 9-10 | `description`/`long_description` | Duplicated at pyproject.toml:8-9 (pyproject uses README.md) | -| 11-12 | `author`/`author_email` | Duplicated at pyproject.toml:11-13 | -| 13 | `url="https://github.com/Adyen/adyen-python-api-library"` | **NOT in pyproject.toml** — needs `[project.urls]` | -| 14 | `keywords=["payments", "adyen", "fintech"]` | Duplicated at pyproject.toml:17 | -| 15 | `python_requires=">=3.8"` | Duplicated at pyproject.toml:10 | -| 16 | `install_requires=[]` | **Empty!** pyproject.toml:18 has `["pydantic>=2.0"]` — this is the known discrepancy | -| 17-29 | `extras_require={...}` | Duplicated at pyproject.toml:34-46 | -| 31-44 | `classifiers=[...]` | Duplicated at pyproject.toml:19-32 | - -**Target state:** Replace entire file with: -```python -from setuptools import setup -setup() -``` - -**Integration points:** -- `.github/workflows/release.yml:38` — `version-files: setup.py pyproject.toml Adyen/settings.py` -- `.github/workflows/lint.yml:30` — `hashFiles('**/setup.py')` -- No Python code imports from `setup.py` -- `adyen-sdk-automation` does **not** generate `setup.py` (confirmed in SpecResearch) - ---- - -### 2. `pyproject.toml` (81 lines) — IN SCOPE, MUST MODIFY - -**Current sections:** - -| Lines | Section | Status | -|-------|---------|--------| -| 1-3 | `[build-system]` | Exists. `requires = ["setuptools>=45", "wheel"]`. **Must update** `setuptools>=45` → `setuptools>=61.0.0` per FR-005 | -| 5-32 | `[project]` | Complete metadata. Already has name, version, description, readme, requires-python, authors, maintainers, keywords, dependencies, classifiers | -| 34-46 | `[project.optional-dependencies]` | Complete. Has requests, pycurl, test, dev extras | -| 48-60 | `[tool.ruff]` | Exists. Out of scope | -| 62-75 | `[tool.ruff.lint]` | Exists. Out of scope | -| 77-78 | `[tool.ruff.lint.isort]` | Exists. Out of scope | -| 80-81 | `[tool.coverage.run]` | Exists. Out of scope | - -**Missing sections (must add):** - -1. **`[tool.setuptools.packages.find]`** — FR-003. Replaces `setup.py:5` `find_packages(include=["Adyen*"], exclude=["tests", "tests.*"])`. Should be: - ```toml - [tool.setuptools.packages.find] - include = ["Adyen*"] - ``` - -2. **`[project.urls]`** — FR-004. Replaces `setup.py:13` `url=...`. Should be: - ```toml - [project.urls] - Homepage = "https://github.com/Adyen/adyen-python-api-library" - Repository = "https://github.com/Adyen/adyen-python-api-library" - Issues = "https://github.com/Adyen/adyen-python-api-library/issues" - ``` - -**Must change:** -- Line 2: `requires = ["setuptools>=45", "wheel"]` → `requires = ["setuptools>=61.0.0", "wheel"]` - -**Integration points:** -- `.github/workflows/release.yml:38` — listed in `version-files` (stays) -- `.github/workflows/lint.yml:30` — will become the new cache key hash source -- `.github/workflows/lint.yml:37` — `pip install -e ".[dev]"` reads extras from here -- `tox.ini` — tox installs the package, reads metadata from here -- `.github/workflows/pypipublish.yml:33-38` — `python -m build` reads from here - ---- - -### 3. `Makefile` (103 lines) — IN SCOPE, MUST MODIFY - -**Relevant targets:** - -| Lines | Target | Current Command | Issue | -|-------|--------|-----------------|-------| -| 1-2 | `install` | `pip install requests pycurl mock coveralls ruff` | **FR-006**: Should use `pip install -e ".[test,dev,requests,pycurl]"` or equivalent extras-based install | -| 4-5 | `lint` | `ruff check Adyen test` | No change needed | -| 7-8 | `lint-fix` | `ruff check --fix Adyen test` | No change needed | -| 10-11 | `format` | `ruff format Adyen test` | No change needed | -| 13-14 | `tests` | `python -m unittest discover -s test -p '*Test.py'` | No change needed | -| 16-17 | `coverage` | `coverage run -m unittest discover...` | No change needed | -| 20-101 | Generator targets | OpenAPI code gen | Out of scope | - -**Integration points:** -- `tox.ini:13` — `commands = make tests` -- `.github/workflows/lint.yml` — does **not** use `make install`; uses `pip install -e ".[dev]"` directly - ---- - -### 4. `.github/workflows/release.yml` (43 lines) — IN SCOPE, MUST MODIFY - -**Key line:** -- **Line 38**: `version-files: setup.py pyproject.toml Adyen/settings.py` - -This is the `Adyen/release-automation-action@v1.4.0` parameter that lists files to apply version bumps via perl regex substitution. The action reads the current version from the `VERSION` file (or detects it), then runs `perl -i -pe"s/$old/$new/g"` on each listed file. - -**FR-007 change:** Remove `setup.py` from this line → `version-files: pyproject.toml Adyen/settings.py` - -**Context:** The shim `setup.py` will have no version string. While perl's `s///` is a no-op on no-match (harmless), keeping `setup.py` in the list is misleading. - ---- - -### 5. `.github/workflows/lint.yml` (61 lines) — IN SCOPE, MUST MODIFY - -**Key line:** -- **Line 30**: `key: ${{ runner.os }}-pip-${{ hashFiles('**/setup.py') }}` - -**FR-008 change:** Update to `hashFiles('**/pyproject.toml')` since after consolidation, `setup.py` is a static 2-line shim that never changes — useless as a cache invalidation key. - -**Other notable lines (no changes needed):** -- Line 37: `pip install -e ".[dev]"` — already uses pyproject.toml extras. No change. -- Line 41: `ruff check Adyen test` — no change. -- Line 45: `ruff format --check Adyen test` — no change. - ---- - -### 6. `.github/workflows/python-ci.yml` (44 lines) — VERIFICATION ONLY - -**No `setup.py` references found.** This workflow: -- Line 38: `pip install tox` — installs tox -- Line 43: `tox` — runs tox which invokes `make tests` - -Confirmed clean. No changes needed. - ---- - -### 7. `.github/workflows/pypipublish.yml` (49 lines) — VERIFICATION ONLY - -**No `setup.py` references found.** This workflow: -- Line 29-30: `pip install build` — installs build tool -- Line 33-38: `python -m build --sdist --wheel --outdir dist/ .` — builds from pyproject.toml - -Confirmed clean. No changes needed. - ---- - -### 8. `tox.ini` (20 lines) — OUT OF SCOPE, VERIFIED - -**Content analysis:** -- Line 2: `envlist = py{38,39,310,311,312,313,314}-{pycurl,requests,urllib},lint` -- Lines 8-11: `deps = mock; requests: requests; pycurl: pycurl` — installs deps per variant -- Line 13: `commands = make tests` -- Lines 15-20: lint env uses `ruff>=0.4.4` - -**No references to `setup.py` or `pyproject.toml`.** Tox implicitly installs the package using the build backend, which reads pyproject.toml. No changes needed. - ---- - -### 9. `setup.cfg` (14 lines) — OUT OF SCOPE, VERIFIED - -**Content:** -- Line 1: `[bdist_wheel]` (empty section) -- Lines 3-4: `[metadata]` with `description_file = README.md` -- Lines 6-9: `[egg_info]` with tag settings -- Lines 11-14: `[coverage:run]` with `source = Adyen/` (duplicated in pyproject.toml:80-81) - -**No version or metadata that conflicts.** The `description_file = README.md` is a legacy setuptools config, but with `pyproject.toml` having `readme = "README.md"`, this is redundant but harmless. Out of scope per spec. - ---- - -### 10. `Adyen/settings.py` (2 lines) — OUT OF SCOPE, AWARENESS ONLY - -```python -LIB_NAME = "adyen-python-api-library" # line 1 -LIB_VERSION = "15.0.0" # line 2 -``` - -**Used by:** -- `Adyen/client.py:123` — `self.LIB_VERSION = settings.LIB_VERSION` -- `Adyen/client.py:124` — `self.USER_AGENT_SUFFIX = settings.LIB_NAME + "/"` -- `Adyen/client.py:387-388` — used in HTTP headers -- `Adyen/client.py:435` — passed to `lib_version` - -**Stays in `release.yml` version-files.** No changes needed to this file. - ---- - -### 11. `VERSION` (1 line) — OUT OF SCOPE, AWARENESS ONLY - -Content: `15.0.0` (line 1, with trailing newline) - -This is the canonical version source read by the release automation action. Not listed in `version-files` — the action reads it separately to determine the current version. Out of scope. - ---- - -## Test Coverage - -### Test Structure - -The `test/` directory contains 24 test files, all named `*Test.py`. They test API service functionality via mocked HTTP responses (`test/mocks/` directory). - -**No tests exist for:** -- Package metadata correctness -- Import structure / packaging -- Version consistency across files -- `setup.py` behavior - -**Relevant test observations:** -- `test/BaseTest.py` — Base test class, imports `Adyen` package -- All tests use `import Adyen` and `from Adyen import settings` — these import paths are unaffected by the consolidation -- `test/methodNamesTests/checkoutTest.py` — generated test for method names - -**Conclusion:** No existing tests will break from the consolidation. The changes are purely in build/packaging configuration files and CI workflows. The Python test suite tests runtime API behavior, not packaging metadata. - ---- - -## Code References (Consolidated) - -### Files that MUST change (in scope): - -| File | Line(s) | What changes | -|------|---------|--------------| -| `setup.py` | 1-45 (entire file) | Replace with 2-line shim | -| `pyproject.toml` | 2 | `setuptools>=45` → `setuptools>=61.0.0` | -| `pyproject.toml` | after line 32 | Add `[project.urls]` section | -| `pyproject.toml` | after line 46 | Add `[tool.setuptools.packages.find]` section | -| `Makefile` | 2 | Change install to use pyproject.toml extras | -| `.github/workflows/release.yml` | 38 | Remove `setup.py` from `version-files` | -| `.github/workflows/lint.yml` | 30 | Change `hashFiles('**/setup.py')` → `hashFiles('**/pyproject.toml')` | - -### Files verified clean (no changes needed): - -| File | Reason | -|------|--------| -| `.github/workflows/python-ci.yml` | No setup.py references | -| `.github/workflows/pypipublish.yml` | No setup.py references | -| `tox.ini` | No setup.py references; implicitly reads pyproject.toml | -| `setup.cfg` | Legacy config, out of scope | -| `Adyen/settings.py` | Runtime version, out of scope | -| `VERSION` | Release automation source, out of scope | - -### All `setup.py` references in non-PAW files: - -| Location | Reference | Action | -|----------|-----------|--------| -| `setup.py` (itself) | Full metadata file | Replace with shim | -| `.github/workflows/release.yml:38` | `version-files: setup.py pyproject.toml Adyen/settings.py` | Remove `setup.py` | -| `.github/workflows/lint.yml:30` | `hashFiles('**/setup.py')` | Change to `hashFiles('**/pyproject.toml')` | - -No other files in the repository reference `setup.py`. - ---- - -## Architecture Documentation - -### Metadata Flow (Current) - -``` -VERSION ─────────────────────> release-automation-action reads current version -setup.py ────────────────────> release-automation-action bumps version (line 6) -pyproject.toml ──────────────> release-automation-action bumps version (line 7) -Adyen/settings.py ───────────> release-automation-action bumps version (line 2) - -pip install . ───> reads pyproject.toml [project] (modern path) - OR setup.py (legacy path, but pyproject.toml takes precedence when both exist) - -python -m build ─> reads pyproject.toml [build-system], then [project] - -tox ─────────────> installs package (reads pyproject.toml via build backend) - then runs `make tests` -``` - -### Metadata Flow (After Consolidation) - -``` -VERSION ─────────────────────> release-automation-action reads current version -pyproject.toml ──────────────> release-automation-action bumps version (line 7) -Adyen/settings.py ───────────> release-automation-action bumps version (line 2) - -setup.py (shim) ─────────────> delegates to setuptools, which reads pyproject.toml - (exists only for backward compat with very old pip) - -pip install . ───> reads pyproject.toml [project] (sole source) -python -m build ─> reads pyproject.toml (sole source) -tox ─────────────> same as before, pyproject.toml is authoritative -``` - -### Key Discrepancies Between Current setup.py and pyproject.toml - -| Field | setup.py | pyproject.toml | Notes | -|-------|----------|----------------|-------| -| `install_requires` / `dependencies` | `[]` (empty) | `["pydantic>=2.0"]` | **BUG** — the discrepancy that motivated this work | -| `long_description` | Hardcoded string | `readme = "README.md"` | pyproject.toml uses full README — an improvement | -| `url` | `"https://github.com/Adyen/adyen-python-api-library"` | Not present | Must add `[project.urls]` | -| Package discovery | `find_packages(include=["Adyen*"], exclude=["tests", "tests.*"])` | Not present | Must add `[tool.setuptools.packages.find]` | - ---- - -## Open Questions - -1. **`setup.cfg` cleanup**: The `[coverage:run]` section in `setup.cfg:11-14` duplicates `[tool.coverage.run]` in `pyproject.toml:80-81`. This is harmless but redundant. Not in scope per spec, but flagged for awareness. - -2. **`coveralls` in Makefile**: The current `make install` installs `coveralls` which is not in any pyproject.toml extras group. If `make install` switches to extras-based install, `coveralls` would need to be added to a group or installed separately. Note: `coveralls` is not used in any CI workflow or test command visible in the repo — it may be vestigial. - -3. **`wheel` in build-system.requires**: The current `requires = ["setuptools>=45", "wheel"]` includes `wheel`. Modern setuptools (≥61) does not need `wheel` in build-system.requires (it's handled internally). However, the spec does not call for removing it, so it should stay. diff --git a/.paw/work/471-consolidate-pyproject/Docs.md b/.paw/work/471-consolidate-pyproject/Docs.md deleted file mode 100644 index c0f3edb..0000000 --- a/.paw/work/471-consolidate-pyproject/Docs.md +++ /dev/null @@ -1,71 +0,0 @@ -# Technical Documentation: Consolidate Package Metadata into pyproject.toml - -**Date**: 2025-07-21 | **Issue**: [#471](https://github.com/Adyen/adyen-python-api-library/issues/471) | **Branch**: feature/471-consolidate-pyproject - -## Summary - -This change eliminates metadata duplication between `setup.py` and `pyproject.toml` by making `pyproject.toml` the single source of truth for all package metadata, following PEP 621. The existing `setup.py` is reduced to a minimal 2-line shim that delegates entirely to setuptools. - -## Motivation - -Package metadata was duplicated across two files, which caused a real bug: `pydantic>=2.0` was listed in `pyproject.toml` dependencies but missing from `setup.py`'s `install_requires` (fixed in PR #468). This consolidation prevents that entire class of sync-related bugs. - -## Changes Made - -### 1. `setup.py` → Minimal Shim - -**Before**: 45-line file with full metadata (name, version, description, author, classifiers, extras, etc.) -**After**: 2-line shim — `from setuptools import setup` / `setup()` - -The shim exists for backward compatibility with legacy build tools that invoke `python setup.py install` directly. Modern pip reads metadata from `pyproject.toml` via PEP 517. - -### 2. `pyproject.toml` — Three Additions - -| Section | Purpose | -|---------|---------| -| `[project.urls]` | Homepage, Repository, Issues links (previously only in setup.py `url` field) | -| `[tool.setuptools.packages.find]` | Package discovery with `include = ["Adyen*"]` (replaces setup.py `find_packages()`) | -| `build-system.requires` bump | `setuptools>=45` → `setuptools>=61.0.0` (minimum for PEP 621 support) | - -### 3. `Makefile` — Install Target - -**Before**: `pip install requests pycurl mock coveralls ruff` (hardcoded individual packages) -**After**: `pip install -e ".[requests,test,dev]"` (editable install using pyproject.toml extras) - -Pycurl is intentionally excluded (requires system libcurl; tested via tox). Coveralls is dropped (CI-only, not in any extras group). - -### 4. CI Workflows - -| File | Change | -|------|--------| -| `release.yml` | Removed `setup.py` from `version-files` (shim has no version to bump) | -| `lint.yml` | Cache key changed from `hashFiles('**/setup.py')` to `hashFiles('**/pyproject.toml')` | - -## Version Synchronization - -After this change, version appears in three files (down from four): - -| File | Field | Bumped By | -|------|-------|-----------| -| `VERSION` | Plain text | Release automation (source of truth) | -| `pyproject.toml` | `version = "X.Y.Z"` | Release automation (perl `s///`) | -| `Adyen/settings.py` | `LIB_VERSION = "X.Y.Z"` | Release automation (perl `s///`) | - -`setup.py` no longer contains a version string. The release automation's perl substitution is a no-op on files with no match. - -## Verification - -| Command | Expected Result | -|---------|-----------------| -| `make install && make tests` | Editable install + all 162 tests pass | -| `make lint` | All checks passed | -| `pip install .` | Installs with pydantic>=2.0 as dependency | -| `python -m build --sdist --wheel` | Builds with correct metadata | -| `grep -c 'version' setup.py` | Returns 0 | - -## Out of Scope - -- `VERSION` file — unchanged, remains release automation source of truth -- `setup.cfg` — unchanged, legacy config (redundant but harmless) -- `Adyen/settings.py` — unchanged, provides runtime version for API headers -- `tox.ini` — unchanged, installs deps directly (future improvement candidate) diff --git a/.paw/work/471-consolidate-pyproject/ImplementationPlan.md b/.paw/work/471-consolidate-pyproject/ImplementationPlan.md deleted file mode 100644 index 12a936c..0000000 --- a/.paw/work/471-consolidate-pyproject/ImplementationPlan.md +++ /dev/null @@ -1,121 +0,0 @@ -# Consolidate Package Metadata into pyproject.toml — Implementation Plan - -## Overview - -Eliminate metadata duplication between `setup.py` and `pyproject.toml` by making `pyproject.toml` the single source of truth (PEP 621). Replace `setup.py` with a minimal shim, add missing sections to `pyproject.toml`, update the Makefile install target, and fix CI workflow references to `setup.py`. - -## Current State Analysis - -Package metadata is fully duplicated between `setup.py` (45 lines) and `pyproject.toml` (81 lines). The duplication already caused a real bug: `install_requires=[]` in `setup.py` while `pyproject.toml` correctly has `dependencies = ["pydantic>=2.0"]` (PR #468). - -`pyproject.toml` is nearly complete — it only lacks `[tool.setuptools.packages.find]` and `[project.urls]` sections. The `build-system.requires` needs a bump from `setuptools>=45` to `setuptools>=61.0.0` for PEP 621 support. - -Two CI workflows reference `setup.py`: `release.yml:38` (version-files) and `lint.yml:30` (cache key hash). The `Makefile:2` install target hardcodes individual packages instead of using pyproject.toml extras. - -No tests verify packaging metadata — all existing tests cover API runtime behavior. - -## Desired End State - -- `pyproject.toml` is the sole, authoritative source of all package metadata -- `setup.py` is a 2-line shim: `from setuptools import setup` / `setup()` -- `make install` uses pyproject.toml extras for development setup -- CI workflows reference `pyproject.toml` instead of `setup.py` where applicable -- All existing tests continue to pass without modification -- `pip install .`, `pip install -e ".[requests,test,dev]"`, and `python -m build` all work correctly - -**Verification approach**: `make install && make tests` in a clean virtualenv; `make lint`; manual inspection of file contents. - -## What We're NOT Doing - -- Removing `setup.py` entirely (kept as shim for backward compatibility) -- Modifying `VERSION` file -- Modifying `setup.cfg` -- Modifying `Adyen/settings.py` -- Updating `tox.ini` to use pyproject.toml extras -- Adding `coveralls` to any extras group -- Removing `wheel` from `build-system.requires` - -## Phase Status - -- [x] **Phase 1: Metadata Consolidation** — Consolidate all metadata into pyproject.toml and update all dependent files -- [x] **Phase 2: Documentation** — Create Docs.md technical reference - -## Phase Candidates - - - ---- - -## Phase 1: Metadata Consolidation - -All five file changes are tightly coupled and must be applied atomically — the pyproject.toml additions and setup.py simplification are interdependent. Covers FR-001 through FR-008. - -### Changes Required: - -- **`setup.py`** (lines 1-45): Replace entire file with minimal shim: - ```python - from setuptools import setup - setup() - ``` - This removes all duplicate metadata (name, version, description, author, dependencies, classifiers, etc.). The shim delegates entirely to setuptools which reads `[project]` from `pyproject.toml`. (FR-002) - -- **`pyproject.toml`**: - - **Line 2**: Bump `requires = ["setuptools>=45", "wheel"]` → `requires = ["setuptools>=61.0.0", "wheel"]` (FR-005) - - **After line 32** (after classifiers closing bracket): Add `[project.urls]` section with Homepage, Repository, Issues (FR-004) - - **After `[project.optional-dependencies]` section** (after line 46): Add `[tool.setuptools.packages.find]` section with `include = ["Adyen*"]` (FR-003) - - The `[project.urls]` placement follows PEP 621 convention — URLs go after the main `[project]` table. The `[tool.setuptools.packages.find]` section goes before `[tool.ruff]` since it's a setuptools concern. - -- **`Makefile`** (line 2): Change install target from `@pip install requests pycurl mock coveralls ruff` to `@pip install -e ".[requests,test,dev]"` (FR-006) - - This installs the project in editable mode with requests, test, and dev extras from pyproject.toml. Intentionally excludes pycurl (requires system libcurl, handled by tox) and drops coveralls (not in any extras; CI-only). - -- **`.github/workflows/release.yml`** (line 38): Change `version-files: setup.py pyproject.toml Adyen/settings.py` → `version-files: pyproject.toml Adyen/settings.py` (FR-007) - - The shim setup.py has no version string, so keeping it in the list is misleading (though harmless — perl `s///` is a no-op on no match). - -- **`.github/workflows/lint.yml`** (line 30): Change `hashFiles('**/setup.py')` → `hashFiles('**/pyproject.toml')` (FR-008) - - After consolidation, `setup.py` is a static 2-line shim that never changes — useless as a cache invalidation key. - -- **Tests**: No test changes needed. All 24 test files test API runtime behavior via mocked HTTP responses. The import paths (`import Adyen`, `from Adyen import settings`) are unaffected. - -### Success Criteria: - -#### Automated Verification: -- [ ] `pip install .` in clean env installs pydantic: `pip install . && python -c "import pydantic"` (SC-001) -- [ ] Editable install with extras: `pip install -e ".[requests,test,dev]"` succeeds and extras available (SC-002) -- [ ] Package build + metadata: `python -m build --sdist --wheel` succeeds; inspect built metadata for correct name, version, dependencies, URLs (SC-003) -- [ ] Full dev workflow: `make install && make tests` succeeds with all tests passing (SC-004) -- [ ] Lint passes: `make lint` - -#### Manual Verification: -- [ ] `setup.py` contains exactly 2 lines (import + setup call), no metadata arguments (SC-005) -- [ ] `grep -c 'version' setup.py` returns 0 — no version string in setup.py (SC-006) -- [ ] `pyproject.toml` has `[project.urls]`, `[tool.setuptools.packages.find]`, and `setuptools>=61.0.0` (FR-003, FR-004, FR-005) -- [ ] `release.yml` version-files does not include `setup.py` (FR-007) -- [ ] `lint.yml` cache key uses `hashFiles('**/pyproject.toml')` (FR-008) - ---- - -## Phase 2: Documentation - -> Depends on: Phase 1 - -### Changes Required: - -- **`.paw/work/471-consolidate-pyproject/Docs.md`**: Technical reference documenting the consolidation — what changed, why, verification approach, and impact on maintainer workflows. Load `paw-docs-guidance` for template. - -- **No project documentation updates**: This is an internal packaging improvement. The README already describes the library correctly. No user-facing APIs change. No CHANGELOG entry needed (internal chore, not a feature/fix users interact with). - -### Success Criteria: -- [ ] Docs.md accurately captures the as-built state -- [ ] Content is consistent with actual implementation - ---- - -## References - -- Issue: https://github.com/Adyen/adyen-python-api-library/issues/471 -- Spec: `.paw/work/471-consolidate-pyproject/Spec.md` -- Research: `.paw/work/471-consolidate-pyproject/SpecResearch.md`, `.paw/work/471-consolidate-pyproject/CodeResearch.md` diff --git a/.paw/work/471-consolidate-pyproject/Spec.md b/.paw/work/471-consolidate-pyproject/Spec.md deleted file mode 100644 index b9150ed..0000000 --- a/.paw/work/471-consolidate-pyproject/Spec.md +++ /dev/null @@ -1,134 +0,0 @@ -# Feature Specification: Consolidate Package Metadata into pyproject.toml - -**Branch**: feature/471-consolidate-pyproject | **Created**: 2026-04-07 | **Status**: Draft -**Input Brief**: Eliminate metadata duplication between setup.py and pyproject.toml by making pyproject.toml the single source of truth (PEP 621) - -## Overview - -The Adyen Python API Library currently maintains package metadata in two places: `setup.py` and `pyproject.toml`. This duplication has already caused a real bug — the `pydantic>=2.0` dependency was added to `pyproject.toml` but missed in `setup.py` (fixed in PR #468), meaning users installing via certain code paths could get a broken install without pydantic. - -This specification defines the consolidation of all package metadata into `pyproject.toml` as the single authoritative source, following PEP 621 — the Python standard for declaring project metadata in `pyproject.toml`. The existing `setup.py` will be reduced to a minimal shim that delegates entirely to setuptools, which reads metadata from `pyproject.toml`. Supporting files (CI workflows, Makefile) will be updated to reflect the new single source of truth. - -The primary beneficiaries are library maintainers, who will no longer need to keep two files in sync when adding dependencies, updating metadata, or bumping versions. Contributors benefit from a clear canonical location for package configuration. End users benefit from consistent dependency resolution regardless of their installation method. - -## Objectives - -- Eliminate metadata duplication that caused the pydantic dependency bug (Rationale: prevents an entire class of sync-related bugs) -- Establish pyproject.toml as the single source of truth for all package metadata (Rationale: aligns with PEP 621 and modern Python packaging standards) -- Ensure the development setup command (`make install`) installs dependencies from the same source as production builds (Rationale: catches dependency mismatches early) -- Keep CI/CD workflows accurate by removing stale references to setup.py as a metadata source (Rationale: prevents misleading cache keys and no-op version bumps) - -## User Scenarios & Testing - -### User Story P1 – Maintainer adds a new dependency - -Narrative: A maintainer needs to add a new runtime dependency to the library. They add it to `pyproject.toml` under `dependencies` and are confident this single change is sufficient for all install paths — pip install, editable install, tox, and CI builds. - -Independent Test: Add a dependency to `pyproject.toml` `dependencies`, run `pip install .`, and verify the dependency is installed. - -Acceptance Scenarios: -1. Given `pyproject.toml` lists `pydantic>=2.0` in `dependencies`, When a user runs `pip install .`, Then pydantic is installed as a dependency -2. Given `pyproject.toml` lists a new dependency, When setup.py is not updated, Then the dependency is still correctly installed (because setup.py is a shim) -3. Given a maintainer edits only `pyproject.toml`, When `python -m build` runs, Then the built wheel includes the correct dependency metadata - -### User Story P2 – Developer sets up local environment - -Narrative: A developer clones the repository and runs `make install` to set up their local development environment. All test, dev, and runtime dependencies are installed from pyproject.toml extras, and the project is available in editable mode. - -Independent Test: Run `make install` in a fresh virtualenv and verify the project is importable and test dependencies are available. - -Acceptance Scenarios: -1. Given a clean virtualenv, When the developer runs `make install`, Then the Adyen package is installed in editable mode with test and dev dependencies -2. Given `make install` has been run, When the developer runs `make tests`, Then tests execute successfully with all required dependencies present - -### User Story P3 – Release automation bumps version - -Narrative: The release automation action triggers a version bump. It updates version strings in `pyproject.toml` and `Adyen/settings.py` without attempting to modify `setup.py` (which no longer contains a version). - -Independent Test: Simulate a version bump by replacing the version string in `pyproject.toml` and `Adyen/settings.py`, build the package, and verify the new version appears in the built distribution metadata. - -Acceptance Scenarios: -1. Given the release workflow runs, When it processes `version-files`, Then it updates `pyproject.toml` and `Adyen/settings.py` only (setup.py is not in the list) -2. Given setup.py is not listed in `version-files`, When the release action runs, Then setup.py is not processed at all - -### Edge Cases - -- **Legacy pip install**: Users with older pip versions that invoke `python setup.py install` — the minimal shim delegates to setuptools which reads pyproject.toml. This path requires setuptools ≥ 61.0.0 to be already installed in the user's environment (note: `build-system.requires` only applies via PEP 517 frontends like `pip install`, not direct `python setup.py` invocation). The risk is low — setuptools 61.0.0 was released in March 2022 and is widely available. -- **Editable install**: `pip install -e .` works with the shim + pyproject.toml metadata via PEP 660 (setuptools ≥ 64) or legacy mode (shim present) -- **tox environments**: tox.ini installs deps directly (not via extras) — unaffected by this change, continues to work as-is -- **setup.py with no version**: The release action's perl substitution is a no-op on a file with no version string — no error, no change - -## Requirements - -### Functional Requirements - -- FR-001: pyproject.toml `[project]` table is the sole source of package metadata (name, version, description, dependencies, classifiers, etc.) (Stories: P1, P3) -- FR-002: setup.py contains only `from setuptools import setup; setup()` with no metadata arguments (Stories: P1, P2) -- FR-003: pyproject.toml includes `[tool.setuptools.packages.find]` configuration for package discovery (Stories: P1) -- FR-004: pyproject.toml includes `[project.urls]` with Homepage, Repository, and Issues links (Stories: P1) -- FR-005: `build-system.requires` specifies `setuptools>=61.0.0` (minimum for PEP 621 support) (Stories: P1, P2) -- FR-006: `make install` installs the project in editable mode using pyproject.toml extras (Stories: P2) -- FR-007: Release workflow `version-files` no longer includes setup.py (Stories: P3) -- FR-008: CI lint workflow cache key uses pyproject.toml hash instead of setup.py hash (Stories: P2) - -### Cross-Cutting / Non-Functional - -- All existing tests pass without modification after the consolidation -- Package builds (`python -m build`) produce equivalent core metadata (name, version, dependencies, classifiers) to the current release. The `long_description` (full README) and `project-urls` fields are intentionally improved. -- No breaking change for users who `pip install Adyen` from PyPI - -## Success Criteria - -- SC-001: `pip install .` in a clean environment installs `pydantic>=2.0` as a dependency (FR-001) -- SC-002: `pip install -e ".[requests,test,dev]"` succeeds and all extras are available (FR-001, FR-003, FR-006) -- SC-003: `python -m build --sdist --wheel` succeeds and the built metadata shows correct name, version, dependencies, and URLs (FR-001, FR-003, FR-004, FR-005) -- SC-004: `make install && make tests` succeeds with all tests passing (FR-006) -- SC-005: setup.py contains exactly two lines: an import and a setup() call with no arguments (FR-002) -- SC-006: `grep -c 'version' setup.py` returns 0 — no version string in setup.py (FR-002, FR-007) - -## Assumptions - -- The `release-automation-action` perl-based version substitution gracefully handles files where no match is found (confirmed: perl `s///` with no match is a no-op) -- The `adyen-sdk-automation` code generator does not generate or modify `setup.py` (confirmed by SpecResearch — templates only generate service files) -- Keeping `wheel` in `build-system.requires` alongside setuptools (harmless, improves compatibility) -- The `coveralls` package previously installed by `make install` is not required for local development (it was a CI-only tool); dropping it from `make install` is acceptable -- Excluding `pycurl` from `make install` is acceptable — it requires system-level libcurl, often fails on developer machines without it, and is adequately tested via tox's pycurl variant - -## Scope - -In Scope: -- Simplify setup.py to minimal shim -- Add `[tool.setuptools.packages.find]` to pyproject.toml -- Add `[project.urls]` to pyproject.toml -- Bump `build-system.requires` to `setuptools>=61.0.0` -- Update Makefile `install` target to use pyproject.toml extras -- Update `release.yml` to remove setup.py from version-files -- Update `lint.yml` cache key from setup.py to pyproject.toml - -Out of Scope: -- Removing setup.py entirely (kept as shim for backward compatibility) -- Modifying `VERSION` file -- Modifying `setup.cfg` -- Modifying `Adyen/settings.py` -- Updating `tox.ini` to use pyproject.toml extras (future improvement) -- Adding `coveralls` to pyproject.toml extras - -## Dependencies - -- setuptools ≥ 61.0.0 (for PEP 621 `[project]` table support) — released March 2022, widely available -- Adyen release-automation-action v1.4.0 (must handle version-files update) - -## Risks & Mitigations - -- **Release automation breaks with updated version-files**: The perl substitution is a simple find-and-replace that operates on the file list. Removing setup.py from the list is a safe reduction. Mitigation: The VERSION file (source of truth for the action) and Adyen/settings.py remain in the list unchanged. -- **Code generation overwrites setup.py**: Confirmed by research that adyen-sdk-automation does NOT touch setup.py or pyproject.toml. Mitigation: None needed; risk is eliminated. -- **Legacy build tools fail**: Some very old tools may not support pyproject.toml. Mitigation: The minimal setup.py shim ensures backward compatibility with any tool that invokes `python setup.py install`. - -## References - -- Issue: https://github.com/Adyen/adyen-python-api-library/issues/471 -- PR #468: Fixed pydantic dependency in pyproject.toml (the bug that motivated this issue) -- PEP 621: https://peps.python.org/pep-0621/ -- Python Packaging Authority migration guide: https://packaging.python.org/en/latest/guides/modernize-setup-py-project/ -- Research: .paw/work/471-consolidate-pyproject/SpecResearch.md -- Work Shaping: .paw/work/471-consolidate-pyproject/WorkShaping.md diff --git a/.paw/work/471-consolidate-pyproject/SpecResearch.md b/.paw/work/471-consolidate-pyproject/SpecResearch.md deleted file mode 100644 index d761a3c..0000000 --- a/.paw/work/471-consolidate-pyproject/SpecResearch.md +++ /dev/null @@ -1,448 +0,0 @@ -# Spec Research: Consolidate Pyproject Metadata - -## 1. Python Packaging Standards - -### PEP 621 — `[project]` Table Fields - -PEP 621 standardises project metadata in `pyproject.toml`. The only **required** field is `name`. Key available fields: - -| Field | Type | Notes | -|---|---|---| -| `name` | string | **Required** | -| `version` | string | Static, or declared `dynamic` | -| `description` | string | One-line summary | -| `readme` | string / table | Path to README; setuptools auto-sets `long_description` and `long_description_content_type` | -| `requires-python` | string | e.g. `">=3.8"` | -| `license` | SPDX / table | e.g. `{text = "MIT"}` or SPDX expression | -| `authors` | list of tables | `[{name = "...", email = "..."}]` | -| `maintainers` | list of tables | Same structure | -| `keywords` | list of strings | | -| `classifiers` | list of strings | Trove classifiers | -| `urls` | table | `[project.urls]` section | -| `dependencies` | list of strings | PEP 508 format | -| `optional-dependencies` | table of lists | `[project.optional-dependencies]` | -| `dynamic` | list of strings | Fields resolved at build time | - -### setuptools ≥ 61.0.0 Behaviour - -- **First version** of setuptools to read PEP 621 `[project]` metadata from `pyproject.toml`. -- When both `setup.py` and `pyproject.toml` define metadata, **`pyproject.toml` takes precedence** for fields defined in `[project]`. Any remaining setup.py kwargs are merged in. -- With a **minimal shim** (`from setuptools import setup; setup()`), setuptools will read all metadata from `pyproject.toml`. The shim adds no conflicting metadata. - -### `[tool.setuptools.packages.find]` Syntax - -```toml -[tool.setuptools.packages.find] -include = ["Adyen*"] # glob patterns (NOT regex) -exclude = ["test*", "tests*"] # glob patterns -``` - -- `where` (default `["."]`): directories to search. -- `include`: if set, **only** packages matching these globs are included. -- `exclude`: packages matching these globs are excluded from the result. -- `namespaces` (default `true`): set `false` to require `__init__.py`. - -### `readme` Field Behaviour - -When `readme = "README.md"` is in `[project]`: -- setuptools sets `long_description` from the file content. -- `long_description_content_type` is auto-detected from the file extension (`.md` → `text/markdown`). -- This **replaces** the current `setup.py` hardcoded string `"A Python client library for accessing Adyen APIs"` with the full README content — **an improvement**. - -### Minimal `setup.py` Shim Compatibility - -- **pip ≥ 21.3 + setuptools ≥ 64**: PEP 660 editable installs (`pip install -e .`) work **without any setup.py**. -- **Older pip**: a `setup.py` shim (`from setuptools import setup; setup()`) is needed for `pip install -e .`. -- The shim pattern is **explicitly recommended** by the [Python Packaging Authority migration guide](https://packaging.python.org/en/latest/guides/modernize-setup-py-project/). -- Keeping the shim ensures compatibility with legacy environments, CI/CD tools, and the `adyen-sdk-automation` build system. - -### Edge Case: Editable Installs - -With `setuptools>=61.0` specified in `build-system.requires`: -- `pip install -e .` works with `pyproject.toml`-only metadata + a minimal shim. -- The `[tool.setuptools.packages.find]` section ensures correct package discovery during editable installs. -- The `lint.yml` workflow already uses `pip install -e ".[dev]"` — this will continue to work. - ---- - -## 2. Current Codebase State - -### `setup.py` — Current State - -```python -from setuptools import find_packages, setup - -setup( - name="Adyen", - packages=find_packages(include=["Adyen*"], exclude=["tests", "tests.*"]), - version="15.0.0", - maintainer="Adyen", - maintainer_email="support@adyen.com", - description="Adyen Python Api", - long_description="A Python client library for accessing Adyen APIs", - author="Adyen", - author_email="support@adyen.com", - url="https://github.com/Adyen/adyen-python-api-library", - keywords=["payments", "adyen", "fintech"], - python_requires=">=3.8", - install_requires=[], - extras_require={ - "requests": ["requests>=2.25.0"], - "pycurl": ["pycurl>=7.43.0"], - "test": ["pytest>=7.0.0", "pytest-cov>=4.0.0", "mock>=4.0.0", "requests>=2.25.0"], - "dev": ["ruff>=0.4.4", "pre-commit>=3.0.0"], - }, - classifiers=[...], -) -``` - -**Key observations:** -- `install_requires=[]` — **empty**. Missing `pydantic>=2.0` which IS in `pyproject.toml`. This is the bug described in issue #471 (fixed in PR #468 for `pyproject.toml` only). -- `long_description` is a static string, not README content. -- `url` is defined but no `project_urls` dict (which maps to `[project.urls]`). -- `find_packages(include=["Adyen*"], exclude=["tests", "tests.*"])` — note exclude uses `"tests"` and `"tests.*"` but the actual test directory is named `test` (singular). - -**What gets removed:** Everything except `from setuptools import setup; setup()`. - -### `pyproject.toml` — Current State - -```toml -[build-system] -requires = ["setuptools>=45", "wheel"] -build-backend = "setuptools.build_meta" - -[project] -name = "Adyen" -version = "15.0.0" -description = "Adyen Python Api" -readme = "README.md" -requires-python = ">=3.8" -authors = [{name = "Adyen", email = "support@adyen.com"}] -maintainers = [{name = "Adyen", email = "support@adyen.com"}] -keywords = ["payments", "adyen", "fintech"] -dependencies = ["pydantic>=2.0"] -classifiers = [...] - -[project.optional-dependencies] -requests = ["requests>=2.25.0"] -pycurl = ["pycurl>=7.43.0"] -test = ["pytest>=7.0.0", "pytest-cov>=4.0.0", "mock>=4.0.0", "requests>=2.25.0"] -dev = ["ruff>=0.4.4", "pre-commit>=3.0.0"] - -[tool.ruff] -... - -[tool.coverage.run] -source = ["Adyen/"] -``` - -**What's already there:** Most metadata is already present and correct. - -**What needs adding/changing:** -1. `build-system.requires` → bump from `"setuptools>=45"` to `"setuptools>=61.0.0"` -2. Add `[tool.setuptools.packages.find]` section -3. Add `[project.urls]` section -4. Optionally remove `"wheel"` from `build-system.requires` (modern pip/setuptools include it automatically, but keeping it is harmless) - -### `setup.cfg` — Current State (Out of Scope) - -```ini -[bdist_wheel] - -[metadata] -description_file = README.md - -[egg_info] -tag_build = -tag_date = 0 -tag_svn_revision = 0 - -[coverage:run] -source = - Adyen/ -``` - -**Awareness notes:** -- The `[metadata] description_file` is a legacy setuptools-specific field. With `readme = "README.md"` in `pyproject.toml`, this becomes redundant but harmless. -- The `[coverage:run]` section duplicates `[tool.coverage.run]` in `pyproject.toml`. Out of scope but noted. -- Not modifying this file per the work shaping decision. - -### `Makefile` — Current Install Target - -```makefile -install: - @pip install requests pycurl mock coveralls ruff -``` - -**Issues:** -- Hardcodes individual package names instead of using the project's dependency groups. -- Doesn't install the project itself. -- Doesn't use `pyproject.toml`-defined extras. -- Includes `coveralls` which isn't in any extras group. - -**Proposed replacement:** -```makefile -install: - @pip install -e ".[requests,test,dev]" -``` - -This: -- Installs the project in editable mode -- Pulls in `requests`, `test`, and `dev` extras from `pyproject.toml` -- Does NOT include `pycurl` (requires system-level `libcurl` — handled by tox separately) -- Does NOT include `coveralls` (not in any extras; consider adding to `test` extras or keeping separate) - -### `Adyen/settings.py` — Runtime Version (Out of Scope) - -```python -LIB_NAME = "adyen-python-api-library" -LIB_VERSION = "15.0.0" -``` - -Used by `Adyen/client.py` for: -- HTTP headers: `adyen-library-name` and `adyen-library-version` -- User-Agent suffix -- These are **runtime values** sent to Adyen APIs, not packaging metadata. -- The release-automation-action bumps this file via the perl regex substitution. -- Out of scope per work shaping decision. - -### `VERSION` File — Current Content (Out of Scope) - -``` -15.0.0 -``` - -- The release-automation-action reads this via `cat VERSION` to get the current version. -- Used as the source of truth for the release automation. -- Out of scope per work shaping decision. - -### `.github/workflows/release.yml` - -```yaml -- name: Bump - run: | - perl -i -pe 's/${{steps.current-version.outputs.current-version}}/${{steps.release.outputs.next-version}}/' VERSION ${{ inputs.version-files }} -``` - -With `version-files: setup.py pyproject.toml Adyen/settings.py`: -- The perl command does a **literal string substitution** of the old version with the new version across all listed files. -- For `setup.py`, it currently matches `version="15.0.0"` on line 6. -- **After our change**, `setup.py` will be `from setuptools import setup\nsetup()` — **no version string present**. The perl command will simply not match anything in `setup.py`, which is harmless (perl `s///` with no match is a no-op). -- However, keeping `setup.py` in `version-files` is misleading. It should be **removed** from `version-files`. - -**⚠️ REQUIRED CHANGE:** `version-files` line must be updated to remove `setup.py`: -```yaml -version-files: pyproject.toml Adyen/settings.py -``` - -### `.github/workflows/pypipublish.yml` - -```yaml -- name: Install pypa/build - run: python -m pip install build --user -- name: Build a binary wheel and a source tarball - run: python -m build --sdist --wheel --outdir dist/ . -``` - -- Uses `python -m build` which reads `pyproject.toml` for metadata. -- **No changes needed.** This workflow already works with `pyproject.toml` as the metadata source. - -### `.github/workflows/python-ci.yml` - -```yaml -- name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install tox - sudo apt-get update - sudo apt install libcurl4-openssl-dev -- name: Test with tox - run: tox -``` - -- Installs `tox` and runs tests. Does not reference `setup.py` directly. -- **No changes needed.** - -### `.github/workflows/lint.yml` - -```yaml -key: ${{ runner.os }}-pip-${{ hashFiles('**/setup.py') }} -``` - -- Uses `hashFiles('**/setup.py')` for pip cache key. -- **⚠️ SHOULD UPDATE** to `hashFiles('**/pyproject.toml')` since `pyproject.toml` is now the source of truth for dependencies. After our change, `setup.py` will never change (it's a static shim), making it useless as a cache key. - -Also in `lint.yml`: -```yaml -- name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install -e ".[dev]" -``` - -- Already uses `pip install -e ".[dev]"` — this works perfectly with pyproject.toml extras. **No changes needed** for this part. - -### `tox.ini` - -```ini -[testenv] -deps = - mock - requests: requests - pycurl: pycurl -commands = - make tests -``` - -- Does **not** use extras from pyproject.toml — installs deps directly. -- **No changes proposed** in this issue. Could be a future improvement to use `pip install .[test,requests]` etc. - ---- - -## 3. Risk Analysis - -### 3.1 Release Automation — `version-files` with Minimal setup.py - -**Mechanism:** The `Adyen/release-automation-action@v1.4.0` (pinned to SHA `3e5694d...`): -1. Reads current version from `cat VERSION` -2. Computes next version from PR labels -3. Runs: `perl -i -pe 's/OLD_VERSION/NEW_VERSION/' VERSION ${{ inputs.version-files }}` -4. Creates a PR with the bumped files - -**Risk assessment:** -- If `setup.py` contains only `from setuptools import setup\nsetup()`, the perl regex `s/15.0.0/16.0.0/` will find **no match** — this is a **no-op**, not an error. Perl `s///` with no match simply leaves the file unchanged. -- **Impact: NONE functionally**, but the file will be needlessly processed. -- **Recommendation:** Remove `setup.py` from `version-files` in `release.yml` to keep the workflow clean and avoid confusion. -- **Updated line:** `version-files: pyproject.toml Adyen/settings.py` - -### 3.2 SDK Automation (Code Generation) - -**Finding:** The `adyen-sdk-automation` repo's `python/build.gradle.kts` does **NOT** generate or modify `setup.py`. It only: -- Generates service files into `repo/Adyen/services//` -- Copies `__init__.py` files for services -- Uses mustache templates for API classes only - -The local `Makefile` generator section (lines 20-101) also only generates into `Adyen/services/` — no `setup.py` involvement. - -**Templates directory** (`templates/`): Contains only API mustache templates (`api-single.mustache`, `api-small.mustache`, etc.) and `config.yaml`. **No setup.py template exists.** - -**Risk: NONE.** The code generation process is completely independent of packaging metadata. - -### 3.3 Files Referencing setup.py - -| File | Reference | Impact | -|---|---|---| -| `.github/workflows/release.yml:38` | `version-files: setup.py pyproject.toml Adyen/settings.py` | Must remove `setup.py` | -| `.github/workflows/lint.yml:30` | `hashFiles('**/setup.py')` | Should update to `hashFiles('**/pyproject.toml')` | -| `setup.py` itself | `from setuptools import find_packages, setup` | Simplify to `from setuptools import setup; setup()` | - -No other files import from or reference `setup.py`. - -### 3.4 Dependency Discrepancy - -**Current state:** -- `pyproject.toml` has `dependencies = ["pydantic>=2.0"]` -- `setup.py` has `install_requires=[]` (empty!) - -**After consolidation:** `pyproject.toml` is authoritative. The empty `install_requires` in the shim `setup.py` is harmless because modern setuptools reads `dependencies` from `[project]` and does **not merge** `install_requires` from `setup()` when `[project].dependencies` is defined. - -**Risk: NONE after consolidation.** Actually resolves the discrepancy. - -### 3.5 Package Discovery - -**Current `setup.py`:** `find_packages(include=["Adyen*"], exclude=["tests", "tests.*"])` -- Note: The exclude pattern says `"tests"` (plural) but the actual directory is `test` (singular). The exclude pattern was **never matching** the real test directory anyway. -- The `include=["Adyen*"]` pattern is the effective filter. - -**Proposed `pyproject.toml`:** -```toml -[tool.setuptools.packages.find] -include = ["Adyen*"] -exclude = ["test*"] -``` - -The `test` directory has a `__init__.py`, making it a discoverable package. But `include = ["Adyen*"]` alone is sufficient to exclude it, since `test` doesn't match `Adyen*`. Adding `exclude = ["test*"]` is a belt-and-suspenders approach. - -### 3.6 Version Synchronisation Across Files - -After this change, version appears in these files (all still bumped by the release action): -- `VERSION` — source of truth for release action (`cat VERSION`) -- `pyproject.toml` — `version = "15.0.0"` in `[project]` -- `Adyen/settings.py` — `LIB_VERSION = "15.0.0"` for runtime headers - -The `setup.py` shim will **no longer contain a version string**, which is correct because `pyproject.toml` is the authoritative source. - ---- - -## 4. Ecosystem Patterns - -### Other Adyen Libraries - -A GitHub code search for `pyproject.toml` + `[tool.setuptools.packages.find]` or `[project.urls]` across the Adyen org returned **no results**. This Python library appears to be the **first Adyen SDK** to adopt PEP 621 consolidation. - -The `release-automation-action` is used across multiple Adyen repos but its `version-files` input is always a space-separated list of files — the action is agnostic to file format and uses simple string replacement. - -### Industry Standard - -The pattern of `pyproject.toml` as single source of truth with a minimal `setup.py` shim is **widely adopted** in the Python ecosystem: -- The Python Packaging Authority has an [official migration guide](https://packaging.python.org/en/latest/guides/modernize-setup-py-project/) -- Major projects (Django, Flask, requests, etc.) have migrated or are migrating -- `setuptools>=61.0` has been available since March 2022 — well-established - ---- - -## 5. Key Findings & Recommendations - -### Critical Actions (Must Do) - -1. **Simplify `setup.py`** to `from setuptools import setup; setup()` — removes all duplicate metadata. - -2. **Add `[tool.setuptools.packages.find]`** to `pyproject.toml`: - ```toml - [tool.setuptools.packages.find] - include = ["Adyen*"] - exclude = ["test*"] - ``` - -3. **Bump `build-system.requires`** from `"setuptools>=45"` to `"setuptools>=61.0.0"`. - -4. **Add `[project.urls]`**: - ```toml - [project.urls] - Homepage = "https://github.com/Adyen/adyen-python-api-library" - Repository = "https://github.com/Adyen/adyen-python-api-library" - Issues = "https://github.com/Adyen/adyen-python-api-library/issues" - ``` - -5. **Update `Makefile` install target**: - ```makefile - install: - @pip install -e ".[requests,test,dev]" - ``` - -6. **Update `release.yml`** — remove `setup.py` from `version-files`: - ```yaml - version-files: pyproject.toml Adyen/settings.py - ``` - -7. **Update `lint.yml`** — change cache key from `hashFiles('**/setup.py')` to `hashFiles('**/pyproject.toml')`. - -### Findings That Align with Work Shaping - -- All shaping decisions are sound and feasible. -- The minimal shim approach is the recommended Python packaging migration pattern. -- No contradictions found with any shaping decision. - -### Additional Findings (Not Contradictions, but Worth Noting) - -1. **`setup.cfg` redundancy:** The `[metadata] description_file = README.md` and `[coverage:run]` sections duplicate what's in `pyproject.toml`. Out of scope per shaping, but worth a follow-up issue. - -2. **`install_requires` bug was real:** `setup.py` had `install_requires=[]` while `pyproject.toml` had `dependencies = ["pydantic>=2.0"]`. The consolidation fully resolves this class of bug. - -3. **Existing `setup.py` exclude pattern was wrong:** `exclude=["tests", "tests.*"]` never matched the `test/` directory. The new `pyproject.toml` config with `include = ["Adyen*"]` is more correct. - -4. **`coveralls` in old Makefile:** The current `Makefile` installs `coveralls` but it's not in any extras group. The new install target drops it. If coverage upload is needed, it should be added to `test` extras in a follow-up. - -5. **`wheel` in build-system.requires:** `"wheel"` can optionally be removed from `build-system.requires` since modern pip (≥21.0) includes it. However, keeping it is harmless and improves compatibility. **Recommendation: keep it.** - -6. **tox.ini doesn't use extras:** The `tox.ini` installs deps manually rather than via `pip install .[test]`. This is out of scope but could be a future improvement. diff --git a/.paw/work/471-consolidate-pyproject/WorkShaping.md b/.paw/work/471-consolidate-pyproject/WorkShaping.md deleted file mode 100644 index a951ac9..0000000 --- a/.paw/work/471-consolidate-pyproject/WorkShaping.md +++ /dev/null @@ -1,115 +0,0 @@ -# Work Shaping: Consolidate Package Metadata into pyproject.toml (PEP 621) - -**Issue**: [#471](https://github.com/Adyen/adyen-python-api-library/issues/471) -**Origin**: Follow-up from PR #468 which fixed `pydantic>=2.0` missing from `setup.py` - -## Problem Statement - -Package metadata is duplicated across `setup.py` and `pyproject.toml`, leading to drift. The `pydantic>=2.0` dependency was added to `pyproject.toml` but missed in `setup.py` (PR #468). The root cause is having two sources of truth for the same information. - -**Who benefits**: Maintainers (fewer places to update), contributors (clear canonical location), and users (consistent dependency resolution regardless of install method). - -## Work Breakdown - -### Core Work - -1. **Add `[tool.setuptools.packages.find]` to `pyproject.toml`** - - Include `Adyen*`, exclude `tests` and `tests.*` - - This replaces the `find_packages()` call in `setup.py` - -2. **Add `[project.urls]` to `pyproject.toml`** - - Homepage, Repository, Issues links - - Replaces the `url=` parameter lost from `setup.py` - -3. **Bump `build-system.requires` to `setuptools>=61.0.0`** - - Required for PEP 621 (`[project]` table) support - - Released March 2022, well-established; pip build isolation installs it regardless - -4. **Simplify `setup.py` to minimal shim** - ```python - from setuptools import setup - setup() - ``` - - All metadata now comes from `pyproject.toml` - - Kept (not removed) because release automation (`release-automation-action`) targets it as a version file - -5. **Update Makefile `install` target** - - Change from hardcoded `pip install requests pycurl mock coveralls ruff` - - To `pip install -e ".[test,dev,requests,pycurl]"` using pyproject.toml extras - - Single source of truth for dependencies - -### Out of Scope (Explicit Decisions) - -- **`VERSION` file**: Left as-is. Not used by release automation, not hurting anything. -- **`setup.cfg`**: Left as-is. Contains some legacy config, but narrowing scope to the issue's intent. -- **`Adyen/settings.py`**: Must keep `LIB_VERSION` — used at runtime for API headers. Cannot be eliminated without changing how the client reads version. -- **Removing `setup.py` entirely**: Too disruptive to release automation config; minimal shim achieves the deduplication goal. - -## Edge Cases & Expected Handling - -| Scenario | Handling | -|----------|----------| -| `pip install .` (modern pip) | Reads `pyproject.toml` directly — works | -| `python setup.py install` (legacy) | Shim delegates to setuptools which reads `pyproject.toml` — works with `setuptools>=61` | -| `python setup.py sdist/bdist_wheel` (legacy) | Same delegation — works | -| Release automation bumps version | Action updates `setup.py pyproject.toml Adyen/settings.py` — `setup.py` shim has no version to bump, but action should handle this gracefully (it searches for version patterns) | -| `adyen-sdk-automation` regenerates code | May regenerate `setup.py` with full metadata — see Risk Assessment | - -## Architecture Sketch - -**Before** (metadata flow): -``` -setup.py ──────────┐ - ├──> pip install (picks one, inconsistency possible) -pyproject.toml ────┘ -``` - -**After** (metadata flow): -``` -pyproject.toml ──────> pip install (single source) - │ -setup.py (shim) ─────> delegates to pyproject.toml via setuptools -``` - -**Version still lives in 3 files** (release automation manages sync): -- `pyproject.toml` → packaging -- `Adyen/settings.py` → runtime API headers -- `setup.py` → will have no version (shim only) - -## Critical Analysis - -**Value**: High. Prevents the exact class of bug that prompted PR #468. Low effort, high confidence. - -**Build vs Modify**: Pure modification — no new tooling or infrastructure. Standard Python packaging modernization. - -**Tradeoff**: Keeping `setup.py` as a shim is slightly redundant, but avoids touching release automation config (owned by Adyen, not us). - -## Codebase Fit - -- `pyproject.toml` already has a comprehensive `[project]` section — this work just makes it authoritative -- The library follows standard Python packaging conventions -- No custom build steps that would conflict with pure `pyproject.toml` metadata - -## Risk Assessment - -1. **Release automation compatibility**: The `release-automation-action` looks for version patterns in `setup.py`. With the shim having no `version=`, the action may fail or skip it. This needs testing, or the workflow's `version-files` should drop `setup.py`. - - **Mitigation**: After changes, verify that `release-automation-action` handles a version-less `setup.py` gracefully, or update `release.yml` to remove `setup.py` from `version-files`. - -2. **`adyen-sdk-automation` code generation**: If the automation repo regenerates `setup.py` with full metadata, it would undo this consolidation. - - **Mitigation**: Document this in the PR. The automation templates may need a corresponding update in `adyen-sdk-automation`. - -3. **Editable installs**: `pip install -e .` with `pyproject.toml` + setuptools works fine with `setuptools>=61`. - -## Open Questions for Downstream Stages - -1. Does `release-automation-action` handle a `setup.py` with no `version=` string? If not, should `setup.py` be removed from `version-files` in `release.yml`? -2. Does `adyen-sdk-automation` generate `setup.py`? If so, the template needs updating too. -3. Should `coveralls` be added to pyproject.toml extras since the Makefile currently installs it but it's not in any extras group? - -## Session Notes - -- **Scope kept tight**: User chose to leave `VERSION` file and `setup.cfg` alone -- **Makefile update included**: User wants `make install` to use pyproject.toml extras instead of hardcoded deps -- **Minimal shim over removal**: Avoids disrupting release automation owned by Adyen -- **`setuptools>=61.0.0`** confirmed as the version floor (matches issue proposal) -- **Project URLs to be added**: Homepage, Repository, Issues links in `[project.urls]` diff --git a/.paw/work/471-consolidate-pyproject/WorkflowContext.md b/.paw/work/471-consolidate-pyproject/WorkflowContext.md deleted file mode 100644 index cd71b22..0000000 --- a/.paw/work/471-consolidate-pyproject/WorkflowContext.md +++ /dev/null @@ -1,41 +0,0 @@ -# WorkflowContext - -Work Title: Consolidate Pyproject Metadata -Work ID: 471-consolidate-pyproject -Base Branch: main -Target Branch: feature/471-consolidate-pyproject -Execution Mode: current-checkout -Repository Identity: github.com/adyen/adyen-python-api-library@76fe8dd0ab9bc1501dc1be87a1806633d04cbb89 -Execution Binding: none -Workflow Mode: full -Review Strategy: local -Review Policy: milestones -Session Policy: continuous -Final Agent Review: enabled -Final Review Mode: single-model -Final Review Interactive: smart -Final Review Models: claude-opus-4.6 -Final Review Specialists: all -Final Review Interaction Mode: parallel -Final Review Specialist Models: none -Final Review Perspectives: auto -Final Review Perspective Cap: 2 -Implementation Model: none -Plan Generation Mode: single-model -Plan Generation Models: none -Planning Docs Review: enabled -Planning Review Mode: multi-model -Planning Review Interactive: smart -Planning Review Models: gpt-5.4, claude-opus-4.6, gpt-5.3-codex -Planning Review Specialists: all -Planning Review Interaction Mode: parallel -Planning Review Specialist Models: none -Planning Review Perspectives: auto -Planning Review Perspective Cap: 2 -Custom Workflow Instructions: none -Initial Prompt: Consolidate package metadata into pyproject.toml (PEP 621) — eliminate duplication between setup.py and pyproject.toml -Issue URL: https://github.com/Adyen/adyen-python-api-library/issues/471 -Remote: origin -Artifact Lifecycle: commit-and-clean -Artifact Paths: auto-derived -Additional Inputs: none