Skip to content

Commit 8362ad0

Browse files
authored
Merge pull request #17 from krcb197/12-add-ability-to-update-and-event-start-and-end
Add the ability to duplicate and delete events
2 parents bb3f890 + 3b9a56f commit 8362ad0

6 files changed

Lines changed: 136 additions & 15 deletions

File tree

src/pytito/__about__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,4 @@
1717
1818
Variables that describes the Package
1919
"""
20-
__version__ = "0.0.10"
20+
__version__ = "0.0.11"

src/pytito/admin/_base_client.py

Lines changed: 42 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ def _get_response(self, endpoint: str) -> dict[str, Any]:
110110

111111
return response.json()
112112

113-
def _patch_reponse(self, value: dict[str, Any]) -> None:
113+
def _patch_response(self, value: dict[str, Any]) -> None:
114114

115115
response = requests.patch(
116116
url=self._end_point,
@@ -130,6 +130,47 @@ def _patch_reponse(self, value: dict[str, Any]) -> None:
130130
if not response.status_code == 200:
131131
raise RuntimeError(f'patch failed with status code: {response.status_code}')
132132

133+
def _post_response(self, endpoint: str, value: dict[str, Any]) -> None:
134+
135+
if endpoint == '':
136+
full_end_point = self._end_point
137+
else:
138+
full_end_point = self._end_point + '/' + endpoint
139+
140+
response = requests.post(
141+
url=full_end_point,
142+
headers={"Accept" : "application/json",
143+
"Authorization" : f"Token token={self.__api_key()}"},
144+
json=value,
145+
timeout=10.0
146+
)
147+
148+
if response.status_code == 401:
149+
raise UnauthorizedException(response.json()['message'])
150+
151+
if response.status_code == 403:
152+
detail = json.loads(response.text)
153+
raise ForbiddenException(detail['errors']['detail'])
154+
155+
if response.status_code not in [200, 201]:
156+
raise RuntimeError(f'post failed with status code: {response.status_code}')
157+
158+
def _delete_response(self) -> None:
159+
160+
response = requests.patch(
161+
url=self._end_point,
162+
headers={"Accept" : "application/json",
163+
"Authorization" : f"Token token={self.__api_key()}"},
164+
timeout=10.0
165+
)
166+
167+
if response.status_code == 401:
168+
raise UnauthorizedException(response.json()['message'])
169+
170+
if response.status_code == 403:
171+
detail = json.loads(response.text)
172+
raise ForbiddenException(detail['errors']['detail'])
173+
133174

134175

135176
class EventChildAPIBase(AdminAPIBase, ABC):

src/pytito/admin/account.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -74,7 +74,13 @@ def next_event(self) -> Event:
7474
"""
7575
Return the chronologically first of the upcoming events
7676
"""
77-
upcoming_events = list(self.events.values())
77+
78+
# in some case draft event may not have a start date configured so must be excluded
79+
def include_event(event: Event) -> bool:
80+
# pylint:disable-next=protected-access
81+
return event._json_content['start_at'] is not None
82+
83+
upcoming_events = list(filter(include_event, self.events.values()))
7884
upcoming_events.sort(key=attrgetter('start_at'))
7985
return upcoming_events[0]
8086

src/pytito/admin/activity.py

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ def _populate_json(self) -> None:
5656
raise ValueError('expected the extended view of the ticket')
5757

5858
def _update(self, payload: dict[str, Any]) -> None:
59-
self._patch_reponse(value={'activity': payload})
59+
self._patch_response(value={'activity': payload})
6060
for key, value in payload.items():
6161
self._json_content[key] = value
6262

@@ -91,7 +91,7 @@ def start_at(self, value: Optional[datetime]) -> None:
9191
'set the end_at to None first')
9292
payload = {'date': None,
9393
'start_time': None}
94-
self._patch_reponse(value={'activity': payload})
94+
self._patch_response(value={'activity': payload})
9595
self._json_content['start_at'] = None
9696
else:
9797
if self.end_at is not None and self.end_at.date() != value.date():
@@ -103,7 +103,7 @@ def start_at(self, value: Optional[datetime]) -> None:
103103
# date and time
104104
payload = {'date': value.strftime("%Y-%m-%d"),
105105
'start_time': value.strftime("%H:%M")}
106-
self._patch_reponse(value={'activity': payload})
106+
self._patch_response(value={'activity': payload})
107107
value_str = datetime_to_json(value)
108108
self._json_content['start_at'] = value_str
109109

@@ -124,7 +124,7 @@ def end_at(self, value: Optional[datetime]) -> None:
124124
payload: dict[str, Any]
125125
if value is None:
126126
payload = {'end_time': None}
127-
self._patch_reponse(value={'activity': payload})
127+
self._patch_response(value={'activity': payload})
128128
self._json_content['end_at'] = None
129129
else:
130130
if self.start_at is None:
@@ -137,6 +137,6 @@ def end_at(self, value: Optional[datetime]) -> None:
137137
# the start_at can not be changed directly, instead it is necessary to modify the
138138
# date and time
139139
payload = {'end_time': value.strftime("%H:%M")}
140-
self._patch_reponse(value={'activity': payload})
140+
self._patch_response(value={'activity': payload})
141141
value_str = datetime_to_json(value)
142142
self._json_content['end_at'] = value_str

src/pytito/admin/event.py

Lines changed: 80 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
This file provides the event class
1919
"""
2020
from typing import Optional, Any
21+
import time
2122

2223
from datetime import datetime
2324

@@ -32,12 +33,15 @@ class Event(AdminAPIBase):
3233
One of the events available through the Tito IO AdminAPI
3334
"""
3435

35-
def __init__(self, account_slug:str, event_slug:str,
36+
def __init__(self, *, account_slug:str, event_slug:str,
3637
json_content:Optional[dict[str, Any]]=None,
37-
api_key: Optional[str] = None) -> None:
38-
super().__init__(json_content=json_content, api_key=api_key)
38+
api_key: Optional[str] = None,
39+
allow_automatic_json_retrieval:bool=False) -> None:
40+
super().__init__(json_content=json_content, api_key=api_key,
41+
allow_automatic_json_retrieval=allow_automatic_json_retrieval)
3942
self.__account_slug = account_slug
4043
self.__event_slug = event_slug
44+
self.__api_key_internal = api_key
4145
if json_content is not None:
4246
if self._json_content['_type'] != "event":
4347
raise ValueError('JSON content type was expected to be ticket')
@@ -60,17 +64,35 @@ def _populate_json(self) -> None:
6064
raise ValueError('JSON content type was expected to be ticket')
6165

6266
def _update(self, payload: dict[str, Any]) -> None:
63-
self._patch_reponse(value={'event': payload})
67+
self._patch_response(value={'event': payload})
6468
for key, value in payload.items():
6569
self._json_content[key] = value
6670

71+
def _update_slug(self, new_slug: str) -> None:
72+
"""
73+
The Slug is a unique component of the data used to reference the release in the API.
74+
It is sometimes desirable to change this
75+
76+
.. Warning::
77+
Changing the slug may break things, especially if it clashes with another slug.
78+
Use this method with caution. In particular, the slug is used to key other
79+
dictionaries within the data model. Once changing the clug it is recommended that
80+
the whole data model is refreshed
81+
"""
82+
self._update({'slug': new_slug})
83+
self.__event_slug = new_slug
84+
6785
@property
6886
def title(self) -> str:
6987
"""
7088
Event title
7189
"""
7290
return self._json_content['title']
7391

92+
@title.setter
93+
def title(self, value: str) -> None:
94+
self._update({'title': value})
95+
7496
def __ticket_getter(self) -> list[Ticket]:
7597

7698
def ticket_factory(json_content:dict[str, Any]) -> Ticket:
@@ -104,7 +126,7 @@ def start_at(self, value: datetime) -> None:
104126
# date and time
105127
payload = {'start_date': value.strftime("%Y-%m-%d"),
106128
'start_time': value.strftime("%H:%M")}
107-
self._patch_reponse(value={'event': payload})
129+
self._patch_response(value={'event': payload})
108130
value_str = datetime_to_json(value)
109131
self._json_content['start_at'] = value_str
110132

@@ -124,7 +146,7 @@ def end_at(self, value: datetime) -> None:
124146
# date and time
125147
payload = {'end_date': value.strftime("%Y-%m-%d"),
126148
'end_time': value.strftime("%H:%M")}
127-
self._patch_reponse(value={'event': payload})
149+
self._patch_response(value={'event': payload})
128150
value_str = datetime_to_json(value)
129151
self._json_content['end_at'] = value_str
130152

@@ -177,3 +199,55 @@ def test_mode(self) -> bool:
177199
Whether the event is in test mode
178200
"""
179201
return self._json_content['test_mode']
202+
203+
def duplicate_event(self, title:str, slug:Optional[str]=None) -> "Event":
204+
"""
205+
Duplicate the event and then update the title and optionally the new slug for the
206+
created event
207+
:param title: New event title
208+
:param slug: New event slug, a value of None will leave the automatically created slug in
209+
place
210+
:return: The newly created event
211+
"""
212+
self._post_response('duplication', value={})
213+
for _ in range(120):
214+
time.sleep(1)
215+
duplication_status = self._get_duplication_status()
216+
status = duplication_status['status']
217+
if status == 'processing':
218+
# pylint:disable-next=bad-builtin
219+
print('Duplication in progress')
220+
continue
221+
if status == 'complete':
222+
new_slug = duplication_status['slug']
223+
new_title = duplication_status['title']
224+
new_event = Event(account_slug=self.__account_slug,
225+
event_slug=new_slug,
226+
json_content=None,
227+
api_key=self.__api_key_internal,
228+
allow_automatic_json_retrieval=True)
229+
if new_event.title != new_title:
230+
raise ValueError(f'New event has different title to reported value:{new_title}')
231+
new_event.title = title
232+
if slug is not None:
233+
# The update slug method is a powerful option that is not normally exposed
234+
# to the users so is private
235+
# pylint:disable-next=protected-access
236+
new_event._update_slug(slug)
237+
return new_event
238+
239+
raise ValueError('Unhandled {status=}')
240+
241+
raise RuntimeError('Timeout During Event Duplication')
242+
243+
def _get_duplication_status(self) -> dict[str, Any]:
244+
duplication_status = self._get_response('duplication')['duplication']
245+
if duplication_status['_type'] != '_duplication':
246+
raise RuntimeError('Duplication response does not have a value of _type=_duplication')
247+
return duplication_status
248+
249+
def _delete_event(self) -> None:
250+
"""
251+
Delete the event
252+
"""
253+
self._delete_response()

src/pytito/admin/release.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,7 @@ def _populate_json(self) -> None:
5858
raise ValueError('JSON content type was expected to be release')
5959

6060
def _update(self, payload: dict[str, Any]) -> None:
61-
self._patch_reponse(value={'release': payload})
61+
self._patch_response(value={'release': payload})
6262
for key, value in payload.items():
6363
self._json_content[key] = value
6464

0 commit comments

Comments
 (0)