Metadata-Version: 2.4
Name: jira2py
Version: 0.14.0
Summary: The Python library to interact with Atlassian Jira REST API
Author-email: nEver1 <7fhhwpuuo@mozmail.com>
License-Expression: MIT
Project-URL: Homepage, https://github.com/en-ver/jira2py
Project-URL: Repository, https://github.com/en-ver/jira2py
Project-URL: Documentation, https://jira2py.org/
Project-URL: Issue Tracker, https://github.com/en-ver/jira2py/issues
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Operating System :: OS Independent
Requires-Python: >=3.11
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: httpx[http2]<1,>=0.28.0
Requires-Dist: adf-bridge<0.2,>=0.1.2
Requires-Dist: pydantic>=2.12.5
Requires-Dist: tenacity>=9.1.4
Dynamic: license-file

# jira2py

[![PyPI version](https://img.shields.io/pypi/v/jira2py.svg)](https://pypi.org/project/jira2py/)
[![Python versions](https://img.shields.io/pypi/pyversions/jira2py.svg)](https://pypi.org/project/jira2py/)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)

A type-safe Python client for the [Jira Cloud REST API v3](https://developer.atlassian.com/cloud/jira/platform/rest/v3/intro/). Use it to read and search issues, create and edit issues, transition workflows, retrieve changelogs, discover canonical field IDs, and work with comments, attachments, links, worklogs, projects, metadata, users, and saved filters.

## Scope

`jira2py` supports **Jira Cloud** and Python **3.11+**. It does not support Jira Server or Data Center, board/sprint/epic workflows, issue deletion or archiving, or a dedicated issue-assignment API.

## Install

```bash
pip install jira2py
```

## Authenticate safely

Create an [Atlassian API token](https://id.atlassian.com/manage-profile/security/api-tokens), then provide your Cloud URL, Atlassian account email, and token. Without `credentials_file`, each credential uses a non-empty explicit `url`, `username`, or `api_token` argument, then its `JIRA_URL`, `JIRA_USER`, or `JIRA_API_TOKEN` environment variable.

When you supply `credentials_file`, jira2py first loads and validates it as a complete set: the JSON must contain non-empty `url`, `username`, and `api_token` values. A partial file cannot be completed from explicit arguments or environment variables; after validation, non-empty explicit arguments override their matching file values.

There is no default credentials-file path. Keep tokens out of source control, logs, and error reports; use environment variables or a protected local JSON file instead.

```bash
export JIRA_URL="https://your-domain.atlassian.net"
export JIRA_USER="your-email@example.com"
export JIRA_API_TOKEN="your-api-token"
```

```json
{
  "url": "https://your-domain.atlassian.net",
  "username": "your-email@example.com",
  "api_token": "your-api-token"
}
```

Pass the JSON file only when needed:

```python
from jira2py import JiraAPI

jira = JiraAPI(credentials_file="./jira-credentials.json")
```

## Choose an API layer

- **`JiraAPI`** is the low-level, endpoint-oriented interface. Operations return parsed Jira JSON-like data when available; downloads return bytes and operations without a response body return `None`.
- **`JiraHelpers`** provides grouped workflows and readable `HelperResult` values, with optional structured data, for common tasks.
- **`format_issue`** is an optional pure presentation function for an issue response you already retrieved.

Use `JiraAPI` when you want direct REST payloads and endpoint control. Full issue retrieval is performed only by `jira.issues.get_issue()`:

```python
from jira2py import JiraAPI

jira = JiraAPI()
issue = jira.issues.get_issue("PROJECT-123", fields=["summary", "status"])
results = jira.search.enhanced_search("project = PROJECT AND status = 'In Progress'")
```

Use `format_issue` only when you want readable text in addition to that structured response. It does not fetch data or change the response:

```python
from jira2py import JiraAPI
from jira2py.helpers import JiraHelpers, format_issue

api = JiraAPI()
issue = api.issues.get_issue(
    "PROJECT-123",
    fields=["summary", "status", "description"],
)
print(format_issue(issue, browse_url=f"{api.credentials.url}/browse/{issue['key']}"))

helpers = JiraHelpers(api)
print(helpers.metadata.transitions("PROJECT-123").text)
field_page = helpers.metadata.list_fields("PROJECT", field_types=["custom"])
print(field_page.text)  # names plus canonical IDs; one Jira page
print(helpers.attachments.list("PROJECT-123").text)
print(helpers.changelogs.list("PROJECT-123").text)
```

## Structured issue-read migration

Issue reads no longer use a helper or a comma-delimited `fields` string. Pass one exact selector per sequence item, then optionally format the returned data:

```python
# Before (removed)
from jira2py.helpers import JiraHelpers

helpers = JiraHelpers(api)
api.issues.get_issue("PROJECT-123", fields="summary,status")
helpers.issues.read("PROJECT-123", extra_fields=["customfield_10001"])

# After
issue = api.issues.get_issue(
    "PROJECT-123",
    fields=["summary", "status", "customfield_10001"],
)
text = format_issue(
    issue,
    browse_url=f"{api.credentials.url}/browse/{issue['key']}",
)
```

The supplied selector sequence is forwarded unchanged: jira2py does not add fields, deduplicate selectors, or request an expansion. `None` omits `fields` and lets Jira choose its default unless raw `extra_params["fields"]` overrides it. Wildcards and negative selectors such as `"*all"` and `"-description"` can still return broad responses; choose projections deliberately.

## Jira account mentions in high-level Markdown

High-level Markdown write helpers recognize Jira account mentions in the canonical form
`[~accountId:<account-id>]`. Use a real opaque account ID from Jira user discovery,
not a display name:

```python
helpers.comments.add("PROJECT-123", "Please review: [~accountId:557057:User:AbC]")
```

This applies to issue create/edit descriptions, `environment`, compatible custom
textarea fields, comment add/update bodies, and worklog add/update comments. The
`accountId` label is case-insensitive on input, and IDs can contain `:`. A single
leading backslash escapes a token; malformed tokens and tokens inside Markdown code,
links, or images remain ordinary text rather than creating mentions. Bold, italic, and
strikethrough wrappers around a valid mention create an unmarked Jira mention, so that
formatting is discarded. Jira may notify the mentioned account where it supports
notifications; jira2py does not guarantee delivery.

Formatted ADF reads from `format_issue` and formatted comment/worklog helper output
present mentions with adf-bridge's identity-preserving `[~accountId:<id>]` tokens; a
mention's display text is not retained. jira2py does not traverse mentions or rewrite
rendered Markdown, so literal matching tokens, including code spans, remain literal.
Raw ADF in low-level responses and `HelperResult.data` remains unchanged. Native transition
`fields` / `update` mappings continue to accept Jira ADF directly and do not convert
Markdown mentions.

## Jira-managed Markdown images

High-level writes can turn an ordinary Markdown image into Jira-managed media when its
destination is the exact HTTPS attachment `content` URL returned by an upload on the
same issue:

```python
uploaded = helpers.attachments.upload("PROJECT-123", "screen.png")
content_url = uploaded.data[0]["content"]
helpers.issues.edit(
    "PROJECT-123",
    description=f"Full replacement description\n\n![Failure screen]({content_url})",
)
```

This applies only to issue **edit** `description`, `environment`, and compatible custom
textarea fields, plus comment add/update bodies and worklog add/update comments. The
attachment must be associated with that issue and have an `image/*` MIME type. The URL
must not contain a query or fragment delimiter, including bare `?` or `#`. External and
relative image destinations retain ordinary external-media behavior. These methods
replace their complete rich-text value; they do not append Markdown fragments.

Issue creation has no existing issue context, so an attachment-content image URL is
rejected before the create POST. Create the issue first, upload the image to its returned
key, then edit the complete rich-text field using the upload result's `content` URL.
Markdown image titles and dimensions are not preserved or authored. On formatted
ADF-to-Markdown reads, managed media has no recoverable attachment-content URL and is
shown only as readable attachment text; use raw ADF when Jira enrichments or exact media
structure must be retained. After a successful managed-media write, jira2py verifies the
raw persisted ADF structure. Jira-rendered HTML is not a runtime verification contract;
corroborate tenant rendering with authorized live end-to-end testing.

jira2py validates the observed Jira attachment `303` redirect shape privately before
writing. That Media Services mapping is compatibility-sensitive observed behavior, not a
public Jira mapping API. It never changes `download_attachment_content()`, which still
follows redirects and returns bytes. If a direct issue, comment, or worklog mutation
fails with a connection error or Jira 5xx, it may already have been applied. The helper
error sets `details["mutation_may_have_succeeded"]`; reread the affected resource before
retrying. jira2py does not retry or roll back after that uncertain failure.

## Workflow transitions

Discover the current issue's transitions and transition-screen metadata before choosing the Jira transition **ID**:

```python
metadata = helpers.metadata.transitions("PROJ-123")
print(metadata.text)
print(metadata.data)  # complete Jira transitions envelope, including fields

accepted = helpers.issues.transition(
    "PROJ-123",
    "31",
    fields={"resolution": {"name": "Done"}},
    update={"labels": [{"add": "released"}]},
)
assert accepted.data["verified"] is False
```

`fields` and `update` are Jira-native mappings and cannot share an exact field key. The helper's successful result means Jira accepted the request; it does not read the issue afterward. Verify the expected destination status and changed fields with `api.issues.get_issue()`. Transition names remain supported for compatibility, but IDs from fresh discovery avoid ambiguity.

## Documentation

- [Installation](https://jira2py.org/installation/)
- [Configuration and credential details](https://jira2py.org/guide/configuration/)
- [High-level helpers](https://jira2py.org/guide/high-level-helpers/)
- [API reference](https://jira2py.org/api/)
- [Full documentation](https://jira2py.org/)
- [Machine-readable documentation](https://jira2py.org/llms.txt) and [complete reference](https://jira2py.org/llms-full.txt)

## License

[MIT](LICENSE)
