add/enhance docstrings
Change-Id: I955450cb3dabb38efc2f9e163981d71a55fc4a6f Signed-off-by: Doug Hellmann <doug@doughellmann.com>
This commit is contained in:
parent
a041252855
commit
dc98f770e2
@ -19,8 +19,16 @@ LOG = logging.getLogger(__name__)
|
|||||||
|
|
||||||
|
|
||||||
def requester(url, params={}, headers={}):
|
def requester(url, params={}, headers={}):
|
||||||
"""A requests wrapper to consistently retry HTTPS queries"""
|
"""A requests wrapper to consistently retry HTTPS queries
|
||||||
|
|
||||||
|
:param url: The URL to get.
|
||||||
|
:type url: str
|
||||||
|
:param params: Additional parameters to provide.
|
||||||
|
:type params: dict(str, str)
|
||||||
|
:param headers: Additional headers to set.
|
||||||
|
:type params: dict(str, str)
|
||||||
|
|
||||||
|
"""
|
||||||
# Try up to 3 times
|
# Try up to 3 times
|
||||||
retry = requests.Session()
|
retry = requests.Session()
|
||||||
retry.mount("https://", requests.adapters.HTTPAdapter(max_retries=3))
|
retry.mount("https://", requests.adapters.HTTPAdapter(max_retries=3))
|
||||||
@ -28,7 +36,15 @@ def requester(url, params={}, headers={}):
|
|||||||
|
|
||||||
|
|
||||||
def decode_json(raw):
|
def decode_json(raw):
|
||||||
"""Trap JSON decoding failures and provide more detailed errors"""
|
"""Trap JSON decoding failures and provide more detailed errors
|
||||||
|
|
||||||
|
Remove ')]}' XSS prefix from data if it is present, then decode it
|
||||||
|
as JSON and return the results.
|
||||||
|
|
||||||
|
:param raw: Response text from API
|
||||||
|
:type raw: str
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
# Gerrit's REST API prepends a JSON-breaker to avoid XSS vulnerabilities
|
# Gerrit's REST API prepends a JSON-breaker to avoid XSS vulnerabilities
|
||||||
if raw.text.startswith(")]}'"):
|
if raw.text.startswith(")]}'"):
|
||||||
|
@ -14,6 +14,16 @@ import shelve
|
|||||||
|
|
||||||
|
|
||||||
class Cache:
|
class Cache:
|
||||||
|
"""Data cache with transparent key management
|
||||||
|
|
||||||
|
Keys passed to methods are expected to be tuples of strings but
|
||||||
|
are converted to something the underlying implementation can
|
||||||
|
store and retrieve.
|
||||||
|
|
||||||
|
Values stored in the cache are pickled before being written and
|
||||||
|
unpickled before being returned.
|
||||||
|
|
||||||
|
"""
|
||||||
|
|
||||||
def __init__(self, filename):
|
def __init__(self, filename):
|
||||||
self._shelf = shelve.open(filename)
|
self._shelf = shelve.open(filename)
|
||||||
|
@ -22,20 +22,24 @@ MEMBER_LOOKUP_URL = 'https://openstackid-resources.openstack.org/'
|
|||||||
|
|
||||||
|
|
||||||
class Affiliation:
|
class Affiliation:
|
||||||
|
"A Foundation member relationship to an employer"
|
||||||
|
|
||||||
def __init__(self, data):
|
def __init__(self, data):
|
||||||
self._data = data
|
self._data = data
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def organization(self):
|
def organization(self):
|
||||||
|
"The name of the employer"
|
||||||
return self._data['organization']['name']
|
return self._data['organization']['name']
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def is_current(self):
|
def is_current(self):
|
||||||
|
"Boolean indicating if the affiliation is set to be current"
|
||||||
return self._data.get('is_current', False)
|
return self._data.get('is_current', False)
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def start_date(self):
|
def start_date(self):
|
||||||
|
"Start date of affiliation, if given"
|
||||||
start = self._data['start_date']
|
start = self._data['start_date']
|
||||||
if start:
|
if start:
|
||||||
return datetime.datetime.utcfromtimestamp(start)
|
return datetime.datetime.utcfromtimestamp(start)
|
||||||
@ -43,16 +47,32 @@ class Affiliation:
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def end_date(self):
|
def end_date(self):
|
||||||
|
"End date of affiliation, if given"
|
||||||
end = self._data['end_date']
|
end = self._data['end_date']
|
||||||
if end:
|
if end:
|
||||||
return datetime.datetime.utcfromtimestamp(end)
|
return datetime.datetime.utcfromtimestamp(end)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
def active(self, when):
|
def active(self, when):
|
||||||
|
"""Is the affiliation was in effect on the date specified.
|
||||||
|
|
||||||
|
If we have a current affiliation without start and end dates,
|
||||||
|
assume it is active.
|
||||||
|
|
||||||
|
Otherwise the start date and end dates are compared to the
|
||||||
|
date provided to determine if it falls within the inclusive
|
||||||
|
range.
|
||||||
|
|
||||||
|
Although the argument needs to be a datetime instance, only
|
||||||
|
the date portion is used for comparison. We assume that
|
||||||
|
someone does not change affiliations on the same day.
|
||||||
|
|
||||||
|
:param when: The date to check for active status
|
||||||
|
:type when: datetime.datetime
|
||||||
|
|
||||||
|
"""
|
||||||
if not self.start_date and not self.end_date and self.is_current:
|
if not self.start_date and not self.end_date and self.is_current:
|
||||||
return True
|
return True
|
||||||
# Compare only the date portion so we don't have to worry
|
|
||||||
# about the time of day.
|
|
||||||
if self.start_date and self.start_date.date() > when.date():
|
if self.start_date and self.start_date.date() > when.date():
|
||||||
return False
|
return False
|
||||||
if self.end_date and self.end_date.date() < when.date():
|
if self.end_date and self.end_date.date() < when.date():
|
||||||
@ -61,6 +81,7 @@ class Affiliation:
|
|||||||
|
|
||||||
|
|
||||||
class Member:
|
class Member:
|
||||||
|
"A person who is a member of the Foundation"
|
||||||
|
|
||||||
def __init__(self, email, data):
|
def __init__(self, email, data):
|
||||||
self.email = email
|
self.email = email
|
||||||
@ -68,6 +89,7 @@ class Member:
|
|||||||
|
|
||||||
@property
|
@property
|
||||||
def name(self):
|
def name(self):
|
||||||
|
"The person's full name"
|
||||||
return ' '.join([self._data['first_name'], self._data['last_name']])
|
return ' '.join([self._data['first_name'], self._data['last_name']])
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -100,11 +122,18 @@ def lookup_member(email):
|
|||||||
},
|
},
|
||||||
headers={'Accept': 'application/json'},
|
headers={'Accept': 'application/json'},
|
||||||
)
|
)
|
||||||
|
|
||||||
return apis.decode_json(raw)['data'][0]
|
return apis.decode_json(raw)['data'][0]
|
||||||
|
|
||||||
|
|
||||||
def fetch_member(email, cache):
|
def fetch_member(email, cache):
|
||||||
|
"""Find the member in the cache or look it up in the API.
|
||||||
|
|
||||||
|
:param email: Email address of the member to look for.
|
||||||
|
:type email: str
|
||||||
|
:param cache: Storage for repeated lookups.
|
||||||
|
:type cache: goal_tools.cache.Cache
|
||||||
|
|
||||||
|
"""
|
||||||
key = ('member', email)
|
key = ('member', email)
|
||||||
if key in cache:
|
if key in cache:
|
||||||
LOG.debug('found %s cached', email)
|
LOG.debug('found %s cached', email)
|
||||||
|
@ -62,6 +62,7 @@ def query_gerrit(method, params={}):
|
|||||||
|
|
||||||
|
|
||||||
def _to_datetime(s):
|
def _to_datetime(s):
|
||||||
|
"Convert a string to a datetime.datetime instance"
|
||||||
# Ignore the trailing decimal seconds.
|
# Ignore the trailing decimal seconds.
|
||||||
s = s.rpartition('.')[0]
|
s = s.rpartition('.')[0]
|
||||||
return datetime.datetime.strptime(s, '%Y-%m-%d %H:%M:%S')
|
return datetime.datetime.strptime(s, '%Y-%m-%d %H:%M:%S')
|
||||||
@ -72,6 +73,7 @@ Participant = collections.namedtuple(
|
|||||||
|
|
||||||
|
|
||||||
class Review:
|
class Review:
|
||||||
|
"The history of one code review"
|
||||||
|
|
||||||
def __init__(self, id, data):
|
def __init__(self, id, data):
|
||||||
self._id = id
|
self._id = id
|
||||||
@ -127,6 +129,14 @@ class Review:
|
|||||||
|
|
||||||
|
|
||||||
def fetch_review(review_id, cache):
|
def fetch_review(review_id, cache):
|
||||||
|
"""Find the review in the cache or look it up in the API.
|
||||||
|
|
||||||
|
:param review_id: Review ID of the review to look for.
|
||||||
|
:type review_id: str
|
||||||
|
:param cache: Storage for repeated lookups.
|
||||||
|
:type cache: goal_tools.cache.Cache
|
||||||
|
|
||||||
|
"""
|
||||||
key = ('review', review_id)
|
key = ('review', review_id)
|
||||||
if key in cache:
|
if key in cache:
|
||||||
LOG.debug('found %s cached', review_id)
|
LOG.debug('found %s cached', review_id)
|
||||||
|
@ -25,6 +25,7 @@ LOG = logging.getLogger(__name__)
|
|||||||
|
|
||||||
|
|
||||||
class DateColumn(columns.FormattableColumn):
|
class DateColumn(columns.FormattableColumn):
|
||||||
|
"Format a datetime.datetime to make it serializable."
|
||||||
|
|
||||||
def human_readable(self):
|
def human_readable(self):
|
||||||
return str(self._value)
|
return str(self._value)
|
||||||
|
@ -23,6 +23,8 @@ from goal_tools import caching
|
|||||||
|
|
||||||
|
|
||||||
class WhoHelped(app.App):
|
class WhoHelped(app.App):
|
||||||
|
"""Tool for extracting data and statistics about contributors to projects.
|
||||||
|
"""
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
version_info = pbr.version.VersionInfo('goal-tools')
|
version_info = pbr.version.VersionInfo('goal-tools')
|
||||||
|
@ -16,6 +16,7 @@ from goal_tools import foundation
|
|||||||
|
|
||||||
|
|
||||||
class ShowMember(show.ShowOne):
|
class ShowMember(show.ShowOne):
|
||||||
|
"Show a Foundation member's basic information."
|
||||||
|
|
||||||
def get_parser(self, prog_name):
|
def get_parser(self, prog_name):
|
||||||
parser = super().get_parser(prog_name)
|
parser = super().get_parser(prog_name)
|
||||||
|
Loading…
Reference in New Issue
Block a user