diff --git a/.github/workflows/action.yaml b/.github/workflows/action.yaml
index 5a34701..a3c52d5 100644
--- a/.github/workflows/action.yaml
+++ b/.github/workflows/action.yaml
@@ -78,7 +78,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
- python-version: [ 3.9, "3.10", "3.11", "3.12", "3.13" ]
+ python-version: [ 3.9, "3.10", "3.11", "3.12", "3.13", "3.14" ]
steps:
- uses: actions/checkout@v4
@@ -104,7 +104,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
- python-version: [ 3.9, "3.10", "3.11", "3.12", "3.13" ]
+ python-version: [ 3.9, "3.10", "3.11", "3.12", "3.13", "3.14" ]
steps:
- uses: actions/checkout@v4
diff --git a/pyproject.toml b/pyproject.toml
index 0ff9cba..a73802a 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -30,6 +30,7 @@ classifiers = [
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
+ "Programming Language :: Python :: 3.14",
"Programming Language :: Python :: 3 :: Only",
"Intended Audience :: Developers",
"License :: OSI Approved :: GNU General Public License v3 (GPLv3)",
diff --git a/src/pytito/__about__.py b/src/pytito/__about__.py
index 0e3f4b8..666444b 100644
--- a/src/pytito/__about__.py
+++ b/src/pytito/__about__.py
@@ -17,4 +17,4 @@
Variables that describes the Package
"""
-__version__ = "0.0.6"
+__version__ = "0.0.8"
diff --git a/src/pytito/admin/__init__.py b/src/pytito/admin/__init__.py
index 35a6901..16d38e4 100644
--- a/src/pytito/admin/__init__.py
+++ b/src/pytito/admin/__init__.py
@@ -19,5 +19,7 @@
from .event import Event
from .ticket import Ticket
from .account import Account
+from .release import Release
+from .activity import Activity
from ._base_client import UnauthorizedException
diff --git a/src/pytito/admin/_base_client.py b/src/pytito/admin/_base_client.py
index 0dc6c76..4fd0ba3 100644
--- a/src/pytito/admin/_base_client.py
+++ b/src/pytito/admin/_base_client.py
@@ -20,6 +20,7 @@
import os
from abc import ABC
from typing import Any, Optional
+from datetime import datetime
import requests
@@ -98,3 +99,43 @@ def _get_response(self, endpoint: str) -> dict[str, Any]:
raise RuntimeError(f'Hello failed with status code: {response.status_code}')
return response.json()
+
+class EventChildAPIBase(AdminAPIBase, ABC):
+ """
+ Base Class for the children of an event e.g. Tickets, Releases, Actvities
+ """
+ # pylint: disable=too-few-public-methods
+
+ def __init__(self, *, account_slug:str, event_slug:str,
+ json_content:Optional[dict[str, Any]]=None,
+ allow_automatic_json_retrieval: bool=False) -> None:
+ if json_content is None and allow_automatic_json_retrieval is False:
+ raise RuntimeError('If the JSON content is not provided at initialisation, '
+ 'runtime retrival is needed')
+ super().__init__(json_content=json_content,
+ allow_automatic_json_retrieval=allow_automatic_json_retrieval)
+ self.__account_slug = account_slug
+ self.__event_slug = event_slug
+
+ @property
+ def _account_slug(self) -> str:
+ return self.__account_slug
+
+ @property
+ def _event_slug(self) -> str:
+ return self.__event_slug
+
+def datetime_from_json(json_value: str) -> datetime:
+ """
+ convert the isoformat datetime from the json content to a python object
+ """
+ return datetime.fromisoformat(json_value)
+
+def optional_datetime_from_json(json_value: str) -> Optional[datetime]:
+ """
+ convert the isoformat datetime from the json content to a python object, with support for
+ a null (unpopulated value)
+ """
+ if json_value is None:
+ return None
+ return datetime.fromisoformat(json_value)
diff --git a/src/pytito/admin/account.py b/src/pytito/admin/account.py
index 9fd0af0..0571cb2 100644
--- a/src/pytito/admin/account.py
+++ b/src/pytito/admin/account.py
@@ -52,16 +52,15 @@ def _populate_json(self) -> None:
raise ValueError('slug in json content does not match expected value')
def __event_getter(self, end_point: str) -> dict[str, Event]:
- response = self._get_response(end_point)
- return_dict:dict[str, Event] = {}
- for event in response['events']:
- if event['account_slug'] != self._account_slug:
- raise RuntimeError('Account Slug inconsistency')
- slug = event['slug']
- return_dict[slug] = Event(event_slug=slug, account_slug=self._account_slug,
+
+ def event_factory(json_content:dict[str, Any]) -> tuple[str, Event]:
+ event_slug = json_content['slug']
+ return event_slug, Event(event_slug=event_slug, account_slug=self._account_slug,
api_key=self.__api_key_internal,
- json_content=event)
- return return_dict
+ json_content=json_content)
+
+ response = self._get_response(end_point)
+ return dict(event_factory(event) for event in response['events'])
@property
def events(self) -> dict[str, Event]:
diff --git a/src/pytito/admin/activity.py b/src/pytito/admin/activity.py
new file mode 100644
index 0000000..e111280
--- /dev/null
+++ b/src/pytito/admin/activity.py
@@ -0,0 +1,86 @@
+"""
+pytito is a python wrapper for the tito.io API
+Copyright (C) 2024
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see .
+
+This file provides the activity class
+"""
+from typing import Optional, Any
+from datetime import datetime
+
+from ._base_client import EventChildAPIBase, optional_datetime_from_json
+
+class Activity(EventChildAPIBase):
+ """
+ One of the activities for an event available through the Tito IO AdminAPI
+ """
+
+ def __init__(self, *, account_slug:str, event_slug:str, activity_id:str,
+ json_content:Optional[dict[str, Any]]=None,
+ allow_automatic_json_retrieval: bool=False) -> None:
+ super().__init__(json_content=json_content,
+ account_slug=account_slug,
+ event_slug=event_slug,
+ allow_automatic_json_retrieval=allow_automatic_json_retrieval)
+ self.__activity_id = activity_id
+ if json_content is not None:
+ if self._json_content['_type'] != "activity":
+ raise ValueError('JSON content type was expected to be activity')
+
+ @property
+ def _activity_id(self) -> str:
+ return self.__activity_id
+
+ @property
+ def _end_point(self) -> str:
+ return super()._end_point +\
+ f'/{self._account_slug}/{self._event_slug}/activities/{self._activity_id}'
+
+ def _populate_json(self) -> None:
+ self._json_content = self._get_response(endpoint='')['id']
+ if self._activity_id != self._json_content['id']:
+ raise ValueError('slug in json content does not match expected value')
+ if self._json_content['view'] != 'extended':
+ raise ValueError('expected the extended view of the ticket')
+
+ @property
+ def name(self) -> str:
+ """
+ Name of the Activity
+ """
+ return self._json_content['name']
+
+ @property
+ def capacity(self) -> Optional[int]:
+ """
+ The number of people who can attend. A value of `None` means there is no limit
+ """
+ return self._json_content['capacity']
+
+ @property
+ def start_at(self) -> Optional[datetime]:
+ """
+ Start date and time for the activity
+ """
+ json_value = self._json_content['start_at']
+ return optional_datetime_from_json(json_value=json_value)
+
+ @property
+ def end_at(self) -> Optional[datetime]:
+ """
+ End date and time for the activity
+ """
+ json_value = self._json_content['end_at']
+ return optional_datetime_from_json(json_value=json_value)
diff --git a/src/pytito/admin/event.py b/src/pytito/admin/event.py
index 743493e..ad91c5c 100644
--- a/src/pytito/admin/event.py
+++ b/src/pytito/admin/event.py
@@ -21,8 +21,10 @@
from datetime import datetime
-from ._base_client import AdminAPIBase
+from ._base_client import AdminAPIBase, datetime_from_json
from .ticket import Ticket
+from .release import Release
+from .activity import Activity
class Event(AdminAPIBase):
@@ -36,6 +38,9 @@ def __init__(self, account_slug:str, event_slug:str,
super().__init__(json_content=json_content, api_key=api_key)
self.__account_slug = account_slug
self.__event_slug = event_slug
+ if json_content is not None:
+ if self._json_content['_type'] != "event":
+ raise ValueError('JSON content type was expected to be ticket')
@property
def _account_slug(self) -> str:
@@ -45,7 +50,6 @@ def _account_slug(self) -> str:
def _event_slug(self) -> str:
return self.__event_slug
-
@property
def _end_point(self) -> str:
return super()._end_point + f'/{self._account_slug}/{self._event_slug}'
@@ -79,4 +83,49 @@ def start_at(self) -> datetime:
"""
Start date and time for the event
"""
- return datetime.fromisoformat(self._json_content['start_at'])
+ json_content = self._json_content['start_at']
+ return datetime_from_json(json_value=json_content)
+
+ @property
+ def end_at(self) -> datetime:
+ """
+ End date and time for the event
+ """
+ json_content = self._json_content['end_at']
+ return datetime_from_json(json_value=json_content)
+
+ def __release_getter(self) -> dict[str, Release]:
+
+ def release_factory(json_content:dict[str, Any]) -> tuple[str, Release]:
+ release_slug = json_content['slug']
+ return release_slug, Release(event_slug=self.__event_slug,
+ account_slug=self._account_slug,
+ release_slug=release_slug,
+ json_content=json_content)
+
+ response = self._get_response('releases')
+ return dict(release_factory(release) for release in response['releases'])
+
+ @property
+ def releases(self) -> dict[str, Release]:
+ """
+ retrieve all the releases for the event
+ """
+ return self.__release_getter()
+
+ def __activity_getter(self) -> list[Activity]:
+
+ def activity_factory(json_content:dict[str, Any]) -> Activity:
+ activity_id = json_content['id']
+ return Activity(event_slug=self.__event_slug, account_slug=self._account_slug,
+ activity_id=activity_id, json_content=json_content)
+
+ response = self._get_response('activities')
+ return [activity_factory(activity) for activity in response['activities']]
+
+ @property
+ def activities(self) -> list[Activity]:
+ """
+ retrieve all the activities for the event
+ """
+ return self.__activity_getter()
diff --git a/src/pytito/admin/release.py b/src/pytito/admin/release.py
new file mode 100644
index 0000000..97332a0
--- /dev/null
+++ b/src/pytito/admin/release.py
@@ -0,0 +1,95 @@
+"""
+pytito is a python wrapper for the tito.io API
+Copyright (C) 2024
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see .
+
+This file provides the release class
+"""
+from typing import Optional, Any
+from datetime import datetime
+
+from ._base_client import EventChildAPIBase, optional_datetime_from_json
+
+class Release(EventChildAPIBase):
+ """
+ One of the release for an event available through the Tito IO AdminAPI
+ """
+
+ def __init__(self, *, account_slug:str, event_slug:str, release_slug:str,
+ json_content:Optional[dict[str, Any]]=None,
+ allow_automatic_json_retrieval: bool=False) -> None:
+ super().__init__(json_content=json_content,
+ account_slug=account_slug,
+ event_slug=event_slug,
+ allow_automatic_json_retrieval=allow_automatic_json_retrieval)
+ self.__release_slug = release_slug
+ if json_content is not None:
+ if self._json_content['_type'] != "release":
+ raise ValueError('JSON content type was expected to be release')
+
+ @property
+ def _release_slug(self) -> str:
+ return self.__release_slug
+
+ @property
+ def _end_point(self) -> str:
+ return super()._end_point +\
+ f'/{self._account_slug}/{self._event_slug}/releases/{self._release_slug}'
+
+ def _populate_json(self) -> None:
+ self._json_content = self._get_response(endpoint='')['release']
+ if self._release_slug != self._json_content['slug']:
+ raise ValueError('slug in json content does not match expected value')
+ if self._json_content['view'] != 'extended':
+ raise ValueError('expected the extended view of the ticket')
+ if self._json_content['_type'] != "release":
+ raise ValueError('JSON content type was expected to be release')
+
+ @property
+ def title(self) -> str:
+ """
+ Title of the release
+ """
+ return self._json_content['title']
+
+ @property
+ def secret(self) -> bool:
+ """
+ Title of the release
+ """
+ return self._json_content['secret']
+
+ @property
+ def start_at(self) -> Optional[datetime]:
+ """
+ Start date and time for the release being available (i.e. when is it on sale)
+ """
+ json_value = self._json_content['start_at']
+ return optional_datetime_from_json(json_value=json_value)
+
+ @property
+ def end_at(self) -> Optional[datetime]:
+ """
+ End date and time for the release being available (i.e. when is it on sale)
+ """
+ json_value = self._json_content['end_at']
+ return optional_datetime_from_json(json_value=json_value)
+
+ @property
+ def quantity(self) -> Optional[int]:
+ """
+ The number of tickets who can attend. A value of `None` means there is no limit
+ """
+ return self._json_content['quantity']
diff --git a/src/pytito/admin/ticket.py b/src/pytito/admin/ticket.py
index fa323f9..90263ee 100644
--- a/src/pytito/admin/ticket.py
+++ b/src/pytito/admin/ticket.py
@@ -20,7 +20,7 @@
from typing import Optional, Any
import sys
-from ._base_client import AdminAPIBase
+from ._base_client import EventChildAPIBase
if sys.version_info < (3,11):
from strenum import StrEnum
@@ -39,7 +39,7 @@ class TicketState(StrEnum):
VOID = 'void'
-class Ticket(AdminAPIBase):
+class Ticket(EventChildAPIBase):
"""
One of the tickets for an event available through the Tito IO AdminAPI
"""
@@ -47,22 +47,14 @@ class Ticket(AdminAPIBase):
def __init__(self, *, account_slug:str, event_slug:str, ticket_slug:str,
json_content:Optional[dict[str, Any]]=None,
allow_automatic_json_retrieval: bool=False) -> None:
- if json_content is None and allow_automatic_json_retrieval is False:
- raise RuntimeError('If the JSON content is not provided at initialisation, '
- 'runtime retrival is needed')
super().__init__(json_content=json_content,
+ account_slug=account_slug,
+ event_slug=event_slug,
allow_automatic_json_retrieval=allow_automatic_json_retrieval)
- self.__account_slug = account_slug
- self.__event_slug = event_slug
self.__ticket_slug = ticket_slug
-
- @property
- def _account_slug(self) -> str:
- return self.__account_slug
-
- @property
- def _event_slug(self) -> str:
- return self.__event_slug
+ if json_content is not None:
+ if self._json_content['_type'] != "ticket":
+ raise ValueError('JSON content type was expected to be ticket')
@property
def _ticket_slug(self) -> str:
@@ -79,11 +71,13 @@ def _populate_json(self) -> None:
raise ValueError('slug in json content does not match expected value')
if self._json_content['view'] != 'extended':
raise ValueError('expected the extended view of the ticket')
+ if self._json_content['_type'] != "ticket":
+ raise ValueError('JSON content type was expected to be ticket')
@property
def state(self) -> TicketState:
"""
- Event title
+ Ticket State
"""
return TicketState(self._json_content['state'])
diff --git a/tests/integration_tests/test_connection.py b/tests/integration_tests/test_connection.py
index 236d70a..0aa6775 100644
--- a/tests/integration_tests/test_connection.py
+++ b/tests/integration_tests/test_connection.py
@@ -34,7 +34,7 @@ def test_bad_api_key():
def test_pytito_connection(pytito_account):
"""
- test the the connection to the pytito account (used by many of the other tests) works
+ test the connection to the pytito account (used by many of the other tests) works
correctly
"""
assert isinstance(pytito_account, Account)
diff --git a/tests/unit_tests/conftest.py b/tests/unit_tests/conftest.py
index a353a69..48fbcbd 100644
--- a/tests/unit_tests/conftest.py
+++ b/tests/unit_tests/conftest.py
@@ -120,7 +120,8 @@ def hello_json_content(request, context):
json={'account': {'name': account.name, 'slug': account.slug}})
requests_mock.get(f"https://api.tito.io/v3/{account.slug}/events", status_code=200,
json={'events': [
- {'title': event.title,
+ {'_type':'event',
+ 'title': event.title,
'slug': event.slug,
'start_at': event.start_at.isoformat(timespec='milliseconds'),
'account_slug': account.slug}