Automatically use token from config and more cleanups
This commit is contained in:
parent
18508fd549
commit
f61cb1632f
8 changed files with 100 additions and 92 deletions
|
@ -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}
|
|
@ -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()
|
||||
|
|
|
@ -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()
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -2,5 +2,5 @@ import click
|
|||
|
||||
|
||||
@click.group()
|
||||
def cli():
|
||||
def cli() -> None:
|
||||
pass
|
||||
|
|
|
@ -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()))
|
||||
|
|
|
@ -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')
|
||||
|
|
Loading…
Reference in a new issue