Add missing domain classes, sub-resource methods, and utility APIs to close gap vs Xurrent REST API v1 - #30
Merged
Conversation
…jects, contracts, knowledge_articles, risks, service_offerings, skill_pools, closure_codes Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: fasteiner <75947402+fasteiner@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: fasteiner <75947402+fasteiner@users.noreply.github.com>
- Requests: get_attachments, get_knowledge_articles, get_automation_rules, get_satisfaction_feedback, get_tags, get_watches - Tasks: get_notes, add_note, get_approvals, get_cis, get_predecessors, get_successors, get_service_instances, get_automation_rules - Workflows: get_notes, add_note, get_automation_rules, get_phases, get_requests, get_problems - People: get_cis, get_addresses, get_contacts, get_permissions, get_ci_coverages, get_sla_coverages, get_service_coverages, get_out_of_office_periods, get_skill_pools - Organizations: get_addresses, get_contacts, get_contracts, get_risks, get_slas, get_time_allocations - Services: get_workflows, get_request_templates, get_risks, get_service_instances, get_slas, get_service_offerings - Calendars: get_duration, get_hours, get_holidays - Teams: get_service_instances - Holidays: get_calendars Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: fasteiner <75947402+fasteiner@users.noreply.github.com>
…exports - Add search(), bulk_import(), list_archive(), list_trash(), list_audit_lines() methods to XurrentApiHelper - Populate __init__.py with exports for all domain classes across all modules Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: fasteiner <75947402+fasteiner@users.noreply.github.com>
…methods - test_problems.py: Problem CRUD, notes, requests, workflows - test_service_instances.py: ServiceInstance CRUD, CIs, users - test_releases.py: Release CRUD, archive/trash/restore, workflows, notes - test_projects.py: Project CRUD, archive/trash/restore, tasks/phases/workflows/notes - test_contracts.py: Contract CRUD, CIs - test_knowledge_articles.py: KnowledgeArticle CRUD, archive/trash/restore, requests/service_instances/translations - test_risks.py: Risk CRUD, archive/trash/restore, organizations/projects/services - test_service_offerings.py: ServiceOffering CRUD - test_skill_pools.py: SkillPool CRUD, enable/disable, members/effort_classes - test_closure_codes.py: ClosureCode CRUD - test_new_sub_resources.py: new sub-resource methods on Request, Task, Workflow, Person, Organization, Service, Calendar, Holiday, Team; core search/bulk_import/ list_archive/list_trash/list_audit_lines utilities Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Co-authored-by: fasteiner <75947402+fasteiner@users.noreply.github.com>
…01 unit tests Agent-Logs-Url: https://github.com/fasteiner/xurrent-python/sessions/7ce5e825-70b8-4b09-ad7e-99899ceede7d Co-authored-by: fasteiner <75947402+fasteiner@users.noreply.github.com>
Copilotcreated this pull request from a session on behalf of fasteinerApril 15, 2026 21:45 View session
fasteiner
marked this pull request as ready for review
May 13, 2026 15:01
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Pull request overview
This PR closes feature gaps between the Python SDK and Xurrent REST API v1 by adding missing domain resource classes, expanding sub-resource traversal methods on existing resources, and introducing several helper utility endpoints in XurrentApiHelper.
Changes:
- Added 10 new domain classes (e.g.,
Problem,Project,Release,Risk,ServiceInstance,ServiceOffering,SkillPool, etc.) with CRUD and relevant sub-resources. - Added many new sub-resource instance methods across existing resources (
Request,Task,Workflow,Person,Organization,Service,Calendar,Team,Holiday). - Added core helper utilities:
search,bulk_import, and globalarchive/trash/audit_lineslist APIs; expanded unit test coverage accordingly.
Reviewed changes
Copilot reviewed 33 out of 33 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| tests/unit_tests/test_closure_codes.py | Adds unit tests for new ClosureCode domain class. |
| tests/unit_tests/test_contracts.py | Adds unit tests for new Contract domain class and CI sub-resource. |
| tests/unit_tests/test_knowledge_articles.py | Adds unit tests for new KnowledgeArticle class and its sub-resources. |
| tests/unit_tests/test_new_sub_resources.py | Adds unit tests covering many newly added sub-resource methods and core utilities. |
| tests/unit_tests/test_problems.py | Adds unit tests for new Problem class and its sub-resources. |
| tests/unit_tests/test_projects.py | Adds unit tests for new Project class and its sub-resources. |
| tests/unit_tests/test_releases.py | Adds unit tests for new Release class and its sub-resources. |
| tests/unit_tests/test_risks.py | Adds unit tests for new Risk class and its sub-resources. |
| tests/unit_tests/test_service_instances.py | Adds unit tests for new ServiceInstance class and its sub-resources. |
| tests/unit_tests/test_service_offerings.py | Adds unit tests for new ServiceOffering class. |
| tests/unit_tests/test_skill_pools.py | Adds unit tests for new SkillPool class and its sub-resources. |
| src/xurrent/init.py | Exports newly added modules/resources from the package root. |
| src/xurrent/calendars.py | Adds get_duration, get_hours, and get_holidays APIs. |
| src/xurrent/closure_codes.py | Introduces new ClosureCode resource wrapper. |
| src/xurrent/contracts.py | Introduces new Contract resource wrapper + CI listing sub-resource. |
| src/xurrent/core.py | Adds search, bulk_import, and global archive/trash/audit listing APIs. |
| src/xurrent/holidays.py | Adds Holiday.get_calendars() sub-resource traversal. |
| src/xurrent/knowledge_articles.py | Introduces new KnowledgeArticle wrapper + sub-resource traversal methods. |
| src/xurrent/organizations.py | Adds organization sub-resource traversal methods (addresses/contacts/contracts/risks/slas/time_allocations). |
| src/xurrent/people.py | Adds person sub-resource traversal methods (cis/addresses/contacts/permissions/coverages/ooo/skill_pools). |
| src/xurrent/problems.py | Introduces new Problem resource wrapper + notes/requests/workflows sub-resources. |
| src/xurrent/projects.py | Introduces new Project resource wrapper + tasks/phases/workflows/risks/notes sub-resources. |
| src/xurrent/releases.py | Introduces new Release resource wrapper + workflows/notes sub-resources. |
| src/xurrent/requests.py | Adds new request sub-resource traversal methods (attachments/knowledge_articles/automation_rules/satisfaction_feedback/tags/watches). |
| src/xurrent/risks.py | Introduces new Risk wrapper + organizations/projects/services sub-resources. |
| src/xurrent/service_instances.py | Introduces new ServiceInstance wrapper + cis/slas/users sub-resources. |
| src/xurrent/service_offerings.py | Introduces new ServiceOffering wrapper and predefined filters/status enum. |
| src/xurrent/services.py | Adds service sub-resource traversal methods (workflows/templates/risks/instances/slas/offerings). |
| src/xurrent/skill_pools.py | Introduces new SkillPool wrapper + members/effort_classes sub-resources + enable/disable. |
| src/xurrent/tasks.py | Adds task sub-resource traversal methods (notes/approvals/cis/predecessors/successors/service_instances/automation_rules). |
| src/xurrent/teams.py | Adds team → service_instances traversal method. |
| src/xurrent/workflows.py | Adds workflow sub-resource traversal methods (notes/phases/requests/problems/automation_rules). |
| CHANGELOG.md | Documents newly added resources/methods/utilities. |
Comments suppressed due to low confidence (2)
tests/unit_tests/test_new_sub_resources.py:531
- This test overrides
mock_connection.searchwith a lambda instead of exercising the realXurrentApiHelper.search()implementation, so it doesn't validate library behavior and duplicates coverage already provided bytest_search_core. Consider removing it or rewriting it to call the actual method (and assert the expected encoded URL).
def test_search(mock_connection):
mock_connection.api_call.return_value = [{"id": 1, "type": "request"}]
mock_connection.search = lambda query, types=None: mock_connection.api_call(
f"/search?q={query}", "GET"
)
result = mock_connection.search("password reset")
mock_connection.api_call.assert_called_once_with("/search?q=password reset", "GET")
assert result == [{"id": 1, "type": "request"}]
tests/unit_tests/test_new_sub_resources.py:550
- The search tests currently assert unencoded spaces in the URL (e.g.
q=test query). IfXurrentApiHelper.search()is fixed to URL-encode query parameters, update this assertion to match the encoded form (or parse the query string and compare decoded values).
def test_search_core():
helper = XurrentApiHelper(
"https://api.example.com", api_key="key", api_account="acct", resolve_user=False
)
helper.api_call = MagicMock(return_value=[{"id": 1}])
result = helper.search("test query")
helper.api_call.assert_called_once_with("/search?q=test query", "GET")
assert result == [{"id": 1}]
def test_search_with_types_core():
helper = XurrentApiHelper(
"https://api.example.com", api_key="key", api_account="acct", resolve_user=False
)
helper.api_call = MagicMock(return_value=[])
helper.search("test query", types=["request", "person"])
helper.api_call.assert_called_once_with("/search?q=test query&types=request,person", "GET")
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comment on lines
+391
to
+401
| def search(self, query: str, types: list = None) -> list: | ||
| """ | ||
| Perform a cross-resource full-text search. | ||
| :param query: Search query string | ||
| :param types: Optional list of resource types to search (e.g. ['request', 'person']) | ||
| :return: List of search results | ||
| """ | ||
| uri = f'/search?q={query}' | ||
| if types: | ||
| uri += '&types=' + ','.join(types) | ||
| return self.api_call(uri, 'GET') |
Comment on lines
+82
to
+96
| def get_duration(self, start: str, end: str, time_zone: str = None) -> dict: | ||
| """ | ||
| Calculate the duration between two timestamps according to the calendar. | ||
| :param start: Start datetime string (ISO 8601) | ||
| :param end: End datetime string (ISO 8601) | ||
| :param time_zone: Optional time zone name | ||
| :return: Duration data from the API | ||
| """ | ||
| uri = f'{self._connection_object.base_url}/{self.__resourceUrl__}/{self.id}/duration' | ||
| params = f'start={start}&end={end}' | ||
| if time_zone: | ||
| params += f'&time_zone={time_zone}' | ||
| uri += f'?{params}' | ||
| return self._connection_object.api_call(uri, 'GET') |
Comment on lines
+133
to
+151
| def get_workflows(self, queryfilter: dict = None) -> List: | ||
| """Retrieve workflows for this service instance.""" | ||
| from .workflows import Workflow | ||
| uri = f'{self._connection_object.base_url}/{self.__resourceUrl__}/{self.id}/workflows' | ||
| if queryfilter: | ||
| uri += '?' + self._connection_object.create_filter_string(queryfilter) | ||
| response = self._connection_object.api_call(uri, 'GET') | ||
| return [Workflow.from_data(self._connection_object, w) for w in response] | ||
| def get_request_templates(self) -> List: | ||
| """Retrieve request templates for this service instance.""" | ||
| from .request_templates import RequestTemplate | ||
| uri = f'{self._connection_object.base_url}/{self.__resourceUrl__}/{self.id}/request_templates' | ||
| response = self._connection_object.api_call(uri, 'GET') | ||
| return [RequestTemplate.from_data(self._connection_object, rt) for rt in response] | ||
| def get_risks(self) -> List: | ||
| """Retrieve risks for this service instance.""" | ||
| from .risks import Risk |
| import pytest | ||
| import os | ||
| import sys | ||
| from unittest.mock import MagicMock, patch |
Comment on lines
+444
to
+447
| def get_satisfaction_feedback(self) -> List[dict]: | ||
| """Retrieve satisfaction feedback for this request instance.""" | ||
| uri = f'{self._connection_object.base_url}/{self.__resourceUrl__}/{self.id}/satisfaction_feedback' | ||
| return self._connection_object.api_call(uri, 'GET') |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for freeto join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The Python wrapper was missing ~10 core ITSM resource types, dozens of sub-resource traversal methods on existing classes, and several utility APIs available in the Xurrent REST API v1.
New domain classes (10)
Problem— predefined filters, archive/trash/restore, notes/requests/workflows sub-resourcesServiceInstance— cis/slas/users sub-resources; referenced widely by other resourcesRelease— archive/trash/restore, workflows/notes sub-resourcesProject— tasks/phases/workflows/risks/notes sub-resources, archive/trash/restoreContract— CI listing sub-resourceKnowledgeArticle— archive/trash/restore, requests/service_instances/translations sub-resourcesRisk— archive/trash/restore, organizations/projects/services sub-resourcesServiceOffering— catalog/portfolio filtersSkillPool— enable/disable, members/effort_classes sub-resourcesClosureCode— basic CRUDSub-resource methods on existing classes (43 new methods)
Request:get_attachments,get_knowledge_articles,get_automation_rules,get_satisfaction_feedback,get_tags,get_watchesTask:get_notes,add_note,get_approvals,get_cis,get_predecessors,get_successors,get_service_instances,get_automation_rulesWorkflow:get_notes,add_note,get_automation_rules,get_phases,get_requests,get_problemsPerson:get_cis,get_addresses,get_contacts,get_permissions,get_ci_coverages,get_sla_coverages,get_service_coverages,get_out_of_office_periods,get_skill_poolsOrganization:get_addresses,get_contacts,get_contracts,get_risks,get_slas,get_time_allocationsService:get_workflows,get_request_templates,get_risks,get_service_instances,get_slas,get_service_offeringsCalendar:get_duration(start, end, time_zone),get_hours,get_holidaysTeam:get_service_instancesHoliday:get_calendarsUtility APIs on
XurrentApiHelper(5)search(query, types)— cross-resource full-text search viaGET /searchbulk_import(data, import_type, import_format)— CSV/TSV bulk import viaPOST /importlist_archive(queryfilter),list_trash(queryfilter)— global archive/trash listingslist_audit_lines(queryfilter)— global audit log queryOther
__init__.pyupdated to export all 35 modules