Skip to content
Draft

Dev #31

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
65e99c7
Add unit tests for OutOfOfficePeriod, Product, ReferencedTypes, Shop,…
fasteiner Apr 13, 2026
53f02c2
Update pyproject.toml for release 0.12.0-preview.1
fasteiner Apr 13, 2026
f271145
Update release workflow to use GitVersion v4 and adjust output handling
fasteiner Apr 13, 2026
cd0c669
Merge branch 'Dev' of https://github.com/fasteiner/xurrent-python int…
fasteiner Apr 13, 2026
0759d89
Update GitVersion action to v3 in release workflow
fasteiner Apr 13, 2026
5c96bc5
Downgrade GitVersion action to v3 in release workflow
fasteiner Apr 13, 2026
df2ca79
Update pyproject.toml for release 0.12.1
fasteiner Apr 13, 2026
e7eee8d
updated contributing file
fasteiner Apr 13, 2026
86340d3
Merge branch 'Dev' of https://github.com/fasteiner/xurrent-python int…
fasteiner Apr 13, 2026
e6b195b
Update pyproject.toml for release 0.12.2
fasteiner Apr 13, 2026
cd35bb1
Add 10 new domain classes: problems, service_instances, releases, pro…
Copilot Apr 15, 2026
3cc3785
Add TypeError for invalid note type in add_note methods
Copilot Apr 15, 2026
bd6bb45
Add sub-resource methods to existing domain classes
Copilot Apr 15, 2026
967c975
Add utility API methods to XurrentApiHelper and populate __init__.py …
Copilot Apr 15, 2026
18efde8
Add comprehensive unit tests for new domain classes and sub-resource …
Copilot Apr 15, 2026
84eb028
Add 10 new domain classes, 43 sub-resource methods, 5 utility APIs, 3…
Copilot Apr 15, 2026
94eca1e
Merge pull request #30 from fasteiner/copilot/add-missing-domain-classes
fasteiner May 13, 2026
5b82588
Update pyproject.toml for release 0.13.0-preview.10
fasteiner May 13, 2026
f76ecf4
fix lazy-loaded cross-module annotations
Copilot May 13, 2026
9961d9a
fix search encoding and release tag parsing
Copilot May 13, 2026
b7af646
Update pyproject.toml for release 0.13.0-preview.23
Copilot May 13, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 14 additions & 7 deletions .github/workflows/python-package.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,12 @@ jobs:
strategy:
fail-fast: false
matrix:
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13"]
python-version: ["3.9", "3.10", "3.11", "3.12", "3.13", "3.14"]

steps:
- uses: actions/checkout@v4
- uses: actions/checkout@v6
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v3
uses: actions/setup-python@v5
with:
python-version: ${{ matrix.python-version }}
- name: Set environment variables
Expand All @@ -38,13 +38,20 @@ jobs:
run: |
python -m pip install --upgrade pip
python -m pip install flake8 pytest python-dotenv mock
if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
- name: Test with pytest
pip install .
- name: Lint with flake8
run: |
# Stop the build on syntax errors or undefined names
flake8 ./src/ --count --select=E9,F63,F7,F82 --show-source --statistics
- name: Unit tests
run: |
pytest ./tests/unit_tests
pytest ./src/ --doctest-modules -v
- name: Integration tests
env:
APITOKEN: ${{ secrets.DEMO_API_TOKEN }}
APIACCOUNT: ${{ vars.DEMO_APIACCOUNT }}
APIURL: ${{ vars.DEMO_APIURL }}
run: |
pytest ./tests/
pytest ./src/ --doctest-modules -v
pytest ./tests/integration

52 changes: 28 additions & 24 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@ on:
- main
- Dev

env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true

permissions:
contents: write

Expand All @@ -18,18 +21,18 @@ jobs:

steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v6
with:
fetch-depth: 0 # Required for GitVersion to work correctly

- name: Install GitVersion
uses: GitTools/actions/gitversion/setup@v0
uses: GitTools/actions/gitversion/setup@v3
with:
versionSpec: '5.x'

- name: Determine Version
id: gitversion
uses: GitTools/actions/gitversion/execute@v0
uses: GitTools/actions/gitversion/execute@v3

- name: Extract Release Notes from Changelog
id: extract-changelog
Expand All @@ -47,10 +50,10 @@ jobs:

if echo "$CHANGED_FILES" | grep -E '\.py$'; then
echo "python_changed=true" >> $GITHUB_ENV
echo "::set-output name=python_changed::true"
echo "python_changed=true" >> $GITHUB_OUTPUT
else
echo "python_changed=false" >> $GITHUB_ENV
echo "::set-output name=python_changed::false"
echo "python_changed=false" >> $GITHUB_OUTPUT
fi

- name: Set Version Bump Type from GitVersion
Expand Down Expand Up @@ -84,23 +87,24 @@ jobs:
- name: Determine Effective Version
id: effective-version
run: |
if [[ "${{ env.python_changed }}" == "true" ]]; then
EFFECTIVE_VERSION="${{ steps.gitversion.outputs.semVer }}"
else
PREV_TAG=$(git describe --tags --abbrev=0)
echo "Previous tag: $PREV_TAG"

MAJOR=$(echo $PREV_TAG | cut -d. -f1)
MINOR=$(echo $PREV_TAG | cut -d. -f2)
PATCH=$(echo $PREV_TAG | cut -d. -f3)
NEW_PATCH=$((PATCH + 1))

EFFECTIVE_VERSION="$MAJOR.$MINOR.$NEW_PATCH"
echo "Forcing patch bump: $EFFECTIVE_VERSION"
fi
if [[ "${{ env.python_changed }}" == "true" ]]; then
EFFECTIVE_VERSION="${{ steps.gitversion.outputs.semVer }}"
else
PREV_TAG=$(git describe --tags --abbrev=0)
echo "Previous tag: $PREV_TAG"
CLEAN_TAG=${PREV_TAG#v}

MAJOR=$(echo $CLEAN_TAG | cut -d. -f1)
MINOR=$(echo $CLEAN_TAG | cut -d. -f2)
PATCH=$(echo $CLEAN_TAG | cut -d. -f3)
NEW_PATCH=$((PATCH + 1))

EFFECTIVE_VERSION="$MAJOR.$MINOR.$NEW_PATCH"
echo "Forcing patch bump: $EFFECTIVE_VERSION"
fi

echo "effective_version=$EFFECTIVE_VERSION" >> $GITHUB_ENV
echo "::set-output name=effective_version::$EFFECTIVE_VERSION"
echo "effective_version=$EFFECTIVE_VERSION" >> $GITHUB_OUTPUT

- name: Update CHANGELOG.md
if: github.ref == 'refs/heads/main'
Expand All @@ -111,7 +115,7 @@ jobs:

- name: Commit Updated CHANGELOG.md
if: github.ref == 'refs/heads/main'
uses: stefanzweifel/git-auto-commit-action@v5
uses: stefanzweifel/git-auto-commit-action@v7
with:
commit_message: "Update CHANGELOG for release ${{ steps.effective-version.outputs.effective_version }}"
file_pattern: "CHANGELOG.md"
Expand All @@ -137,7 +141,7 @@ jobs:
EOF

- name: Commit Updated pyproject.toml
uses: stefanzweifel/git-auto-commit-action@v5
uses: stefanzweifel/git-auto-commit-action@v7
with:
commit_message: "Update pyproject.toml for release ${{ steps.effective-version.outputs.effective_version }}"
file_pattern: "pyproject.toml"
Expand All @@ -155,7 +159,7 @@ jobs:

- name: Create GitHub Release
if: env.python_changed == 'true'
uses: softprops/action-gh-release@v1
uses: softprops/action-gh-release@v2
with:
tag_name: v${{ steps.effective-version.outputs.effective_version }}
name: Release v${{ steps.effective-version.outputs.effective_version }}
Expand Down Expand Up @@ -194,4 +198,4 @@ jobs:
- name: Publish release distributions to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
with:
packages-dir: dist/
packages-dir: dist/
6 changes: 5 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,11 @@ cython_debug/

# poetry
poetry.lock
uv.lock

# diff files
*.diff
diff*
diff*

#Claude local
.claude/
63 changes: 63 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,73 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]

### Added

- Problems: added `Problem` class with `ProblemPredefinedFilter`, `ProblemStatus`, and `ProblemImpact` enums; supports CRUD, archive/trash/restore, and sub-resources (requests, workflows, notes).
- ServiceInstances: added `ServiceInstance` class with `ServiceInstancePredefinedFilter` and `ServiceInstanceStatus` enums; supports CRUD and sub-resources (cis, slas, users).
- Releases: added `Release` class with `ReleasePredefinedFilter`, `ReleaseStatus`, and `ReleaseImpact` enums; supports CRUD, archive/trash/restore, and sub-resources (workflows, notes).
- Projects: added `Project` class with `ProjectPredefinedFilter`, `ProjectStatus`, and `ProjectCategory` enums; supports CRUD, archive/trash/restore, and sub-resources (tasks, phases, workflows, risks, notes).
- Contracts: added `Contract` class with `ContractPredefinedFilter` and `ContractStatus` enums; supports CRUD and CI listing.
- KnowledgeArticles: added `KnowledgeArticle` class with `KnowledgeArticlePredefinedFilter` and `KnowledgeArticleStatus` enums; supports CRUD, archive/trash/restore, and sub-resources (requests, service_instances, translations).
- Risks: added `Risk` class with `RiskPredefinedFilter`, `RiskStatus`, and `RiskSeverity` enums; supports CRUD, archive/trash/restore, and sub-resources (organizations, projects, services).
- ServiceOfferings: added `ServiceOffering` class with `ServiceOfferingPredefinedFilter` and `ServiceOfferingStatus` enums; supports CRUD.
- SkillPools: added `SkillPool` class with `SkillPoolPredefinedFilter` enum; supports CRUD, enable/disable, and sub-resources (members, effort_classes).
- Requests: added `get_attachments`, `get_knowledge_articles`, `get_automation_rules`, `get_satisfaction_feedback`, `get_tags`, and `get_watches` instance methods.
- Tasks: added `get_notes`, `add_note`, `get_approvals`, `get_cis`, `get_predecessors`, `get_successors`, `get_service_instances`, and `get_automation_rules` instance methods.
- Workflows: added `get_notes`, `add_note`, `get_automation_rules`, `get_phases`, `get_requests`, and `get_problems` instance methods.
- People: added `get_cis`, `get_addresses`, `get_contacts`, `get_permissions`, `get_ci_coverages`, `get_sla_coverages`, `get_service_coverages`, `get_out_of_office_periods`, and `get_skill_pools` instance methods.
- Organizations: added `get_addresses`, `get_contacts`, `get_contracts`, `get_risks`, `get_slas`, and `get_time_allocations` instance methods.
- Services: added `get_workflows`, `get_request_templates`, `get_risks`, `get_service_instances`, `get_slas`, and `get_service_offerings` instance methods.
- Calendars: added `get_duration`, `get_hours`, and `get_holidays` instance methods.
- Teams: added `get_service_instances` instance method.
- Holidays: added `get_calendars` instance method.
- ClosureCodes: added `ClosureCode` class; supports CRUD.
- Core: added `search(query, types)` for cross-resource full-text search via `GET /search`.
- Core: added `bulk_import(data, import_type, import_format)` for CSV/TSV bulk imports via `POST /import`.
- Core: added `list_archive(queryfilter)` to list all archived items via `GET /archive`.
- Core: added `list_trash(queryfilter)` to list all trashed items via `GET /trash`.
- Core: added `list_audit_lines(queryfilter)` to query the global audit log via `GET /audit_lines`.
- Docs: added `CLAUDE.md` with setup instructions, test commands, architecture overview, and changelog requirements for Claude Code.
- Products: added `Product` class with `ProductPredefinedFilter` and `ProductDepreciationMethod` enums; supports CRUD, enable/disable, and CI listing.
- ProductCategories: added `ProductCategory` class with `ProductCategoryRuleSet` enum; supports CRUD and enable/disable.
- Organizations: added `Organization` class with `OrganizationPredefinedFilter` enum; supports CRUD, enable/disable, archive/trash/restore, people and child org listing.
- Sites: added `Site` class with `SitePredefinedFilter` enum; supports CRUD, enable/disable, archive/trash/restore.
- OutOfOfficePeriods: added `OutOfOfficePeriod` class with `OutOfOfficePeriodPredefinedFilter` enum; supports CRUD and DELETE.
- Holidays: added `Holiday` class; supports CRUD.
- CustomCollections: added `CustomCollection` class with `CustomCollectionPredefinedFilter` enum; supports CRUD, enable/disable, and element listing.
- CustomCollectionElements: added `CustomCollectionElement` class with `CustomCollectionElementPredefinedFilter` enum; supports CRUD and enable/disable.
- ShopArticleCategories: added `ShopArticleCategory` class with `ShopArticleCategoryPredefinedFilter` enum; supports CRUD.
- ShopArticles: added `ShopArticle` class with `ShopArticlePredefinedFilter` and `ShopArticleRecurringPeriod` enums; supports CRUD and enable/disable.
- ShopOrderLines: added `ShopOrderLine` class with `ShopOrderLinePredefinedFilter`, `ShopOrderLineStatus`, and `ShopOrderLineRecurringPeriod` enums; supports CRUD.
- Services: added `Service` class with `ServicePredefinedFilter` enum; supports CRUD and enable/disable.
- Calendars: added `Calendar` class with `CalendarPredefinedFilter` enum; supports CRUD and enable/disable.
- TimeAllocations: added `TimeAllocation` class with `TimeAllocationPredefinedFilter` and category enums; supports CRUD and enable/disable.
- EffortClasses: added `EffortClass` class with `EffortClassPredefinedFilter` enum; supports CRUD and enable/disable.
- RequestTemplates: added `RequestTemplate` class with `RequestTemplatePredefinedFilter`, `RequestTemplateCategory`, `RequestTemplateStatus`, and `RequestTemplateImpact` enums; supports CRUD and enable/disable.
- UiExtensions: added `UiExtension` class with `UiExtensionCategory` enum; supports CRUD and enable/disable.
- WorkflowTemplates: added `WorkflowTemplate` class with `WorkflowTemplatePredefinedFilter` and `WorkflowTemplateCategory` enums; supports CRUD and enable/disable.

### Changed

- People: `Person` now deserializes `site` (→ `Site`) and `organization` (→ `Organization`) references; also fixed a `People.from_data` typo in `update()`.
- ConfigurationItems: `ConfigurationItem` now deserializes the `product` reference (→ `Product`).
- Products: `Product` now deserializes the `category` reference (→ `ProductCategory`).
- CI: updated GitHub Actions in `release.yml` — `GitTools/actions` `v0` → `v3` (latest version compatible with GitVersion 5.x; v4+ requires GitVersion ≥6.1), `stefanzweifel/git-auto-commit-action` `v5` → `v7`, `softprops/action-gh-release` `v1` → `v2`.
- CI: updated `python-package.yml` — `actions/setup-python` `v3` → `v5`; added `pip install .` so the package itself is installed before tests run; added a `flake8` lint step (syntax errors and undefined names only); split test run into separate `Unit tests` and `Integration tests` steps so unit tests always run regardless of credentials; added Python 3.14 to the test matrix.

### Fixed

- Core/People/Requests/Tasks/Workflows: restored lazy-loaded cross-module references in type annotations and fixed `Task` sub-resource helper name resolution so flake8 no longer reports `F821` undefined names.
- Core: URL-encode `search()` query parameters so spaces and reserved characters do not produce ambiguous request URLs.
- CI: strip an optional leading `v` from release tags before computing the forced patch bump in `release.yml`.

## [0.11.0] - 2026-04-13

### Added


- Core: support OAuth client credentials authentication via `client_id` and `client_secret` in `XurrentApiHelper` while maintaining API key compatibility.
- Core: The OAuth token endpoint now dynamically determines the domain from `base_url`, preserving any regional subdomains to ensure consistency between API and OAuth endpoints.
- Core: When using OAuth, if a 401 Unauthorized error is received, the token is automatically refreshed and the API call is retried once. If authentication still fails after token refresh, an explicit HTTPError is raised.
Expand Down
92 changes: 92 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## Project Overview

`xurrent-python` is a Python wrapper for the Xurrent API (a service/incident management platform). It provides object-oriented abstractions over REST endpoints with built-in handling for authentication, pagination, rate limiting, and OAuth token refresh.

## Setup

This project uses [Poetry](https://python-poetry.org/) for dependency management.

```bash
pip install poetry
poetry install --with dev
eval $(poetry env activate)
pre-commit install
```

## Commands

### Testing

```bash
# Unit tests (no credentials required)
pytest ./tests/unit_tests

# Run a single test file
pytest ./tests/unit_tests/test_core_oauth.py -v

# Run a single test function
pytest ./tests/unit_tests/test_core_oauth.py::test_init_requires_authentication_method -v

# Doctests in source modules
pytest ./src/ --doctest-modules -v

# Integration tests (requires env vars: APITOKEN, APIACCOUNT, APIURL)
pytest ./tests/integration
```

Integration tests require a `.env` file or environment variables: `APITOKEN`, `APIACCOUNT`, `APIURL`.

### Pre-commit

```bash
pre-commit run --all-files
```

The pre-commit hook runs the unit test suite automatically before each commit.

## Architecture

### Core (`xurrent/core.py`)

`XurrentApiHelper` is the central HTTP client. It:
- Supports two auth modes: **API key** (`XurrentApiHelper(token=..., account=..., api_url=...)`) and **OAuth 2.0 client credentials** (`XurrentApiHelper(client_id=..., client_secret=..., account=..., api_url=...)`)
- Automatically handles pagination via `Link` response headers — callers receive aggregated results
- Retries on HTTP 429 (rate limit) and refreshes OAuth tokens on HTTP 401
- Exposes `api_call(method, url, data, params)` as the low-level HTTP wrapper used by all domain classes

`JsonSerializableDict` is the base class for all resource models, providing `to_dict()` and `to_json()` serialization.

### Domain Classes

Each module wraps one Xurrent resource type. All domain classes follow the same patterns:
- Accept a `XurrentApiHelper` instance as `connection_object`
- Use `@classmethod` factory methods (`get_by_id()`, `get_<resource>()`, `create()`) to deserialize API responses into instances via `from_data()`
- Expose lifecycle methods (enable/disable/archive/trash/restore) and resource-specific operations

| Module | Class | Notable Features |
|---|---|---|
| `requests.py` | `Request` | Notes, linked CIs, status/category/completion enums |
| `people.py` | `Person` | `get_me()`, team membership |
| `configuration_items.py` | `ConfigurationItem` | Auto-increments label on creation |
| `tasks.py` | `Task` | Approve/reject/cancel, workflow linkage |
| `workflows.py` | `Workflow` | Close with completion reason |
| `teams.py` | `Team` | Basic lifecycle management |

### Circular Import Handling

Domain classes cross-reference each other (e.g., `Request` embeds `Person`, `Team`, `Workflow`). To avoid circular imports, these are imported lazily — inside methods rather than at module top level. When adding new cross-module references, follow this same pattern.

### Tests

- **`tests/unit_tests/`** — Uses `MagicMock` to stub `XurrentApiHelper`; no real credentials needed
- **`tests/integration/`** — Hits a live Xurrent instance; requires credentials in environment

When adding a new domain class, add corresponding unit tests under `tests/unit_tests/`.

## Changelog

All changes must be documented in [CHANGELOG.md](CHANGELOG.md) under the `[Unreleased]` section, following the [Keep a Changelog](https://keepachangelog.com/en/1.1.0/) format. Use the appropriate subsection (`Added`, `Changed`, `Fixed`, `Removed`) and prefix each entry with the affected module or component (e.g., `Core:`, `Requests:`).
3 changes: 3 additions & 0 deletions Contributing.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,3 +14,6 @@
eval $(poetry env activate)
```

## ChangeLog

When you make code changes, please document them in the ChangeLog. You can use the [Keep a Changelog assistant](https://chatgpt.com/g/g-684af39084148191b7c83c89daf1b477-keep-a-changelog-assistant) or Claude Code to help write the entry.
12 changes: 9 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "xurrent"
version = "0.11.0"
version = "0.13.0-preview.23"
authors = [
{ name="Fabian Steiner", email="fabian@stei-ner.net" },
]
Expand All @@ -18,9 +18,9 @@ Homepage = "https://github.com/fasteiner/xurrent-python"
Issues = "https://github.com/fasteiner/xurrent-python/issues"
[tool.poetry]
name = "xurrent"
version = "0.11.0"
version = "0.13.0-preview.23"
description = "A python module to interact with the Xurrent API."
authors = ["Ing. Fabian Franz Steiner BSc. <fabian.steiner@tttech.com>"]
authors = ["Ing. Fabian Franz Steiner BSc. <f.steiner@techwork.at>"]
readme = "README.md"

[tool.poetry.dependencies]
Expand All @@ -38,3 +38,9 @@ shell = "^1.0.1"
[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"

[dependency-groups]
dev = [
"mock>=5.2.0",
"pytest>=8.4.2",
]
Comment thread
fasteiner marked this conversation as resolved.
Loading