Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions ChangeLog.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,24 @@
# Change Log

## v0.0.2.12

### New Features

- configuration_items: add class ConfigurationItem
- configuration_items: add static methods: get_configuration_items, get_by_id
- Request: add class method get_cis_by_request_id to retrieve configuration items associated with a request by its ID.
- Request: add class method add_cis_to_request_by_id to link configuration items to a request by its ID.
- Request: add class method remove_cis_from_request_by_id to unlink configuration items from a request by its ID.
- Request: add instance method get_cis to retrieve configuration items associated with the current request instance.
- Request: add instance method add_cis to link configuration items to the current request instance.
- Request: add instance method remove_cis to unlink configuration items from the current request instance.

### Bug Fixes

- Core: fix issue where 204 status code was not handled correctly
- Core: paging, ensure that '<>' gets removed


## v0.0.2.11

### New Features
Expand Down
99 changes: 98 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ This module is used to interact with the Xurrent API. It provides a set of class

apitoken = "********"

baseUrl = "https://api.4me.qa/v1"
baseUrl = "https://api.xurrent.qa/v1"
account = "account-name"

x_api_helper = XurrentApiHelper(baseUrl, apitoken, account)
Expand All @@ -37,6 +37,54 @@ This module is used to interact with the Xurrent API. It provides a set of class

```

#### Configuration Items

```python
# Example usage of ConfigurationItem class
from xurrent.configuration_items import ConfigurationItem

# Get a Configuration Item by ID
ci = ConfigurationItem.get_by_id(x_api_helper, <id>)
print(ci)

# List all Configuration Items
all_cis = ConfigurationItem.get_configuration_items(x_api_helper)
print(all_cis)

# List active Configuration Items
active_cis = ConfigurationItem.get_configuration_items(x_api_helper, predefinedFilter="active")
print(active_cis)

# Update a Configuration Item
updated_ci = ci.update({"name": "Updated Name", "status": "being_repaired"})
print(updated_ci)

# Create a new Configuration Item
# creating without specifying the label, takes the last ci of the product and increments the label
# example: "wdc-02" -> "wdc-03"
data = {"name": "New CI", "type": "software", "status": "in_production", "product_id": "<product_id>"}
new_ci = ConfigurationItem.create(api_helper, data)
print(new_ci)

# Archive a Configuration Item (must be in an allowed state)
try:
archived_ci = ci.archive()
print(archived_ci)
except ValueError as e:
print(f"Error: {e}")

# Trash a Configuration Item (must be in an allowed state)
try:
trashed_ci = ci.trash()
print(trashed_ci)
except ValueError as e:
print(f"Error: {e}")

# Restore a Configuration Item
restored_ci = ci.restore()
print(restored_ci)
```

#### People

```python
Expand Down Expand Up @@ -93,6 +141,55 @@ This module is used to interact with the Xurrent API. It provides a set of class

```

##### Request Configuration Items

```python
from src.xurrent.requests import Request

# Get Configuration Items for a Request
request_id = <request_id>


# Add a Configuration Item to a Request
ci_id = <ci_id>
try:
response = Request.add_ci_to_request_by_id(x_api_helper, request_id, ci_id)
print("CI added:", response)
except ValueError as e:
print(f"Error: {e}")

cis = Request.get_cis_by_request_id(x_api_helper, request_id)
print(cis)

# Remove a Configuration Item from a Request
try:
response = Request.remove_ci_from_request_by_id(x_api_helper, request_id, ci_id)
print("CI removed:", response)
except ValueError as e:
print(f"Error: {e}")

# Instance-based example
req = Request.get_by_id(x_api_helper, request_id)

# Add a CI to this request
try:
response = req.add_ci(ci_id)
print("CI added:", response)
except ValueError as e:
print(f"Error: {e}")

# Get CIs for this request
cis_instance = req.get_cis()
print(cis_instance)

# Remove a CI from this request
try:
response = req.remove_ci(ci_id)
print("CI removed:", response)
except ValueError as e:
print(f"Error: {e}")
```

##### Request Notes

```python
Expand Down
2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[project]
name = "xurrent"
version = "0.0.2.11"
version = "0.0.2.12"
authors = [
{ name="Fabian Steiner", email="fabian@stei-ner.net" },
]
Expand Down
134 changes: 134 additions & 0 deletions src/xurrent/configuration_items.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
from __future__ import annotations # Needed for forward references
from .core import XurrentApiHelper, JsonSerializableDict
from typing import Optional, List, Dict, Type, TypeVar
from enum import Enum

T = TypeVar('T', bound='ConfigurationItem')

class ConfigurationItemPredefinedFilter(str, Enum):
active = "active" # List all active configuration items
inactive = "inactive" # List all inactive configuration items
supported_by_my_teams = "supported_by_my_teams" # List all configuration items supported by the teams of the API user

class ConfigurationItem(JsonSerializableDict):
# https://developer.xurrent.com/v1/configuration_items/
__resourceUrl__ = 'cis'

def __init__(self,
connection_object: XurrentApiHelper,
id: int,
label: Optional[str] = None,
name: Optional[str] = None,
type: Optional[str] = None,
status: Optional[str] = None,
attributes: Optional[Dict] = None,
**kwargs):
self.id = id
self._connection_object = connection_object
self.label = label
self.name = name
self.status = status
self.attributes = attributes or {}

for key, value in kwargs.items():
setattr(self, key, value)

def __str__(self) -> str:
"""Provide a human-readable string representation of the object."""
return f"ConfigurationItem(id={self.id}, label={self.label},name={self.name}, status={self.status})"

def ref_str(self) -> str:
"""Provide a human-readable string representation of the object."""
return f"ConfigurationItem(id={self.id}, label={self.label})"

@classmethod
def from_data(cls, connection_object: XurrentApiHelper, data) -> T:
if not isinstance(data, dict):
raise TypeError(f"Expected 'data' to be a dictionary, got {type(data).__name__}")
if 'id' not in data:
raise ValueError("Data dictionary must contain an 'id' field.")
return cls(connection_object, **data)

@classmethod
def get_by_id(cls, connection_object: XurrentApiHelper, id: int) -> T:
"""
Retrieve a configuration item by its ID.
"""
uri = f'{connection_object.base_url}/{cls.__resourceUrl__}/{id}'
return cls.from_data(connection_object, connection_object.api_call(uri, 'GET'))

@classmethod
def get_configuration_items(cls, connection_object: XurrentApiHelper, predefinedFilter: ConfigurationItemPredefinedFilter = None, queryfilter: dict = None) -> List[T]:
"""
Retrieve all configuration items.
"""
uri = f'{connection_object.base_url}/{cls.__resourceUrl__}'
if predefinedFilter:
uri = f'{uri}/{predefinedFilter}'
if queryfilter:
uri += '?' + connection_object.create_filter_string(queryfilter)
response = connection_object.api_call(uri, 'GET')
return [cls.from_data(connection_object, ci) for ci in response]

def update(self, data: dict) -> T:
"""
Update the current configuration item instance with new data.
"""
uri = f'{self._connection_object.base_url}/{self.__resourceUrl__}/{self.id}'
response = self._connection_object.api_call(uri, 'PATCH', data)
return ConfigurationItem.from_data(self._connection_object, response)

@classmethod
def create(cls, connection_object: XurrentApiHelper, data: dict) -> T:
"""
Create a new configuration item.
"""
uri = f'{connection_object.base_url}/{cls.__resourceUrl__}'
response = connection_object.api_call(uri, 'POST', data)
return cls.from_data(connection_object, response)

def archive(self) -> T:
"""
Archive the configuration item.

Allowed statuses for archiving:
- undergoing_maintenance
- broken_down
- being_repaired
- archived
- to_be_removed
- lost_or_stolen
- removed
"""
if self.status not in {"undergoing_maintenance", "broken_down", "being_repaired", "archived", "to_be_removed", "lost_or_stolen", "removed"}:
raise ValueError("Configuration item must be in one of the following statuses to be archived: undergoing_maintenance, broken_down, being_repaired, archived, to_be_removed, lost_or_stolen, removed.")
uri = f'{self._connection_object.base_url}/{self.__resourceUrl__}/{self.id}/archive'
response = self._connection_object.api_call(uri, 'POST')
return ConfigurationItem.from_data(self._connection_object, response)

def restore(self) -> T:
"""
Restore the configuration item.
"""
uri = f'{self._connection_object.base_url}/{self.__resourceUrl__}/{self.id}/restore'
response = self._connection_object.api_call(uri, 'POST')
return ConfigurationItem.from_data(self._connection_object, response)

def trash(self) -> T:
"""
Trash the configuration item.

Allowed statuses for trashing:
- undergoing_maintenance
- broken_down
- being_repaired
- archived
- to_be_removed
- lost_or_stolen
- removed
"""
if self.status not in {"undergoing_maintenance", "broken_down", "being_repaired", "archived", "to_be_removed", "lost_or_stolen", "removed"}:
raise ValueError("Configuration item must be in one of the following statuses to be trashed: undergoing_maintenance, broken_down, being_repaired, archived, to_be_removed, lost_or_stolen, removed.")
uri = f'{self._connection_object.base_url}/{self.__resourceUrl__}/{self.id}/trash'
response = self._connection_object.api_call(uri, 'POST')
return ConfigurationItem.from_data(self._connection_object, response)
5 changes: 5 additions & 0 deletions src/xurrent/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -164,6 +164,9 @@ def api_call(self, uri: str, method='GET', data=None, per_page=100):
# Make the HTTP request
response = requests.request(method, next_page_url, headers=headers, json=data)

if response.status_code == 204:
return None

# Handle rate limiting (429 status code)
if response.status_code == 429:
retry_after = int(response.headers.get('Retry-After', 1)) # Default to 1 second if not provided
Expand All @@ -189,6 +192,8 @@ def api_call(self, uri: str, method='GET', data=None, per_page=100):
links = {rel.strip(): url.strip('<>') for url, rel in
(link.split(';') for link in link_header.split(','))}
next_page_url = links.get('rel="next"')
if next_page_url:
next_page_url = next_page_url.replace('<', '').replace('>', '')
else:
next_page_url = None
else:
Expand Down
2 changes: 1 addition & 1 deletion src/xurrent/people.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ class PeoplePredefinedFilter(str, Enum):
T = TypeVar('T', bound='Person')

class Person(JsonSerializableDict):
#https://developer.4me.com/v1/people/
#https://developer.xurrent.com/v1/people/
__resourceUrl__ = 'people'

def __init__(self, connection_object: XurrentApiHelper, id, name: str = None, primary_email: str = None,**kwargs):
Expand Down
Loading