Polarion REST API client, wrappers & helpers
Project description
Polarion REST API Client
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, Documents, Document Parts, Document Comments, Document Attachments, and Document Table extraction
- Sync and Async — every operation works with both
PolarionClient(sync) andPolarionAsyncClient(async) - Automatic pagination — fetch all pages transparently with
page_size=-1or iterate lazily with.iter() - Batch operations — bulk create, update, and delete work items in a single request
- Typed error hierarchy —
JSONAPIErrorsubclasses (Unauthorized,Forbidden,NotFound,Conflict,ServerError) for precise exception handling - Environment-based configuration — configure via environment variables or
.envfiles (withpython-dotenvsupport) - 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
│ ├── 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 pdmrecommended)
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"))
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
# 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.
- Place or update the upstream spec:
codegen/polarion-openapi.json
- Run the pipeline (clean, validate, generate, copy):
pdm run regenerate-client
- 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.mdwith 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.).
- Place overrides under
codegen/custom_templates/using the same relative paths as the upstream templates. - The regeneration script automatically passes
--custom-template-pathwhen 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 servicesintegration— tests that hit a real Polarion server
pdm run pytest -m unit
pdm run pytest -m integration
Contributing
- Create a feature branch:
git checkout -b feature/my-feature
- Run the full pipeline before committing:
pdm run pre_build
- Run tests:
pdm run pytest
- Lint:
flake8 . --exclude ./venv --max-line-length=120
- 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
- Update
CHANGELOG.md - Set version:
pdm run python scripts/set_version.py XX.YY.ZZ - Regenerate and build:
pdm run pre_build
pdm build
- Dry-run on TestPyPI:
pdm publish -r testpypi --username __token__ --password "$TESTPYPI_TOKEN"
- Verify from TestPyPI:
pip install -i https://test.pypi.org/simple/ \
--extra-index-url https://pypi.org/simple \
polarion-rest-client==<version>
- Publish to PyPI:
pdm publish --username __token__ --password "$PYPI_TOKEN"
- 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
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file polarion_rest_client-25.6.13.tar.gz.
File metadata
- Download URL: polarion_rest_client-25.6.13.tar.gz
- Upload date:
- Size: 723.9 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: pdm/2.26.8 CPython/3.12.13 Linux/5.15.154+
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
b745e751b5427b3d5bb61e06b09a07113084c1232eb4d81eb9e3cd2faed0158a
|
|
| MD5 |
040db5b9b863d3ef5caa6daffd9ce6de
|
|
| BLAKE2b-256 |
f1c9aeec01f169b3d50d093c1d3ed927cb13c4c0735b9af23eb7f0e7e2ba45de
|
File details
Details for the file polarion_rest_client-25.6.13-py3-none-any.whl.
File metadata
- Download URL: polarion_rest_client-25.6.13-py3-none-any.whl
- Upload date:
- Size: 2.7 MB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: pdm/2.26.8 CPython/3.12.13 Linux/5.15.154+
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0ded2a48a550dde922621a1680a853816d1ecd0480c18e784e53ca6bf52a8511
|
|
| MD5 |
6a8535e2cc8eee09ba910bcb2c727970
|
|
| BLAKE2b-256 |
f91fa928a945048619df183efdbd42dc7138a88c419b4892c1465ce84bc578ad
|