Skip to main content

Polarion REST API client, wrappers & helpers

Project description

Polarion REST API Client

Python 3.10+ License: MIT PDM

A Python client for the Polarion ALM REST API, combining an auto-generated low-level client (from the upstream OpenAPI spec) with high-level resource wrappers for common operations.

Both synchronous and asynchronous usage are supported out of the box.


Features

  • Auto-generated client from the Polarion OpenAPI specification via openapi-python-client
  • High-level resource wrappers with full CRUD support for Projects, Work Items, Work Item Attachments, Work Item Comments, Work Item Approvals, Work Item Work Records, Linked Work Items, Documents, Document Parts, Document Comments, Document Attachments, and Document Table extraction
  • Sync and Async — every operation works with both PolarionClient (sync) and PolarionAsyncClient (async)
  • Automatic pagination — fetch all pages transparently with page_size=-1 or iterate lazily with .iter()
  • Batch operations — bulk create, update, and delete work items in a single request
  • Typed error hierarchyJSONAPIError subclasses (Unauthorized, Forbidden, NotFound, Conflict, ServerError) for precise exception handling
  • Environment-based configuration — configure via environment variables or .env files (with python-dotenv support)
  • SSL via truststore — uses the OS certificate store by default for enterprise environments

Project Structure

.
├── codegen/                                # OpenAPI specs + generation config
│   ├── polarion-openapi.json               # upstream Polarion REST spec (input)
│   ├── polarion-openapi-clean.json         # cleaned spec (derived)
│   └── client-config.yaml                  # openapi-python-client config
├── scripts/                                # helper scripts
│   ├── clean_rest_spec.py                  # applies Polarion-specific fixes to the spec
│   ├── rest_json_validator.py              # JSON / OpenAPI validation
│   ├── regenerate_polarion_rest_client.sh  # end-to-end: clean → validate → generate → copy
│   └── set_version.py                      # set package version in pyproject.toml
├── src/
│   └── polarion_rest_client/               # installable package
│       ├── __init__.py                     # public API surface
│       ├── client.py                       # PolarionClient & PolarionAsyncClient
│       ├── session.py                      # get_env_vars() environment loader
│       ├── error.py                        # typed exception hierarchy
│       ├── resource.py                     # PolarionResource base class
│       ├── paging.py                       # generic pagination utilities
│       ├── project.py                      # Project resource
│       ├── workitem.py                     # WorkItem resource
│       ├── workitem_attachment.py          # WorkItemAttachment resource
│       ├── workitem_comment.py            # WorkItemComment resource
│       ├── workitem_approval.py          # WorkItemApproval resource
│       ├── workitem_workrecord.py        # WorkItemWorkRecord resource
│       ├── workitem_linked.py            # WorkItemLinked resource
│       ├── document.py                     # Document resource
│       ├── document_part.py                # DocumentPart resource
│       ├── document_comment.py             # DocumentComment resource
│       ├── document_attachment.py          # DocumentAttachment resource
│       ├── document_table_utils.py         # HTML table extraction helpers
│       └── openapi/                        # auto-generated client (committed)
├── tests/                                  # unit + integration tests
├── examples/                               # runnable usage examples
│   ├── common.py                           # shared helpers for examples
│   ├── workitem/                           # work item examples
│   └── document/                           # document examples
├── pyproject.toml                          # PDM / PEP 621 metadata
├── CHANGELOG.md
├── LICENSE
└── README.md

Requirements

  • Python 3.10+
  • PDM (pipx install pdm recommended)

Installation

From PyPI

pip install polarion-rest-client

From Source

git clone https://gitlab.com/elimesika-group/polarion-rest-client.git
cd polarion-rest-client
pdm install --dev

Configuration

The client reads configuration from environment variables. Set them in your shell or place them in a .env file (requires python-dotenv).

Required Variables

Variable Description
POLARION_URL Base URL of the Polarion server (e.g., https://polarion.example.com)

Authentication (one of the following)

Variable Description
POLARION_TOKEN Personal access token (preferred)
POLARION_USERNAME + POLARION_PASSWORD Basic authentication credentials

Optional Variables

Variable Default Description
POLARION_TOKEN_PREFIX Bearer Authorization header prefix for token auth
POLARION_VERIFY_SSL true Set to false to disable SSL verification
POLARION_TIMEOUT 30.0 Request timeout in seconds

Example .env File

POLARION_URL=https://polarion.example.com
POLARION_TOKEN=your-personal-access-token
POLARION_VERIFY_SSL=true
POLARION_TIMEOUT=30.0

Quick Start

Synchronous

import polarion_rest_client as prc
from polarion_rest_client.project import Project

pc = prc.PolarionClient(**prc.get_env_vars())

project_api = Project(pc)
project = project_api.get("my-project-id")
print(project)

Asynchronous

import asyncio
import polarion_rest_client as prc
from polarion_rest_client.project import Project

async def main():
    async with prc.PolarionAsyncClient(**prc.get_env_vars()) as pc:
        project_api = Project(pc)
        project = await project_api.get("my-project-id")
        print(project)

asyncio.run(main())

High-Level Resources

Each resource wrapper accepts either a PolarionClient (sync) or PolarionAsyncClient (async) and provides a consistent API.

Project

from polarion_rest_client.project import Project

api = Project(pc)

api.list()                                     # list all projects
api.get("project-id")                          # get a single project
api.create("new-project", tracker_prefix="NP") # create (async job, waits by default)
api.patch("project-id", name="New Name")       # update attributes
api.delete("project-id")                       # delete (async job)
api.exists("project-id")                       # check existence (returns bool)
api.list_templates()                           # list project templates

WorkItem

from polarion_rest_client.workitem import WorkItem

api = WorkItem(pc)

api.create("proj", wi_type="task", title="My Task")
api.get("proj", "WI-001")
api.update("proj", "WI-001", title="Updated Title")
api.delete("proj", ["WI-001", "WI-002"])

# Pagination
items = api.list("proj", page_size=50)             # single page
all_items = api.list("proj", page_size=-1)          # fetch all pages
for item in api.iter("proj", page_size=100):        # lazy iteration
    print(item["id"])

# Batch operations
api.update_many("proj", [
    {"id": "WI-001", "attributes": {"severity": "critical"}},
    {"id": "WI-002", "attributes": {"severity": "major"}},
])
api.update_many_same_attrs("proj", ["WI-001", "WI-002"],
    attributes={"status": "approved"})

# Search
api.find_by_title("proj", "My Task")

# Global (cross-project) operations
all_items = api.list_all(page_size=50)
for item in api.iter_all(page_size=100):
    print(item["id"])
api.update_all([{"id": "proj/WI-001", "attributes": {"severity": "critical"}}])
api.delete_all([{"id": "proj/WI-001"}])

# Enum options & workflow actions
api.get_available_enum_options("proj", "WI-001", "status")
api.get_available_enum_options_for_type("proj", "status", wi_type="task")
api.get_current_enum_options("proj", "WI-001", "status")
api.get_workflow_actions("proj", "WI-001")

# Relationships (generic JSON:API)
api.get_relationships("proj", "WI-001", "categories")
api.create_relationships("proj", "WI-001", "categories",
    [{"type": "categories", "id": "proj/cat-1"}])
api.update_relationships("proj", "WI-001", "categories",
    [{"type": "categories", "id": "proj/cat-2"}])
api.delete_relationships("proj", "WI-001", "categories",
    [{"type": "categories", "id": "proj/cat-2"}])

# Move to/from document
api.move_to_document("proj", "WI-001", target_document="proj/space/doc-name")
api.move_from_document("proj", "WI-001")

# Test parameter definitions
api.list_test_parameter_definitions("proj", "WI-001")
api.get_test_parameter_definition("proj", "WI-001", "param-id")

WorkItemAttachment

from polarion_rest_client.workitem_attachment import WorkItemAttachment

api = WorkItemAttachment(pc)

api.list("proj", "WI-001")
api.get("proj", "WI-001", "attachment-id")
api.get_content("proj", "WI-001", "attachment-id")          # returns bytes
api.create("proj", "WI-001",
    file_data=b"...", file_name="diagram.png", mime_type="image/png")
api.update("proj", "WI-001", "attachment-id", title="New Title")
api.delete("proj", "WI-001", "attachment-id")
for att in api.iter("proj", "WI-001"):
    print(att["id"])

WorkItemComment

from polarion_rest_client.workitem_comment import WorkItemComment

api = WorkItemComment(pc)

api.list("proj", "WI-001")
api.get("proj", "WI-001", "comment-id")
api.create("proj", "WI-001", text="Review needed")
api.update("proj", "WI-001", "comment-id", resolved=True)
api.reply("proj", "WI-001", "comment-id", text="Done")
api.resolve("proj", "WI-001", "comment-id")
api.unresolve("proj", "WI-001", "comment-id")
for c in api.iter("proj", "WI-001"):
    print(c["id"])

WorkItemApproval

from polarion_rest_client.workitem_approval import WorkItemApproval

api = WorkItemApproval(pc)

api.list("proj", "WI-001")
api.get("proj", "WI-001", "user-id")
api.create("proj", "WI-001", user_id="user-id")
api.update("proj", "WI-001", "user-id", status="approved")
api.approve("proj", "WI-001", "user-id")
api.disapprove("proj", "WI-001", "user-id")
api.reset("proj", "WI-001", "user-id")
api.delete("proj", "WI-001", "user-id")
for a in api.iter("proj", "WI-001"):
    print(a["id"], a.get("attributes", {}).get("status"))

WorkItemWorkRecord

from polarion_rest_client.workitem_workrecord import WorkItemWorkRecord

api = WorkItemWorkRecord(pc)

api.list("proj", "WI-001")
api.get("proj", "WI-001", "record-id")
api.create("proj", "WI-001", user_id="user-id", time_spent="2h", date="2026-04-26", comment="Design work")
api.delete("proj", "WI-001", "record-id")
for r in api.iter("proj", "WI-001"):
    print(r["id"], r.get("attributes", {}).get("timeSpent"))

WorkItemLinked

from polarion_rest_client.workitem_linked import WorkItemLinked

api = WorkItemLinked(pc)

api.list("proj", "WI-001")
api.get("proj", "WI-001", role_id="relates_to", target_project_id="proj", linked_work_item_id="WI-002")
api.create("proj", "WI-001", target_project_id="proj", target_work_item_id="WI-002", role="relates_to")
api.update("proj", "WI-001", "relates_to", "proj", "WI-002", suspect=True)
api.delete("proj", "WI-001", "relates_to", "proj", "WI-002")
for lk in api.iter("proj", "WI-001"):
    print(lk["id"], lk.get("attributes", {}).get("role"))

Document

from polarion_rest_client.document import Document

api = Document(pc)

api.create("proj", "space", module_name="my-doc", title="My Document")
api.get("proj", "space", "my-doc")
api.update("proj", "space", "my-doc", title="Updated Title")
api.branch("proj", "space", "my-doc", target_project_id="other-proj")
api.copy("proj", "space", "my-doc", target_document_name="my-doc-copy")
api.merge_from_master("proj", "space", "my-doc")
api.merge_to_master("proj", "space", "my-doc")

DocumentPart

from polarion_rest_client.document_part import DocumentPart

api = DocumentPart(pc)

api.list("proj", "space", "my-doc", page_size=-1)
api.get("proj", "space", "my-doc", "part-id", fields_parts="@all")
api.create("proj", "space", "my-doc", part_type="workitem", work_item_id="WI-001")
for part in api.iter("proj", "space", "my-doc"):
    print(part["id"])

DocumentComment

from polarion_rest_client.document_comment import DocumentComment

api = DocumentComment(pc)

api.list("proj", "space", "my-doc")
api.get("proj", "space", "my-doc", "comment-id")
api.create("proj", "space", "my-doc", text="Review needed")
api.update("proj", "space", "my-doc", "comment-id", resolved=True)
api.reply("proj", "space", "my-doc", "comment-id", text="Done")
api.resolve("proj", "space", "my-doc", "comment-id")
api.unresolve("proj", "space", "my-doc", "comment-id")

DocumentAttachment

from polarion_rest_client.document_attachment import DocumentAttachment

api = DocumentAttachment(pc)

api.list("proj", "space", "my-doc")
api.get("proj", "space", "my-doc", "attachment-id")
api.create("proj", "space", "my-doc",
    file_data=b"...", file_name="diagram.png", mime_type="image/png")
api.update("proj", "space", "my-doc", "attachment-id", title="Updated Title")
content = api.get_content("proj", "space", "my-doc", "attachment-id")

Document Table Extraction

Extract structured data from HTML tables embedded in Polarion documents into pandas DataFrames.

from polarion_rest_client.document_table_utils import (
    extract_document_tables_by_columns,
)

df = extract_document_tables_by_columns(
    pc, "proj", "space", "my-doc",
    expected_columns=["Name", "Version", "Status"],
)
print(df)

Error Handling

The client provides a typed exception hierarchy rooted in PolarionError:

PolarionError
├── HTTPStatusError          # non-2xx without JSON:API errors payload
└── JSONAPIError             # JSON:API errors[] present
    ├── Unauthorized         # 401
    ├── Forbidden            # 403
    ├── NotFound             # 404
    ├── Conflict             # 409
    └── ServerError          # 5xx
from polarion_rest_client import NotFound, Unauthorized

try:
    project_api.get("nonexistent")
except NotFound as e:
    print(f"Not found: {e.detail}")
except Unauthorized:
    print("Check your credentials")

Examples

The examples/ directory contains runnable scripts demonstrating common workflows. Set the following environment variables before running:

Variable Description
POLARION_TEST_PROJECT_ID Project ID for examples
POLARION_TEST_SPACE_ID Space ID (defaults to _default)
POLARION_TEST_WI_TYPE Work item type (defaults to task)
# Work item CRUD
python examples/workitem/workitem_CRUD.py

# Document CRUD
python examples/document/document_CRUD.py

# Document attachment CRUD
python examples/document/document_attachment_CRUD.py

# Document comment CRUD
python examples/document/document_comment_CRUD.py

# Pagination
python examples/workitem/workitem_paging.py
python examples/document/document_paging.py

# Batch updates
python examples/workitem/workitem_batch_update.py

# Work item attachment CRUD
python examples/workitem/workitem_attachment_CRUD.py

# Work item comment CRUD
python examples/workitem/workitem_comment_CRUD.py

# Work item approval CRUD
python examples/workitem/workitem_approval_CRUD.py

# Work item work record CRUD
python examples/workitem/workitem_workrecord_CRUD.py

# Linked work items CRUD
python examples/workitem/workitem_linked_CRUD.py

# Advanced work item operations (enum options, workflow, move, global list)
python examples/workitem/workitem_advanced.py

# Table extraction
python examples/document/document_tables.py

Regeneration Workflow

The generated client can be rebuilt from an updated OpenAPI specification.

  1. Place or update the upstream spec:
codegen/polarion-openapi.json
  1. Run the pipeline (clean, validate, generate, copy):
pdm run regenerate-client
  1. Verify the import:
python -c "import polarion_rest_client.openapi as pkg; print('OK:', pkg.__name__)"

The generated package under src/polarion_rest_client/openapi/ is committed so that consumers can use the client without running the generator.


Bumping to a New Polarion Version (Developer Guide)

When a new Polarion server version is released and a new OpenAPI specification becomes available, follow this end-to-end procedure to update the client.

Prerequisites

  • Access to the new Polarion REST API JSON specification (exported from the target server)
  • A working development environment (pdm install --dev)
  • Access to a Polarion test server running the new version (for integration tests)

Step-by-Step Procedure

1. Obtain the new OpenAPI specification

Export the REST API JSON from the new Polarion server (typically available at https://<server>/polarion/rest/v1/openapi.json) and place it at:

cp /path/to/new-spec.json codegen/polarion-openapi.json

2. Clean the specification

The upstream spec contains known quirks that must be patched before code generation. Run the cleaner:

python scripts/clean_rest_spec.py \
    codegen/polarion-openapi.json \
    codegen/polarion-openapi-clean.json

The cleaner applies the following deterministic fixes:

Fix What it does
octet_stream_upload Normalizes application/octet-stream upload schemas to string/binary
wildcard_error_responses Expands vendor 4XX-5XX entries into concrete 4xx/5xx responses
missing_downloads_items Adds missing items for array schemas in jobsSingle*Response
error_source_nullability Marks error source objects as nullable
expand_reference_only_components Wraps bare $ref component schemas in allOf for the generator

Review the output. If the new spec introduces new quirks that break generation, add a fixer function in scripts/clean_rest_spec.py, register it in apply_all_fixes(), and document it in the table above.

3. Validate the cleaned specification

python scripts/rest_json_validator.py codegen/polarion-openapi-clean.json

This checks JSON syntax and validates the spec against the OpenAPI schema. Fix any errors before proceeding.

4. Regenerate the client

pdm run regenerate-client

This runs the full pipeline (clean, validate, generate, copy into src/polarion_rest_client/openapi/). Alternatively, steps 2-4 are equivalent to this single command.

5. Check for breaking changes in the generated code

After regeneration, verify that the high-level wrappers still compile and function correctly:

python -c "import polarion_rest_client.openapi as pkg; print('OK:', pkg.__name__)"

Common issues to watch for:

Symptom Likely cause Resolution
ImportError in a resource module Endpoint or model was renamed/removed in the new spec Update the import paths in the affected wrapper (e.g., project.py, workitem.py)
TypeError on a wrapper method call Generated function signature changed (new/renamed params) Run resolve_page_params() diagnostics; update keyword arguments in the wrapper
New endpoints available Polarion added new REST resources Consider adding new high-level wrappers under src/polarion_rest_client/
Generator warnings about unsupported schemas New spec patterns the cleaner doesn't handle yet Add a new fixer in scripts/clean_rest_spec.py

6. Update the package version

Bump the version to reflect the new Polarion server version. The format is XX.YY.ZZ where XX.YY matches the Polarion version and ZZ starts at 01 for a new server release:

pdm run python scripts/set_version.py XX.YY.01

For example, if upgrading to Polarion 25.12:

pdm run python scripts/set_version.py 25.12.01

7. Run the test suite

# Unit tests (no server required)
pdm run pytest -m unit

# Integration tests (requires POLARION_* env vars pointing to the new server)
pdm run pytest -m integration

Fix any failures caused by API changes before proceeding.

8. Verify the examples

The examples/ directory contains runnable scripts that exercise the high-level wrappers against a real server. Run them all to confirm that the new spec hasn't broken any workflows:

# Ensure env vars point to the new Polarion server
export POLARION_URL=https://polarion-new.example.com
export POLARION_TOKEN=...
export POLARION_TEST_PROJECT_ID=my-test-project

# Run the full example suite
bash examples/run_all.sh

If any example fails, investigate whether the issue is in the generated client (step 5) or the high-level wrapper, and fix accordingly.

9. Update documentation

  • Update CHANGELOG.md with the new version and a summary of changes
  • If new high-level resources were added, update the High-Level Resources section of this README
  • If new environment variables or configuration options were introduced, update the Configuration section

10. Build and verify

pdm run pre_build
pdm build

Sanity-check the wheel:

pip install dist/polarion_rest_client-*.whl
python -c "import polarion_rest_client; print(polarion_rest_client.__version__)"

11. Commit and release

git add -A
git commit -m "chore(release): bump to Polarion XX.YY (vXX.YY.01)"
git tag -a vXX.YY.01 -m "Polarion XX.YY, patch 01"
git push origin HEAD && git push origin vXX.YY.01

Then follow the Releases and Publishing section to publish to PyPI.

Custom Templates (Advanced)

You can override the Jinja templates used by openapi-python-client to customize the generated code (naming conventions, default headers, timeouts, docstrings, etc.).

  1. Place overrides under codegen/custom_templates/ using the same relative paths as the upstream templates.
  2. The regeneration script automatically passes --custom-template-path when this directory exists.

To discover the upstream template paths:

python -c "
import importlib.resources as r, openapi_python_client as opc
print((r.files(opc) / 'templates').as_posix())
"

Copy only the files you want to override; the rest fall back to defaults.


Versioning

Package versions follow the format XX.YY.ZZ:

Segment Meaning
XX.YY Polarion server version (e.g., 25.06)
ZZ Client patch number (e.g., 08)

PEP 440 normalizes 25.06.08 to 25.6.8 on PyPI. Git tags use the human-friendly form (v25.06.08).

Set the version with:

pdm run python scripts/set_version.py 25.06.08

Tests

Tests are parameterized to run in both sync and async modes.

pdm run pytest

Test markers:

  • unit — fast tests, no external services
  • integration — tests that hit a real Polarion server
pdm run pytest -m unit
pdm run pytest -m integration

Contributing

  1. Create a feature branch:
git checkout -b feature/my-feature
  1. Run the full pipeline before committing:
pdm run pre_build
  1. Run tests:
pdm run pytest
  1. Lint:
flake8 . --exclude ./venv --max-line-length=120
  1. Commit following Conventional Commits and open a Merge Request with a clear description, issue references, and test evidence.

Avoid manual edits to files under src/polarion_rest_client/openapi/ — they are regenerated.


Releases and Publishing

Quick Release Checklist

  1. Update CHANGELOG.md
  2. Set version: pdm run python scripts/set_version.py XX.YY.ZZ
  3. Regenerate and build:
pdm run pre_build
pdm build
  1. Dry-run on TestPyPI:
pdm publish -r testpypi --username __token__ --password "$TESTPYPI_TOKEN"
  1. Verify from TestPyPI:
pip install -i https://test.pypi.org/simple/ \
    --extra-index-url https://pypi.org/simple \
    polarion-rest-client==<version>
  1. Publish to PyPI:
pdm publish --username __token__ --password "$PYPI_TOKEN"
  1. Tag and push:
git tag -a vXX.YY.ZZ -m "Polarion XX.YY, patch ZZ"
git push origin vXX.YY.ZZ

Token Setup (One-Time)

Registry Registration Token Scope
TestPyPI Required for dry-run TESTPYPI_TOKEN — Entire account (first upload)
PyPI Required for release PYPI_TOKEN — Entire account, then project-scoped

Never commit tokens. Pass them via environment variables. After your first PyPI release, create a project-scoped token and revoke the wide one.

Troubleshooting

Error Fix
"File already exists" / 400 Bump patch version, rebuild, retry
403 "user isn't allowed to upload" Use an Entire account token for first upload
Name conflict Change [project].name in pyproject.toml
Packaging issues rm -rf dist && pdm run pre_build && pdm build

License

Licensed under the MIT License.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

polarion_rest_client-25.6.14.tar.gz (723.5 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

polarion_rest_client-25.6.14-py3-none-any.whl (2.7 MB view details)

Uploaded Python 3

File details

Details for the file polarion_rest_client-25.6.14.tar.gz.

File metadata

  • Download URL: polarion_rest_client-25.6.14.tar.gz
  • Upload date:
  • Size: 723.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: pdm/2.26.8 CPython/3.12.13 Linux/5.15.154+

File hashes

Hashes for polarion_rest_client-25.6.14.tar.gz
Algorithm Hash digest
SHA256 703ae0a8294576291af1df8747a08dbb24285c27b401584b28b9c4fb0e886eb8
MD5 40e1be2fe663cfb0047929a31a1078ea
BLAKE2b-256 d564c13a67fdf897620f0b54b212b2fc7174ff107a6554cbb7579e7e2b4adc3e

See more details on using hashes here.

File details

Details for the file polarion_rest_client-25.6.14-py3-none-any.whl.

File metadata

File hashes

Hashes for polarion_rest_client-25.6.14-py3-none-any.whl
Algorithm Hash digest
SHA256 653bb1a99228a748ac2ceb844255c1fb54bc185ef6d3b767e0c2a8512c7a22eb
MD5 b511a2ea5ef2bba38ce681fc561aafa1
BLAKE2b-256 9c9a5a96bcefeda70bdfd55345c215765148dbee6d7c2ea1b25c8542f5e1206a

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page