Description
Problem trying to solve
While this library is fantastic, there are some capabilities I would like to include via extending the classes. Specifically, I want to be able to get an issue's fields by display name, hiding the several queries I must make to the REST API to get a custom field's display name.
In order to properly extend Field
, however, I have to either reimplement significant parts of the methods, or make the standard call, and re-instantiate all the returned issues.
For example:
def search_issues(
self,
jql_str: str,
startAt: int = 0,
maxResults: int = 50,
validate_query: bool = True,
fields: str | list[str] | None = "*all",
expand: str | None = None,
properties: str | None = None,
json_result: bool = False,
use_post: bool = False,
) -> dict[str, Any] | ResultList[Issue]:
result = super().search_issues(jql_str, startAt, maxResults, validate_query, fields, expand, properties, json_result, use_post)
if json_result:
return result
items = [ExtendedIssues(x) for x in result]
return ResultList(items, result.startAt, result.maxResults, result.total, result.isLast)
Possible solution(s)
A couple of thoughts come to mind:
- Allow us to specify custom types at client instantiation (probably the safest/easiest)
- Allow each public method that will return an issue type to have
item_type
exposed (probably bug-prone and messier) - Expose a GenericJiraClient that allows someone to implement a concrete client with the types passed into the class definition (may be most elegant, but could look funny with multiple concrete types passed in)
1. Example Exposing CustomTypes at Instantiation
This seems a bit friendlier
class CustomIssue(Issue):
def get_field(self, field_name: str, by_display_name: bool = False) -> Any:
if not hasattr(self.fields, field_name) and by_display_name:
return ...
return super().get_field(field_name)
client = JIRA("server", auth=(), item_types={"issue": CustomIssue}) # Will only replace 'issue' type. All other defaults remain the same
issues = client.search_issues("project=ABC and status=closed")
field = issues[0].get_field("Epic Status", by_display_name=True)
2. Example Exposing item_type
for search_issues
This one feels like it would be the most error-prone, since I think you would have to expose something similar for 9 total methods related to fields.
class CustomIssue(Issue):
def get_field(self, field_name: str, by_display_name: bool = False) -> Any:
if not hasattr(self.fields, field_name) and by_display_name:
return ...
return super().get_field(field_name)
client = JIRA(...)
issues = client.search_issues("project=ABC and status=closed", item_type=CustomIssue)
field = issues[0].get_field("Epic Status", by_display_name=True)
3. Example with a GeneticJiraClient
The biggest problem with this is there appears to be 41 different classes inheriting from
Resource
Could very easily be the most arduous of the three.
from typing import TypeVar
from jira.resources import Issue, User
T = TypeVar("T", Issue)
U = TypeVar("U", User)
# et cetera
class GenericJiraClient(Generic[T, U, ...]): ...
class JIRA(GenericJiraClient[Issue, User, ....]): ...
# Or, custom:
class MyJiraClient(GenericJiraClient[CustomIssue]): ...
Alternatives
A quicker solution would probably be to just expose methods like these via the JIRA class, and other classes, directly. But I am thinking about extensibility in the field, for users/teams who don't want to wait for a PR to get merged and a new release to happen.
Additional Context
I took a look through the source code, and no other approaches came to mind.