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
14 changes: 7 additions & 7 deletions .github/workflows/python-package.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,18 +26,18 @@ jobs:
python-version: ${{ matrix.python-version }}
- name: Set environment variables
env:
DEMO_API_TOKEN: ${{ secrets.DEMO_API_TOKEN }}
DEMO_APIACCOUNT: ${{ vars.DEMO_APIACCOUNT }}
DEMO_APIURL: ${{ vars.DEMO_APIURL }}
API_TOKEN: ${{ secrets.DEMO_API_TOKEN }}
APIACCOUNT: ${{ vars.DEMO_APIACCOUNT }}
APIURL: ${{ vars.DEMO_APIURL }}
run: |
echo "Environment variables set:"
echo "DEMO_API_TOKEN=${{ secrets.DEMO_API_TOKEN }}" # Avoid printing sensitive secrets in real workflows
echo "DEMO_APIACCOUNT=${{ vars.DEMO_APIACCOUNT }}"
echo "DEMO_APIURL=${{ vars.DEMO_APIURL }}"
echo "APITOKEN=*******"
echo "APIACCOUNT=${{ vars.DEMO_APIACCOUNT }}"
echo "APIURL=${{ vars.DEMO_APIURL }}"
- name: Install dependencies
run: |
python -m pip install --upgrade pip
python -m pip install flake8 pytest python-dotenv
python -m pip install flake8 pytest python-dotenv mock
if [ -f requirements.txt ]; then pip install -r requirements.txt; fi
- name: Test with pytest
env:
Expand Down
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -161,3 +161,6 @@ cython_debug/
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/

# poetry
poetry.lock
8 changes: 8 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
repos:
- repo: local
hooks:
- id: run-tests
name: Run unit tests
entry: pytest ./tests/unit_tests
language: system
types: [python]
6 changes: 6 additions & 0 deletions .vscode/extensions.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"recommendations": [
"elagil.pre-commit-helper",
"ms-python.python"
]
}
1 change: 1 addition & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
{
"editor.wordWrap": "on",
"python.testing.pytestArgs": [
"tests"
],
Expand Down
25 changes: 25 additions & 0 deletions ChangeLog.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,30 @@
# Change Log

## v0.0.2.9

### New Features

- Request, Workflow, Task, Person, Team: add non static methods: ref_str() --> return a reference string
- Request: add RequestCategory enum
- core: JSONSerializableDict: handle datetime and list of objects
- Workflow: add WorkflowCategory enum
- Workflow: use WorkflowCategory and WorkflowStatus enums on instantiation
- Team: add Team class
- Team: add enum TeamPredefinedFilter
- People: add non static methods: get_teams
- Tests: add tests for Request
- Tests: add pre-commit hooks yaml file

### Bugfixes

- Person, Workflow, Task: inherit JsonSerializableDict --> make serializable
- Request: close: make it possible to close a without a note (using default note)

### Breaking Changes

- Request: request.created_by, request.requested_by, request.requested_for, request.member are now Person objects
- Workflow: workflow.manager is now a Person object

## v0.0.2.8

### Bug Fixes
Expand Down
16 changes: 16 additions & 0 deletions Contributing.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# Contributing

## Setup

1. Clone the repository
2. pip install poetry
3. poetry install --with dev
4. poetry shell
5. pre-commit install

## Activate the virtual environment

```bash
poetry shell
```

4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,10 @@ This module is used to interact with the Xurrent API. It provides a set of class

[ChangeLog.md](https://github.com/fasteiner/xurrent-python/blob/main/ChangeLog.md)

## Contributing

[Contributing.md](Contributing.md)

## Usage

### Basic Usage
Expand Down
27 changes: 24 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
[project]
name = "xurrent"
version = "0.0.2.8"
version = "0.0.2.9"
authors = [
{ name="Fabian Steiner", email="fabian@stei-ner.net" },
]
description = "A python module to interact with the Xurrent API."
readme = "README.md"
requires-python = ">=3.8"
requires-python = ">=3.9"
classifiers = [
"Programming Language :: Python :: 3",
"License :: OSI Approved :: MIT License",
Expand All @@ -15,4 +15,25 @@ classifiers = [

[project.urls]
Homepage = "https://github.com/fasteiner/xurrent-python"
Issues = "https://github.com/fasteiner/xurrent-python/issues"
Issues = "https://github.com/fasteiner/xurrent-python/issues"
[tool.poetry]
name = "xurrent"
version = "0.0.2.9"
description = "A python module to interact with the Xurrent API."
authors = ["Ing. Fabian Franz Steiner BSc. <fabian.steiner@tttech.com>"]
readme = "README.md"

[tool.poetry.dependencies]
python = ">=3.9"
requests = "^2.32.3"


[tool.poetry.group.dev.dependencies]
pytest = "^8.3.4"
python-dotenv = "^1.0.1"
mock = "^5.1.0"
pre-commit = "^4.0.1"

[build-system]
requires = ["poetry-core"]
build-backend = "poetry.core.masonry.api"
9 changes: 9 additions & 0 deletions src/xurrent/core.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
from __future__ import annotations # Needed for forward references
from datetime import datetime
import time
import requests
import logging
import json
Expand All @@ -17,6 +19,11 @@ def to_dict(self) -> dict:
# Recursively call to_dict on nested JsonSerializableDict objects
if isinstance(value, JsonSerializableDict):
result[key] = value.to_dict()
elif isinstance(value, list):
#call to_dict on each item in the list
result[key] = [item.to_dict() for item in value]
elif isinstance(value, datetime):
result[key] = value.isoformat()
else:
result[key] = value
return result
Expand All @@ -28,6 +35,7 @@ def to_json(self):

class XurrentApiHelper:
api_user: Person # Forward declaration with a string
api_user_teams: List[Team] # Forward declaration with a string

def __init__(self, base_url, api_key, api_account, resolve_user=True):
self.base_url = base_url
Expand All @@ -38,6 +46,7 @@ def __init__(self, base_url, api_key, api_account, resolve_user=True):
# Import Person lazily
from .people import Person
self.api_user = Person.get_me(self)
self.api_user_teams = self.api_user.get_teams()

def __append_per_page(self, uri, per_page=100):
"""
Expand Down
47 changes: 28 additions & 19 deletions src/xurrent/people.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from .core import XurrentApiHelper
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
Expand All @@ -14,9 +15,9 @@ class PeoplePredefinedFilter(str, Enum):

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

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

def __init__(self, connection_object: XurrentApiHelper, id, name: str = None, primary_email: str = None,**kwargs):
self._connection_object = connection_object
Expand All @@ -26,18 +27,18 @@ def __init__(self, connection_object: XurrentApiHelper, id, name: str = None, pr
for key, value in kwargs.items():
setattr(self, key, value)

def __update_object__(self, data) -> None:
if data.get('id') != self.id:
raise ValueError(f"ID mismatch: {self.id} != {data.get('id')}")
for key, value in data.items():
setattr(self, key, value)

def __str__(self) -> str:
"""
Return a string representation of the object.
"""
return f"Person(id={self.id}, name={self.name}, primary_email={self.primary_email})"

def ref_str(self) -> str:
"""
Return a string representation of the object.
"""
return f"Person(id={self.id}, name={self.name})"

@classmethod
def from_data(cls, connection_object: XurrentApiHelper, data) -> T:
if not isinstance(data, dict):
Expand All @@ -48,32 +49,40 @@ def from_data(cls, connection_object: XurrentApiHelper, data) -> T:

@classmethod
def get_by_id(cls, connection_object: XurrentApiHelper, id):
uri = f'{connection_object.base_url}/{cls.resourceUrl}/{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_me(cls, connection_object: XurrentApiHelper):
"""
Retrieve the person object for the authenticated user.
"""
uri = f'{connection_object.base_url}/{cls.resourceUrl}/me'
uri = f'{connection_object.base_url}/{cls.__resourceUrl__}/me'
return cls.from_data(connection_object, connection_object.api_call(uri, 'GET'))

@classmethod
def get_people(cls, connection_object: XurrentApiHelper, predefinedFilter: PeoplePredefinedFilter = None, queryfilter: dict = None) -> List[T]:
uri = f'{connection_object.base_url}/{cls.resourceUrl}'
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, person) for person in response]

def get_teams(self) -> List[Team]:
"""
Retrieve the teams of the person.
"""
from .teams import Team
uri = f'{self._connection_object.base_url}/{self.__resourceUrl__}/{self.id}/teams'
response = self._connection_object.api_call(uri, 'GET')
return [Team.from_data(self._connection_object, team) for team in response]

def update(self, data):
uri = f'{self._connection_object.base_url}/{self.resourceUrl}/{self.id}'
uri = f'{self._connection_object.base_url}/{self.__resourceUrl__}/{self.id}'
response = self._connection_object.api_call(uri, 'PATCH', data)
self.__update_object__(response)
return self
return People.from_data(self._connection_object,response)

def disable(self, prefix: str = '', postfix: str = ''):
"""
Expand Down Expand Up @@ -104,28 +113,28 @@ def create(cls, connection_object: XurrentApiHelper, data: dict):
:param connection_object: Xurrent Connection object
:param data: Data dictionary (containing the data for the new person)
"""
uri = f'{connection_object.base_url}/{cls.resourceUrl}'
uri = f'{connection_object.base_url}/{cls.__resourceUrl__}'
return cls.from_data(connection_object, connection_object.api_call(uri, 'POST', data))

def archive(self):
"""
Archive the person.
"""
uri = f'{self._connection_object.base_url}/{self.resourceUrl}/{self.id}/archive'
uri = f'{self._connection_object.base_url}/{self.__resourceUrl__}/{self.id}/archive'
return self._connection_object.api_call(uri, 'POST')

def trash(self):
"""
Trash the person.
"""
uri = f'{self._connection_object.base_url}/{self.resourceUrl}/{self.id}/trash'
uri = f'{self._connection_object.base_url}/{self.__resourceUrl__}/{self.id}/trash'
return self._connection_object.api_call(uri, 'POST')

def restore(self):
"""
Restore the person.
"""
uri = f'{self._connection_object.base_url}/{self.resourceUrl}/{self.id}/restore'
uri = f'{self._connection_object.base_url}/{self.__resourceUrl__}/{self.id}/restore'
return self._connection_object.api_call(uri, 'POST')


Loading
Loading