Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package python-rt for openSUSE:Factory checked in at 2026-03-25 21:19:57 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-rt (Old) and /work/SRC/openSUSE:Factory/.python-rt.new.8177 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-rt" Wed Mar 25 21:19:57 2026 rev:26 rq:1342395 version:3.6.0 Changes: -------- --- /work/SRC/openSUSE:Factory/python-rt/python-rt.changes 2026-01-08 15:29:53.651239189 +0100 +++ /work/SRC/openSUSE:Factory/.python-rt.new.8177/python-rt.changes 2026-03-27 06:49:01.420776599 +0100 @@ -1,0 +2,12 @@ +Wed Mar 25 08:07:01 UTC 2026 - Dirk Müller <[email protected]> + +- update to 3.6.0: + * Parameters search_params and catalog_id used in asset search + are now optional + * If catalog_id is set to None, search all catalogs + * Added option to define all query parameters for get/search + asset and ticket endpoints + * Fixed typing of methods that returned a partially unknown + dictionary + +------------------------------------------------------------------- Old: ---- rt-3.4.0.tar.gz New: ---- rt-3.6.0.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-rt.spec ++++++ --- /var/tmp/diff_new_pack.4PetH6/_old 2026-03-27 06:49:02.760831782 +0100 +++ /var/tmp/diff_new_pack.4PetH6/_new 2026-03-27 06:49:02.760831782 +0100 @@ -18,7 +18,7 @@ %{?sle15_python_module_pythons} Name: python-rt -Version: 3.4.0 +Version: 3.6.0 Release: 0 Summary: Python interface to Request Tracker API License: GPL-3.0-only ++++++ rt-3.4.0.tar.gz -> rt-3.6.0.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python-rt-3.4.0/.codespell_ignore new/python-rt-3.6.0/.codespell_ignore --- old/python-rt-3.4.0/.codespell_ignore 2025-11-28 08:11:12.000000000 +0100 +++ new/python-rt-3.6.0/.codespell_ignore 2026-02-06 23:13:59.000000000 +0100 @@ -1,2 +1,2 @@ requestor -requestors \ No newline at end of file +requestors diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python-rt-3.4.0/.github/ISSUE_TEMPLATE/bug_report.md new/python-rt-3.6.0/.github/ISSUE_TEMPLATE/bug_report.md --- old/python-rt-3.4.0/.github/ISSUE_TEMPLATE/bug_report.md 2025-11-28 08:11:12.000000000 +0100 +++ new/python-rt-3.6.0/.github/ISSUE_TEMPLATE/bug_report.md 2026-02-06 23:13:59.000000000 +0100 @@ -21,10 +21,10 @@ A clear and concise description of what you expected to happen. **Environment (please complete the following information):** - - OS: - - RT version: - - Python version: - - python-rt version: + - OS: + - RT version: + - Python version: + - python-rt version: **Additional context** Add any other context about the problem here. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python-rt-3.4.0/.pre-commit-config.yaml new/python-rt-3.6.0/.pre-commit-config.yaml --- old/python-rt-3.4.0/.pre-commit-config.yaml 1970-01-01 01:00:00.000000000 +0100 +++ new/python-rt-3.6.0/.pre-commit-config.yaml 2026-02-06 23:13:59.000000000 +0100 @@ -0,0 +1,56 @@ +# See https://pre-commit.com for more information +# See https://pre-commit.com/hooks.html for more hooks +default_language_version: + python: ">=3.13" + +exclude: | + (?x)^( + doc/.*| + rt.egg-info/.*| + venv/.*| + .venv/.*| + noxfile.py| + )$ + +repos: + - repo: builtin + hooks: + - id: check-added-large-files + args: ['--maxkb=750'] + exclude: ^uv.lock$ + - id: end-of-file-fixer + - id: trailing-whitespace + args: [--markdown-linebreak-ext=md] + - id: fix-byte-order-marker + - id: check-json + - id: check-toml + - id: check-yaml + - id: mixed-line-ending + args: [--fix=lf] + - id: check-symlinks + - id: check-merge-conflict + - id: check-executables-have-shebangs + + - repo: local + hooks: + - id: local-ruff-check + name: ruff check + entry: uv run ruff check --force-exclude --fix --exit-non-zero-on-fix + require_serial: true + language: system + types: [python] + + - id: local-ruff-format + name: ruff format + entry: uv run ruff format --force-exclude --exit-non-zero-on-format + require_serial: true + language: system + types: [python] + + - id: mypy + name: mypy + entry: uv run mypy + files: rt + require_serial: true + language: python + types: [python] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python-rt-3.4.0/CHANGELOG.md new/python-rt-3.6.0/CHANGELOG.md --- old/python-rt-3.4.0/CHANGELOG.md 2025-11-28 08:11:12.000000000 +0100 +++ new/python-rt-3.6.0/CHANGELOG.md 2026-02-06 23:13:59.000000000 +0100 @@ -3,6 +3,16 @@ This project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). +## [v3.6.0], 2026-02-06 +### Added +- Parameters search_params and catalog_id used in asset search are now optional +- If catalog_id is set to None, search all catalogs + +## [v3.5.0], 2026-02-04 +### Added +- Added option to define all query parameters for get/search asset and ticket endpoints +- Fixed typing of methods that returned a partially unknown dictionary + ## [v3.4.0], 2025-11-21 ### Added - Added functionality for some of the asset endpoints (get, create, edit, search, get history) @@ -141,9 +151,9 @@ - Importing the `rt` class changed in order to better accommodate the new `rest2` implementation. - Where one use to be able to import `rt` using: `from rt import Rt` - + you now have to use the following syntax: - + `from rt.rest1 import Rt` - Importing the `rt` module does no longer import all exceptions but only the core `RtError` exception. If you require other exceptions, please import them from `rt.exceptions`. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python-rt-3.4.0/README.rst new/python-rt-3.6.0/README.rst --- old/python-rt-3.4.0/README.rst 2025-11-28 08:11:12.000000000 +0100 +++ new/python-rt-3.6.0/README.rst 2026-02-06 23:13:59.000000000 +0100 @@ -8,7 +8,7 @@ :target: https://badge.fury.io/py/rt ============================================== - Rt - Python interface to Request Tracker API + Rt - Python interface to Request Tracker API ============================================== Python implementation of REST API described here: @@ -108,7 +108,7 @@ >>> print(content) - + Please use docstrings to see how to use different functions. They are written in ReStructuredText. You can also generate HTML documentation by running ``make html`` in doc directory (Sphinx required). diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python-rt-3.4.0/doc/conf.py new/python-rt-3.6.0/doc/conf.py --- old/python-rt-3.4.0/doc/conf.py 2025-11-28 08:11:12.000000000 +0100 +++ new/python-rt-3.6.0/doc/conf.py 2026-02-06 23:13:59.000000000 +0100 @@ -1,4 +1,3 @@ -# -*- coding: utf-8 -*- # # rt documentation build configuration file, created by # sphinx-quickstart on Thu Jan 10 16:31:25 2013. @@ -11,9 +10,9 @@ # All configuration values have a default; values that are commented out # serve to show the default. +import importlib.metadata import os import sys -import importlib.metadata sys.path.insert(0, os.path.abspath('..')) @@ -35,7 +34,7 @@ 'member-order': 'alphabetical', 'special-members': '__init__', 'undoc-members': True, - 'exclude-members': '__weakref__' + 'exclude-members': '__weakref__', } # Add any paths that contain templates here, relative to this directory. @@ -51,8 +50,8 @@ master_doc = 'index' # General information about the project. -project = u'rt' -copyright = u'2013-present, A project founded by Jiri Machalek and maintained by the python-rt community.' +project = 'rt' +copyright = '2013-present, A project founded by Jiri Machalek and maintained by the python-rt community.' # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the @@ -186,10 +185,8 @@ latex_elements = { # The paper size ('letterpaper' or 'a4paper'). # 'papersize': 'letterpaper', - # The font size ('10pt', '11pt' or '12pt'). # 'pointsize': '10pt', - # Additional stuff for the LaTeX preamble. # 'preamble': '', } @@ -197,8 +194,7 @@ # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, author, documentclass [howto/manual]). latex_documents = [ - ('index', 'rt.tex', u'rt Documentation', - u'Jiri Machalek', 'manual'), + ('index', 'rt.tex', 'rt Documentation', 'Jiri Machalek', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of @@ -226,10 +222,7 @@ # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). -man_pages = [ - ('index', 'rt', u'rt Documentation', - [u'Jiri Machalek'], 1) -] +man_pages = [('index', 'rt', 'rt Documentation', ['Jiri Machalek'], 1)] # If true, show URL addresses after external links. # man_show_urls = False @@ -241,9 +234,7 @@ # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ - ('index', 'rt', u'rt Documentation', - u'Jiri Machalek', 'rt', 'One line description of project.', - 'Miscellaneous'), + ('index', 'rt', 'rt Documentation', 'Jiri Machalek', 'rt', 'One line description of project.', 'Miscellaneous'), ] # Documents to append as an appendix to all manuals. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python-rt-3.4.0/doc/glossary.rst new/python-rt-3.6.0/doc/glossary.rst --- old/python-rt-3.4.0/doc/glossary.rst 2025-11-28 08:11:12.000000000 +0100 +++ new/python-rt-3.6.0/doc/glossary.rst 2026-02-06 23:13:59.000000000 +0100 @@ -12,4 +12,3 @@ REST Representational state transfer, a style of software architecture for distributed hypermedia systems such as the World Wide Web - diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python-rt-3.4.0/doc/index.rst new/python-rt-3.6.0/doc/index.rst --- old/python-rt-3.4.0/doc/index.rst 2025-11-28 08:11:12.000000000 +0100 +++ new/python-rt-3.6.0/doc/index.rst 2026-02-06 23:13:59.000000000 +0100 @@ -42,7 +42,7 @@ pip install rt Using project git repository:: - + git clone https://github.com/python-rt/python-rt Indices and tables @@ -51,4 +51,3 @@ * :ref:`genindex` * :ref:`modindex` * :ref:`search` - diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python-rt-3.4.0/noxfile.py new/python-rt-3.6.0/noxfile.py --- old/python-rt-3.4.0/noxfile.py 2025-11-28 08:11:12.000000000 +0100 +++ new/python-rt-3.6.0/noxfile.py 2026-02-06 23:13:59.000000000 +0100 @@ -12,5 +12,5 @@ @nox.session() def lint(session): - session.install(".[test,dev]") + session.install('.[test,dev]') session.run('ruff', 'rt') diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python-rt-3.4.0/pyproject.toml new/python-rt-3.6.0/pyproject.toml --- old/python-rt-3.4.0/pyproject.toml 2025-11-28 08:11:12.000000000 +0100 +++ new/python-rt-3.6.0/pyproject.toml 2026-02-06 23:13:59.000000000 +0100 @@ -47,6 +47,7 @@ "types-requests", "codespell", "bandit", + "prek>=0.3.2", ] test = [ "pytest~=8.3", @@ -97,7 +98,11 @@ line-length = 140 indent-width = 4 target-version = "py39" -include = ["pyproject.toml", "rt/**/*.py", "tests/**/*.py"] +include = [ + "pyproject.toml", + "rt/**/*.py", + "tests/**/*.py" +] [tool.ruff.lint] select = [ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python-rt-3.4.0/rt/rest1.py new/python-rt-3.6.0/rt/rest1.py --- old/python-rt-3.4.0/rt/rest1.py 2025-11-28 08:11:12.000000000 +0100 +++ new/python-rt-3.6.0/rt/rest1.py 2026-02-06 23:13:59.000000000 +0100 @@ -258,7 +258,7 @@ :rtype: int """ try: - return int(msg.split('\n')[0].split(' ')[1]) + return int(msg.split('\n', maxsplit=1)[0].split(' ')[1]) except (ValueError, AttributeError, LookupError): return None diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python-rt-3.4.0/rt/rest2.py new/python-rt-3.6.0/rt/rest2.py --- old/python-rt-3.4.0/rt/rest2.py 2025-11-28 08:11:12.000000000 +0100 +++ new/python-rt-3.6.0/rt/rest2.py 2026-02-06 23:13:59.000000000 +0100 @@ -409,7 +409,7 @@ return res - def new_correspondence(self, queue: typing.Optional[typing.Union[str, object]] = None) -> typing.Iterator[dict]: + def new_correspondence(self, queue: typing.Optional[typing.Union[str, object]] = None) -> typing.Iterator[dict[str, typing.Any]]: """Obtains tickets changed by other users than the system one. :param queue: Queue where to search @@ -421,7 +421,7 @@ """ return self.search(queue=queue, order='-LastUpdated') - def last_updated(self, since: str, queue: typing.Optional[str] = None) -> typing.Iterator[dict]: + def last_updated(self, since: str, queue: typing.Optional[str] = None) -> typing.Iterator[dict[str, typing.Any]]: """Obtains tickets changed after given date. :param since: Date as string in form '2011-02-24' @@ -460,9 +460,9 @@ queue: typing.Optional[typing.Union[str, object]] = None, order: typing.Optional[str] = None, raw_query: typing.Optional[str] = None, - query_format: typing.Union[str, list[str]] = 'l', + query_format: typing.Union[str, list[str], dict[str, str]] = 'l', **kwargs: typing.Any, - ) -> typing.Iterator[dict]: + ) -> typing.Iterator[dict[str, typing.Any]]: r"""Search arbitrary needles in given fields and queue. Example:: @@ -554,6 +554,8 @@ if isinstance(query_format, list): get_params['fields'] = ','.join(query_format) + elif isinstance(query_format, dict): + get_params = {**get_params, **query_format} elif query_format == 'l': get_params['fields'] = ( 'Owner,Status,Created,Subject,Queue,CustomFields,Requestor,Cc,AdminCc,Started,Created,TimeEstimated,Due,Type,InitialPriority,Priority,TimeLeft,LastUpdated' @@ -564,13 +566,16 @@ yield from self.__paged_request(url, params=get_params) - def get_ticket(self, ticket_id: typing.Union[str, int]) -> dict: + def get_ticket( + self, ticket_id: typing.Union[str, int], query_format: typing.Union[dict[str, str], None] = None + ) -> dict[str, typing.Any]: """Fetch ticket by its ID. :param ticket_id: ID of demanded ticket + :param query_format: Returned fields to be populated :returns: Dictionary with key, value pairs for ticket with - *ticket_id* or None if ticket does not exist. List of keys: + *ticket_id* or None if ticket does not exist. List of keys (when query_format is None): * id * numerical_id @@ -597,7 +602,10 @@ :raises UnexpectedMessageFormatError: Unexpected format of returned message. :raises NotFoundError: If there is no ticket with the specified ticket_id. """ - res = self.__request(f'ticket/{ticket_id}', get_params={'fields[Queue]': 'Name'}) + if not query_format: + query_format = {} + + res = self.__request(f'ticket/{ticket_id}', get_params={'fields[Queue]': 'Name', **query_format}) if not isinstance(res, dict): # pragma: no cover raise UnexpectedResponseError(str(res)) @@ -916,7 +924,7 @@ return attachments - def get_attachment(self, attachment_id: typing.Union[str, int]) -> dict: + def get_attachment(self, attachment_id: typing.Union[str, int]) -> dict[str, typing.Any]: """Get attachment. :param attachment_id: ID of attachment to fetch @@ -1628,12 +1636,13 @@ return response - def get_asset(self, asset_id: typing.Union[str, int]) -> dict[str, typing.Any]: + def get_asset(self, asset_id: typing.Union[str, int], query_format: typing.Optional[dict[str, str]] = None) -> dict[str, typing.Any]: """ Get asset. :param asset_id: Asset ID. - :return: Asset. + :param query_format: Returned fields to be populated. + :return: Asset (when `query_format` is `None`). id: int Lifecycle: str Disabled: str @@ -1651,7 +1660,10 @@ Owner: dict[str, str] CustomFields: list[dict[str, typing.Any]] """ - response = self.__request(f'asset/{asset_id}') + if not query_format: + query_format = {} + + response = self.__request(f'asset/{asset_id}', get_params=query_format) self.logger.debug(str(response)) @@ -1696,23 +1708,28 @@ return isinstance(response, list) def search_assets( - self, catalog_id: typing.Union[str, int], search_params: list[dict[str, typing.Any]], fields: str = "Owner,Description,Status" + self, + catalog_id: typing.Union[str, int, None] = None, + search_params: typing.Union[list[dict[str, typing.Any]], None] = None, + query_format: typing.Optional[typing.Union[str, list[str], dict[str, str]]] = None, ) -> typing.Iterator[dict[str, typing.Any]]: """ Search assets in a catalog. Example:: - client = Rt(...) - client.search_assets(1, [{"field": "Name", "value": "NameOfMyAsset"}]) + >>> client = Rt(...) + >>> client.search_assets(1, [{"field": "Name", "value": "NameOfMyAsset"}], "Owner,Status") + >>> client.search_assets(1, [{"field": "Name", "value": "NameOfMyAsset"}], ["Owner", "Status"]) + >>> client.search_assets(1, [{"field": "Name", "value": "NameOfMyAsset"}], {"fields": "Owner,Status", "fields[Owner]": "id,Name"}) - :param catalog_id: Catalog ID. + :param catalog_id: Catalog ID. Use `None` to search all catalogs. :param search_params: Params used to filter the results. field: str value: str | int operator: Literal[">", "<", "=", "!=", "LIKE", "NOT LIKE", ">=", "<="] | None - :param fields: Fields to return separated by a comma. - :return: Found assets. The following is returned with the default `fields` + :param query_format: Returned fields to be populated. + :return: Found assets. The following is returned by default (when `query_format` is `None`) { 'Description': '', 'id': '1', @@ -1722,9 +1739,22 @@ 'type': 'asset' } """ - search_params.append({'field': 'Catalog', 'value': catalog_id, 'operator': '='}) + if not search_params: + search_params = [] + if catalog_id: + search_params.append({'field': 'Catalog', 'value': catalog_id, 'operator': '='}) + else: + search_params.append({'field': 'Catalog', 'value': '%', 'operator': 'LIKE'}) + + get_params = {'fields': 'Owner,Description,Status'} + if isinstance(query_format, dict): + get_params = {**get_params, **query_format} + elif isinstance(query_format, list): + get_params['fields'] = ','.join(query_format) + elif isinstance(query_format, str): + get_params['fields'] = query_format - yield from self.__paged_request('assets', json_data=search_params, params={"fields": fields}) + yield from self.__paged_request('assets', json_data=search_params, params=get_params) def get_asset_history(self, asset_id: typing.Union[str, int]) -> typing.Iterator[dict[str, typing.Any]]: """ @@ -1961,7 +1991,7 @@ page: int = 1, per_page: int = 20, recurse: bool = True, - ) -> collections.abc.AsyncIterator: + ) -> collections.abc.AsyncIterator[dict[str, typing.Any]]: """Request using pagination for :term:`API`. :param selector: End part of URL which completes self.url parameter @@ -2080,7 +2110,9 @@ return res - async def new_correspondence(self, queue: typing.Optional[typing.Union[str, object]] = None) -> collections.abc.AsyncIterator: + async def new_correspondence( + self, queue: typing.Optional[typing.Union[str, object]] = None + ) -> collections.abc.AsyncIterator[dict[str, typing.Any]]: """Obtains tickets changed by other users than the system one. :param queue: Queue where to search @@ -2089,11 +2121,11 @@ the system one, ordered in decreasing order by LastUpdated. Each ticket is dictionary, the same as in :py:meth:`~Rt.get_ticket`. - collections.abc.AsyncIterator[dict] + collections.abc.AsyncIterator[dict[str, typing.Any]] """ return self.search(queue=queue, order='-LastUpdated') - async def last_updated(self, since: str, queue: typing.Optional[str] = None) -> collections.abc.AsyncIterator: + async def last_updated(self, since: str, queue: typing.Optional[str] = None) -> collections.abc.AsyncIterator[dict[str, typing.Any]]: """Obtains tickets changed after given date. :param since: Date as string in form '2011-02-24' @@ -2103,7 +2135,7 @@ *since* ordered in decreasing order by LastUpdated. Each ticket is a dictionary, the same as in :py:meth:`~Rt.get_ticket`. - collections.abc.AsyncIterator[dict] + collections.abc.AsyncIterator[dict[str, typing.Any]] :raises InvalidUseError: If the specified date is of an unsupported format. """ @@ -2133,9 +2165,9 @@ queue: typing.Optional[typing.Union[str, object]] = None, order: typing.Optional[str] = None, raw_query: typing.Optional[str] = None, - query_format: typing.Union[str, list[str]] = 'l', + query_format: typing.Union[str, list[str], dict[str, str]] = 'l', **kwargs: typing.Any, - ) -> collections.abc.AsyncIterator: + ) -> collections.abc.AsyncIterator[dict[str, typing.Any]]: r"""Search arbitrary needles in given fields and queue. Example:: @@ -2186,7 +2218,7 @@ :returns: Iterator over matching tickets. Each ticket is the same dictionary as in :py:meth:`~Rt.get_ticket`. - collections.abc.AsyncIterator[dict] + collections.abc.AsyncIterator[dict[str, typing.Any]] :raises: UnexpectedMessageFormatError: Unexpected format of returned message. InvalidQueryError: If raw query is malformed """ @@ -2228,6 +2260,8 @@ if isinstance(query_format, list): get_params['fields'] = ','.join(query_format) + elif isinstance(query_format, dict): + get_params = {**get_params, **query_format} elif query_format == 'l': get_params['fields'] = ( 'Owner,Status,Created,Subject,Queue,CustomFields,Requestor,Cc,AdminCc,Started,Created,TimeEstimated,Due,Type,InitialPriority,Priority,TimeLeft,LastUpdated' @@ -2239,13 +2273,16 @@ async for item in self.__paged_request(url, params=get_params): yield item - async def get_ticket(self, ticket_id: typing.Union[str, int]) -> dict: + async def get_ticket( + self, ticket_id: typing.Union[str, int], query_format: typing.Union[dict[str, str], None] = None + ) -> dict[str, typing.Any]: """Fetch ticket by its ID. :param ticket_id: ID of demanded ticket + :param query_format: Returned fields to be populated :returns: Dictionary with key, value pairs for ticket with - *ticket_id* or None if ticket does not exist. List of keys: + *ticket_id* or None if ticket does not exist. List of keys (when query_format is None): * id * numerical_id @@ -2272,7 +2309,10 @@ :raises UnexpectedMessageFormatError: Unexpected format of returned message. :raises NotFoundError: If there is no ticket with the specified ticket_id. """ - res = await self.__request(f'ticket/{ticket_id}', get_params={'fields[Queue]': 'Name'}) + if not query_format: + query_format = {} + + res = await self.__request(f'ticket/{ticket_id}', get_params={'fields[Queue]': 'Name', **query_format}) if not isinstance(res, dict): # pragma: no cover raise UnexpectedResponseError(str(res)) @@ -2374,7 +2414,7 @@ return bool(msg[0]) - async def get_ticket_history(self, ticket_id: typing.Union[str, int]) -> collections.abc.AsyncIterator: + async def get_ticket_history(self, ticket_id: typing.Union[str, int]) -> collections.abc.AsyncIterator[dict[str, typing.Any]]: """Get set of short history items. :param ticket_id: ID of ticket @@ -2527,7 +2567,7 @@ self, ticket_id: typing.Union[str, int], query_filter: typing.Optional[list[dict[str, str]]] = None, - ) -> collections.abc.AsyncIterator: + ) -> collections.abc.AsyncIterator[dict[str, typing.Any]]: """Get attachment list for a given ticket. Example of a return result: @@ -2563,7 +2603,7 @@ self, ticket_id: typing.Union[str, int], query_filter: typing.Optional[list[dict[str, str]]] = None, - ) -> collections.abc.AsyncIterator: + ) -> collections.abc.AsyncIterator[int]: """Get IDs of attachments for given ticket. :param ticket_id: ID of @@ -2581,7 +2621,7 @@ ): yield int(item['id']) - async def get_attachment(self, attachment_id: typing.Union[str, int]) -> dict: + async def get_attachment(self, attachment_id: typing.Union[str, int]) -> dict[str, typing.Any]: """Get attachment. :param attachment_id: ID of attachment to fetch @@ -2953,7 +2993,7 @@ return res - async def get_all_queues(self, include_disabled: bool = False) -> collections.abc.AsyncIterator: + async def get_all_queues(self, include_disabled: bool = False) -> collections.abc.AsyncIterator[dict[str, typing.Any]]: """Return a list of all queues. Example of a return result: @@ -3293,12 +3333,15 @@ return response - async def get_asset(self, asset_id: typing.Union[str, int]) -> dict[str, typing.Any]: + async def get_asset( + self, asset_id: typing.Union[str, int], query_format: typing.Optional[dict[str, str]] = None + ) -> dict[str, typing.Any]: """ Get asset. :param asset_id: Asset ID. - :return: Asset. + :param query_format: Returned fields to be populated. + :return: Asset (when `query_format` is `None`). id: int Lifecycle: str Disabled: str @@ -3316,7 +3359,10 @@ Owner: dict[str, str] CustomFields: list[dict[str, typing.Any]] """ - response = await self.__request(f'asset/{asset_id}') + if not query_format: + query_format = {} + + response = await self.__request(f'asset/{asset_id}', get_params=query_format) self.logger.debug(str(response)) @@ -3361,23 +3407,28 @@ return isinstance(response, list) async def search_assets( - self, catalog_id: typing.Union[str, int], search_params: list[dict[str, typing.Any]], fields: str = "Owner,Description,Status" + self, + catalog_id: typing.Union[str, int, None] = None, + search_params: typing.Union[list[dict[str, typing.Any]], None] = None, + query_format: typing.Optional[typing.Union[str, list[str], dict[str, str]]] = None, ) -> collections.abc.AsyncIterator[dict[str, typing.Any]]: """ Search assets in a catalog. Example:: - client = AsyncRt(...) - await client.search_assets(1, [{"field": "Name", "value": "NameOfMyAsset"}]) + >>> client = AsyncRt(...) + >>> await client.search_assets(1, [{"field": "Name", "value": "NameOfMyAsset"}], "Owner,Status") + >>> await client.search_assets(1, [{"field": "Name", "value": "NameOfMyAsset"}], ["Owner", "Status"]) + >>> await client.search_assets(1, [{"field": "Name", "value": "NameOfMyAsset"}], {"fields": "Owner,Status", "fields[Owner]": "id,Name"}) - :param catalog_id: Catalog ID. + :param catalog_id: Catalog ID. Use `None` to search all catalogs. :param search_params: Params used to filter the results. field: str value: str | int operator: Literal[">", "<", "=", "!=", "LIKE", "NOT LIKE", ">=", "<="] | None - :param fields: Fields to return separated by a comma. - :return: Found assets. The following is returned with the default `fields` + :param query_format: Returned fields to be populated. + :return: Found assets. The following is returned by default (when `query_format` is `None`) { 'Description': '', 'id': '1', @@ -3387,12 +3438,25 @@ 'type': 'asset' } """ - search_params.append({'field': 'Catalog', 'value': catalog_id, 'operator': '='}) + if not search_params: + search_params = [] + if catalog_id: + search_params.append({'field': 'Catalog', 'value': catalog_id, 'operator': '='}) + else: + search_params.append({'field': 'Catalog', 'value': '%', 'operator': 'LIKE'}) + + get_params = {'fields': 'Owner,Description,Status'} + if isinstance(query_format, dict): + get_params = {**get_params, **query_format} + elif isinstance(query_format, list): + get_params['fields'] = ','.join(query_format) + elif isinstance(query_format, str): + get_params['fields'] = query_format - async for item in self.__paged_request('assets', json_data=search_params, params={"fields": fields}): + async for item in self.__paged_request('assets', json_data=search_params, params=get_params): yield item - async def get_asset_history(self, asset_id: typing.Union[str, int]) -> collections.abc.AsyncIterator[list[dict[str, typing.Any]]]: + async def get_asset_history(self, asset_id: typing.Union[str, int]) -> collections.abc.AsyncIterator[dict[str, typing.Any]]: """ Get asset history. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python-rt-3.4.0/tests/test_basic.py new/python-rt-3.6.0/tests/test_basic.py --- old/python-rt-3.4.0/tests/test_basic.py 2025-11-28 08:11:12.000000000 +0100 +++ new/python-rt-3.6.0/tests/test_basic.py 2026-02-06 23:13:59.000000000 +0100 @@ -81,6 +81,13 @@ assert search_result[0]['Status'] == 'new' assert 'Requestor' not in search_result[0].keys() + # search with query_format field dict + search_result = list(rt_connection.search(Subject=ticket_subject, query_format={'fields': 'Owner', 'fields[Owner]': 'Name'})) + assert len(search_result) == 1 + assert 'Subject' not in search_result[0] + assert 'Status' not in search_result[0] + assert search_result[0]['Owner']['Name'] == 'Nobody' + # raw search search_result = list(rt_connection.search(raw_query=f'Subject="{ticket_subject}"')) assert len(search_result) == 1 @@ -448,7 +455,10 @@ def test_assets(rt_connection: rt.rest2.Rt): - asset_id = rt_connection.create_asset('test', 1, Creator='root') + asset_name = random_string() + asset_name_new = random_string() + + asset_id = rt_connection.create_asset(asset_name, 1, Creator='root') assert asset_id asset = rt_connection.get_asset(asset_id) @@ -457,10 +467,34 @@ asset_history = rt_connection.get_asset_history(asset_id) assert len(list(asset_history)) == 1 - asset_edited = rt_connection.edit_asset(asset_id, Name='test2') + asset_edited = rt_connection.edit_asset(asset_id, Name=asset_name_new) assert asset_edited - search = rt_connection.search_assets(1, [{'field': 'Name', 'value': 'test2'}]) + search = rt_connection.search_assets(1, [{'field': 'Name', 'value': asset_name_new}]) + items = list(search) + assert len(items) == 1 + assert items[0]['Status'] == 'new' + + search = rt_connection.search_assets(1, [{'field': 'Name', 'value': asset_name_new}], query_format='Owner') + items = list(search) + assert len(items) == 1 + assert items[0]['Owner']['id'] == 'Nobody' + assert 'Status' not in items[0] + + search = rt_connection.search_assets(1, [{'field': 'Name', 'value': asset_name_new}], query_format=['Owner']) + items = list(search) + assert len(items) == 1 + assert items[0]['Owner']['id'] == 'Nobody' + assert 'Status' not in items[0] + + search = rt_connection.search_assets() + items = list(search) + assert items + + search = rt_connection.search_assets( + 1, [{'field': 'Name', 'value': asset_name_new}], query_format={'fields': 'Owner', 'fields[Owner]': 'Name'} + ) items = list(search) assert len(items) == 1 - assert items[0]["Status"] == "new" + assert items[0]['Owner']['Name'] == 'Nobody' + assert 'Status' not in items[0] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/python-rt-3.4.0/tests/test_basic_async.py new/python-rt-3.6.0/tests/test_basic_async.py --- old/python-rt-3.4.0/tests/test_basic_async.py 2025-11-28 08:11:12.000000000 +0100 +++ new/python-rt-3.6.0/tests/test_basic_async.py 2026-02-06 23:13:59.000000000 +0100 @@ -84,6 +84,15 @@ assert search_result[0]['Status'] == 'new' assert 'Requestor' not in search_result[0].keys() + # search with query_format field dict + search_result = [ + item async for item in async_rt_connection.search(Subject=ticket_subject, query_format={'fields': 'Owner', 'fields[Owner]': 'Name'}) + ] + assert len(search_result) == 1 + assert 'Subject' not in search_result[0] + assert 'Status' not in search_result[0] + assert search_result[0]['Owner']['Name'] == 'Nobody' + # raw search search_result = [item async for item in async_rt_connection.search(raw_query=f'Subject="{ticket_subject}"')] assert len(search_result) == 1 @@ -461,7 +470,10 @@ @pytest.mark.asyncio async def test_assets(async_rt_connection: rt.rest2.AsyncRt): - asset_id = await async_rt_connection.create_asset('test', 1, Creator='root') + asset_name = random_string() + asset_name_new = random_string() + + asset_id = await async_rt_connection.create_asset(asset_name, 1, Creator='root') assert asset_id asset = await async_rt_connection.get_asset(asset_id) @@ -470,10 +482,34 @@ asset_history = [item async for item in async_rt_connection.get_asset_history(asset_id)] assert len(asset_history) == 1 - asset_edited = await async_rt_connection.edit_asset(asset_id, Name='test2async') + asset_edited = await async_rt_connection.edit_asset(asset_id, Name=asset_name_new) assert asset_edited - search = async_rt_connection.search_assets(1, [{'field': 'Name', 'value': 'test2async'}]) + search = async_rt_connection.search_assets(1, [{'field': 'Name', 'value': asset_name_new}]) + items = [item async for item in search] + assert len(items) == 1 + assert items[0]['Status'] == 'new' + + search = async_rt_connection.search_assets(1, [{'field': 'Name', 'value': asset_name_new}], query_format='Owner') + items = [item async for item in search] + assert len(items) == 1 + assert items[0]['Owner']['id'] == 'Nobody' + assert 'Status' not in items[0] + + search = async_rt_connection.search_assets(1, [{'field': 'Name', 'value': asset_name_new}], query_format=['Owner']) + items = [item async for item in search] + assert len(items) == 1 + assert items[0]['Owner']['id'] == 'Nobody' + assert 'Status' not in items[0] + + search = async_rt_connection.search_assets() + items = [item async for item in search] + assert items + + search = async_rt_connection.search_assets( + 1, [{'field': 'Name', 'value': asset_name_new}], query_format={'fields': 'Owner', 'fields[Owner]': 'Name'} + ) items = [item async for item in search] assert len(items) == 1 - assert items[0]["Status"] == "new" + assert items[0]['Owner']['Name'] == 'Nobody' + assert 'Status' not in items[0]
