Automatically use token from config and more cleanups

This commit is contained in:
Sijmen 2019-02-05 16:10:31 +01:00
parent 18508fd549
commit f61cb1632f
8 changed files with 100 additions and 92 deletions

View File

@ -0,0 +1,15 @@
from typing import Any, Callable, Dict
from config import Config
_BASE_URL = 'https://www.pivotaltracker.com/services/v5'
def _with_token(function: Callable) -> Callable:
def wrapper(*args: Any, **kwargs: Any) -> Any:
return function(token=Config['user']['api_token'],
*args, **kwargs)
return wrapper
def _headers(token: str, **other: str) -> Dict[str, Any]:
return {'X-TrackerToken': token, **other}

View File

@ -3,44 +3,46 @@ from typing import Any, Dict, List, Optional
import requests
from . import _BASE_URL, _headers, _with_token
@_with_token
def get(token: str) -> List[Dict[str, Any]]:
r = requests.get(
f'https://www.pivotaltracker.com/services/v5/projects',
headers={'X-TrackerToken': token})
r = requests.get(f'{_BASE_URL}/projects', headers=_headers(token))
return r.json()
def get_project(token: str, project_id: int) -> Dict[str, Any]:
r = requests.get(
f'https://www.pivotaltracker.com/services/v5/projects/{project_id}',
headers={'X-TrackerToken': token})
@_with_token
def get_project(project_id: int, token: str) -> Dict[str, Any]:
r = requests.get(f'{_BASE_URL}/projects/{project_id}',
headers=_headers(token))
return r.json()
def get_stories(token: str, project_id: int) -> List[Dict[str, Any]]:
r = requests.get(
f'https://www.pivotaltracker.com/services/v5/projects/{project_id}'
'/stories', headers={'X-TrackerToken': token})
@_with_token
def get_stories(project_id: int, token: str) -> List[Dict[str, Any]]:
r = requests.get(f'{_BASE_URL}/projects/{project_id}/stories',
headers=_headers(token))
return r.json()
def get_memberships(token: str, project_id: int) -> List[Dict[str, Any]]:
r = requests.get(
f'https://www.pivotaltracker.com/services/v5/projects/{project_id}'
'/memberships', headers={'X-TrackerToken': token})
@_with_token
def get_memberships(project_id: int, token: str) -> List[Dict[str, Any]]:
r = requests.get(f'{_BASE_URL}/projects/{project_id}/memberships',
headers=_headers(token))
return r.json()
def get_iterations(token: str, project_id: int, scope: str) \
@_with_token
def get_iterations(project_id: int, scope: str, token: str) \
-> List[Dict[str, Any]]:
r = requests.get(
f'https://www.pivotaltracker.com/services/v5/projects/{project_id}'
f'/iterations?scope={scope}', headers={'X-TrackerToken': token})
r = requests.get(f'{_BASE_URL}/projects/{project_id}/iterations',
headers=_headers(token), params={'scope': scope})
return r.json()
def get_story_transitions(token: str, project_id: int,
@_with_token
def get_story_transitions(str, project_id: int, token: str,
after: Optional[datetime] = None,
before: Optional[datetime] = None) \
-> List[Dict[str, Any]]:
@ -50,19 +52,15 @@ def get_story_transitions(token: str, project_id: int,
if before:
parameters['occurred_before'] = before.isoformat()
r = requests.get(
f'https://www.pivotaltracker.com/services/v5/projects/{project_id}'
f'/story_transitions',
headers={'X-TrackerToken': token},
params=parameters)
r = requests.get(f'{_BASE_URL}/projects/{project_id}/story_transitions',
headers=_headers(token), params=parameters)
return r.json()
def get_history_days(token: str, project_id: int, start: datetime) \
@_with_token
def get_history_days(project_id: int, start: datetime, token: str) \
-> Dict[str, Any]:
parameters = {'start_date': start.isoformat()}
r = requests.get(
f'https://www.pivotaltracker.com/services/v5/projects/{project_id}'
f'/history/days', headers={'X-TrackerToken': token}, params=parameters)
r = requests.get(f'{_BASE_URL}/projects/{project_id}/history/days',
headers=_headers(token),
params={'start_date': start.isoformat()})
return r.json()

View File

@ -1,40 +1,42 @@
from typing import Any, Dict, List
import requests
from typing import Dict, Any, List
from . import _BASE_URL, _headers, _with_token
@_with_token
def get(token: str, story_id: int = None) -> Dict[str, Any]:
r = requests.get(
f'https://www.pivotaltracker.com/services/v5/stories/{story_id}',
headers={'X-TrackerToken': token})
r = requests.get(f'{_BASE_URL}/stories/{story_id}',
headers=_headers(token))
return r.json()
def put_story(token: str, story_id: int, **kwargs) -> Dict[str, Any]:
r = requests.put(
f'https://www.pivotaltracker.com/services/v5/stories/{story_id}',
headers={'X-TrackerToken': token}, json=kwargs)
@_with_token
def put_story(story_id: int, token: str, **kwargs: Any) -> Dict[str, Any]:
r = requests.put(f'{_BASE_URL}/stories/{story_id}',
headers=_headers(token), json=kwargs)
return r.json()
def get_tasks(token: str, project_id: int, story_id: int) \
@_with_token
def get_tasks(project_id: int, story_id: int, token: str) \
-> List[Dict[str, Any]]:
r = requests.get(
f'https://www.pivotaltracker.com/services/v5/projects/{project_id}'
f'/stories/{story_id}/tasks', headers={'X-TrackerToken': token})
url = f'{_BASE_URL}/projects/{project_id}/stories/{story_id}/tasks'
r = requests.get(url, headers=_headers(token))
return r.json()
def get_comments(token: str, project_id: int, story_id: int) \
@_with_token
def get_comments(project_id: int, story_id: int, token: str) \
-> List[Dict[str, Any]]:
r = requests.get(
f'https://www.pivotaltracker.com/services/v5/projects/{project_id}'
f'/stories/{story_id}/comments', headers={'X-TrackerToken': token})
url = f'{_BASE_URL}/projects/{project_id}/stories/{story_id}/comments'
r = requests.get(url, headers=_headers(token))
return r.json()
def get_blockers(token: str, project_id: int, story_id: int) \
@_with_token
def get_blockers(project_id: int, story_id: int, token: str) \
-> List[Dict[str, Any]]:
r = requests.get(
f'https://www.pivotaltracker.com/services/v5/projects/{project_id}'
f'/stories/{story_id}/blockers', headers={'X-TrackerToken': token})
url = f'{_BASE_URL}/projects/{project_id}/stories/{story_id}/blockers'
r = requests.get(url, headers=_headers(token))
return r.json()

View File

@ -1,6 +1,7 @@
import api.projects
from typing import Dict, Any, Optional
from color import Color
import api.projects
COLOR_TITLE = Color(Color.YELLOW)
COLOR_HEADER = Color(Color.CYAN)
@ -28,8 +29,8 @@ def _format_state(state: str, header: Optional[bool] = False) -> str:
return STATES[state]
def _get_persons(token: str, project_id: int) -> Dict[int, Dict[str, Any]]:
memberships = api.projects.get_memberships(token, project_id)
def _get_persons(project_id: int) -> Dict[int, Dict[str, Any]]:
memberships = api.projects.get_memberships(project_id)
persons: Dict[int, Dict[str, Any]] = {}
for membership in memberships:

View File

@ -8,6 +8,7 @@ import base32_crockford as base32
import api.stories
from config import Config
from util import print_wrap
from . import (COLOR_HEADER, COLOR_TITLE, COLOR_WHITE, _format_state,
_get_persons)
@ -77,9 +78,9 @@ def __print_labels(story: Dict[str, Any]) -> None:
print(f' {labels}', end='\n\n')
def __print_tasks(token: str, project_id: int, story_id: int) -> None:
def __print_tasks(project_id: int, story_id: int) -> None:
"""Prints the tasks of the story, if available."""
tasks = api.stories.get_tasks(token, project_id, story_id)
tasks = api.stories.get_tasks(project_id, story_id)
if tasks:
COLOR_HEADER.print('Tasks:')
@ -93,10 +94,10 @@ def __print_tasks(token: str, project_id: int, story_id: int) -> None:
print()
def __print_comments(token: str, project_id: int, story_id: int,
def __print_comments(project_id: int, story_id: int,
persons: Dict[int, Dict[str, Any]]) -> None:
"""Prints the comments on the story, if available."""
comments = api.stories.get_comments(token, project_id, story_id)
comments = api.stories.get_comments(project_id, story_id)
if comments:
COLOR_HEADER.print('Comments:')
@ -114,9 +115,9 @@ def __print_comments(token: str, project_id: int, story_id: int,
print(date_str, end='\n\n')
def __print_blockers(token: str, project_id: int, story_id: int) -> None:
def __print_blockers(project_id: int, story_id: int) -> None:
"""Prints the stories that block this story, if available."""
blockers = api.stories.get_blockers(token, project_id, story_id)
blockers = api.stories.get_blockers(project_id, story_id)
if blockers:
COLOR_HEADER.print('Blockers:')
@ -134,21 +135,16 @@ def __print_blockers(token: str, project_id: int, story_id: int) -> None:
def stories_info(story_b32: str) -> None:
try:
token = Config['user']['api_token']
except KeyError:
sys.exit(1)
story_id = base32.decode(story_b32)
story = api.stories.get(token, story_id)
story = api.stories.get(story_id)
project_id = story['project_id']
persons = _get_persons(token, project_id)
persons = _get_persons(project_id)
__print_story(story)
__print_owners(story, persons)
__print_description(story)
__print_labels(story)
__print_tasks(token, project_id, story_id)
__print_comments(token, project_id, story_id, persons)
__print_blockers(token, project_id, story_id)
__print_tasks(project_id, story_id)
__print_comments(project_id, story_id, persons)
__print_blockers(project_id, story_id)

View File

@ -2,5 +2,5 @@ import click
@click.group()
def cli():
def cli() -> None:
pass

View File

@ -8,6 +8,7 @@ import tabulate
import api.projects
from config import Config
from util import require_login
from .cli import cli
@ -20,7 +21,7 @@ def projects(context: click.Context) -> None:
# invoked as well. In this case, do nothing.
return
projects_ = api.projects.get(Config['user']['api_token'])
projects_ = api.projects.get()
projects_.sort(key=lambda project_: project_['name'])
aliases: Dict[int, str] = {}
@ -61,11 +62,10 @@ def alias_rm(name: str) -> None:
@click.argument('name')
def info(name: str) -> None:
try:
token = Config['user']['api_token']
project_id = int(Config['project_aliases'][name])
except KeyError:
print(f'unknown alias {name}')
sys.exit(1)
project_info = api.projects.get_project(token, project_id)
project_info = api.projects.get_project(project_id)
print(tabulate.tabulate(project_info.items()))

View File

@ -1,10 +1,10 @@
import click
import math
from collections import defaultdict
from datetime import datetime, timedelta
from typing import Any, DefaultDict, Dict, List, Sequence, Tuple, Optional
from typing import Any, DefaultDict, Dict, List, Optional, Sequence, Tuple
import base32_crockford as base32
import click
import tabulate
import api.projects
@ -12,9 +12,9 @@ import api.stories
from config import Config
from util import require_login
from . import COLOR_HEADER, COLOR_PLANNED, COLOR_STARTED, COLOR_FINISHED, \
COLOR_DELIVERED, COLOR_ACCEPTED, COLOR_REJECTED, _format_state, \
_get_persons
from . import (COLOR_ACCEPTED, COLOR_DELIVERED, COLOR_FINISHED, COLOR_HEADER,
COLOR_PLANNED, COLOR_REJECTED, COLOR_STARTED, Color,
_format_state, _get_persons)
from ._stories_info import stories_info
from .cli import cli
@ -26,7 +26,7 @@ Persons = Dict[int, Dict[str, Any]]
Totals = DefaultDict[int, Dict[str, int]]
def __burndown(color, letter, points):
def __burndown(color: Color, letter: str, points: float) -> str:
return color.format(letter * int(math.ceil(points)))
@ -105,13 +105,12 @@ def __print_totals(totals: Totals, persons: Persons, show_accepted: bool) \
print(tabulate.tabulate(data, headers), end='\n\n')
def __print_burndown(token: str, iteration: Dict[str, Any],
show_accepted: bool) -> None:
def __print_burndown(iteration: Dict[str, Any], show_accepted: bool) -> None:
COLOR_HEADER.print('Burndown:', end='\n\n')
start = datetime.strptime(iteration['start'], '%Y-%m-%dT%H:%M:%SZ')
history = api.projects.get_history_days(
token, iteration['project_id'], start - timedelta(days=1))
iteration['project_id'], start - timedelta(days=1))
accepted_points = history['data'][0][1]
@ -149,29 +148,26 @@ def _stories_current(project: str, scope: str, show_accepted: bool) -> None:
except KeyError:
project_id = base32.decode(project)
token = Config['user']['api_token']
iterations = api.projects.get_iterations(
token, project_id, scope=scope)
iterations = api.projects.get_iterations(project_id, scope=scope)
if not iterations:
print('No stories in', scope)
print(f'No stories in {scope}')
return
iteration = iterations[0]
persons = _get_persons(token, project_id=project_id)
persons = _get_persons(project_id=project_id)
totals: DefaultDict[int, Dict[str, int]] = \
defaultdict(lambda: dict((state, 0) for state in STATES))
__print_stories(iteration['stories'], persons, totals, show_accepted)
__print_totals(totals, persons, show_accepted)
if scope == 'current':
__print_burndown(token, iteration, show_accepted)
__print_burndown(iteration, show_accepted)
def _set_story_state(story: str, state: str) -> None:
token = Config['user']['api_token']
story_id = base32.decode(story)
api.stories.put_story(token, story_id, current_state=state)
api.stories.put_story(story_id, current_state=state)
@cli.command('stories')