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={}):
|
||||
"""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
|
||||
retry = requests.Session()
|
||||
retry.mount("https://", requests.adapters.HTTPAdapter(max_retries=3))
|
||||
@ -28,7 +36,15 @@ def requester(url, params={}, headers={}):
|
||||
|
||||
|
||||
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
|
||||
if raw.text.startswith(")]}'"):
|
||||
|
@ -14,6 +14,16 @@ import shelve
|
||||
|
||||
|
||||
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):
|
||||
self._shelf = shelve.open(filename)
|
||||
|
@ -22,20 +22,24 @@ MEMBER_LOOKUP_URL = 'https://openstackid-resources.openstack.org/'
|
||||
|
||||
|
||||
class Affiliation:
|
||||
"A Foundation member relationship to an employer"
|
||||
|
||||
def __init__(self, data):
|
||||
self._data = data
|
||||
|
||||
@property
|
||||
def organization(self):
|
||||
"The name of the employer"
|
||||
return self._data['organization']['name']
|
||||
|
||||
@property
|
||||
def is_current(self):
|
||||
"Boolean indicating if the affiliation is set to be current"
|
||||
return self._data.get('is_current', False)
|
||||
|
||||
@property
|
||||
def start_date(self):
|
||||
"Start date of affiliation, if given"
|
||||
start = self._data['start_date']
|
||||
if start:
|
||||
return datetime.datetime.utcfromtimestamp(start)
|
||||
@ -43,16 +47,32 @@ class Affiliation:
|
||||
|
||||
@property
|
||||
def end_date(self):
|
||||
"End date of affiliation, if given"
|
||||
end = self._data['end_date']
|
||||
if end:
|
||||
return datetime.datetime.utcfromtimestamp(end)
|
||||
return None
|
||||
|
||||
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:
|
||||
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():
|
||||
return False
|
||||
if self.end_date and self.end_date.date() < when.date():
|
||||
@ -61,6 +81,7 @@ class Affiliation:
|
||||
|
||||
|
||||
class Member:
|
||||
"A person who is a member of the Foundation"
|
||||
|
||||
def __init__(self, email, data):
|
||||
self.email = email
|
||||
@ -68,6 +89,7 @@ class Member:
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
"The person's full name"
|
||||
return ' '.join([self._data['first_name'], self._data['last_name']])
|
||||
|
||||
@property
|
||||
@ -100,11 +122,18 @@ def lookup_member(email):
|
||||
},
|
||||
headers={'Accept': 'application/json'},
|
||||
)
|
||||
|
||||
return apis.decode_json(raw)['data'][0]
|
||||
|
||||
|
||||
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)
|
||||
if key in cache:
|
||||
LOG.debug('found %s cached', email)
|
||||
|
@ -62,6 +62,7 @@ def query_gerrit(method, params={}):
|
||||
|
||||
|
||||
def _to_datetime(s):
|
||||
"Convert a string to a datetime.datetime instance"
|
||||
# Ignore the trailing decimal seconds.
|
||||
s = s.rpartition('.')[0]
|
||||
return datetime.datetime.strptime(s, '%Y-%m-%d %H:%M:%S')
|
||||
@ -72,6 +73,7 @@ Participant = collections.namedtuple(
|
||||
|
||||
|
||||
class Review:
|
||||
"The history of one code review"
|
||||
|
||||
def __init__(self, id, data):
|
||||
self._id = id
|
||||
@ -127,6 +129,14 @@ class Review:
|
||||
|
||||
|
||||
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)
|
||||
if key in cache:
|
||||
LOG.debug('found %s cached', review_id)
|
||||
|
@ -25,6 +25,7 @@ LOG = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class DateColumn(columns.FormattableColumn):
|
||||
"Format a datetime.datetime to make it serializable."
|
||||
|
||||
def human_readable(self):
|
||||
return str(self._value)
|
||||
|
@ -23,6 +23,8 @@ from goal_tools import caching
|
||||
|
||||
|
||||
class WhoHelped(app.App):
|
||||
"""Tool for extracting data and statistics about contributors to projects.
|
||||
"""
|
||||
|
||||
def __init__(self):
|
||||
version_info = pbr.version.VersionInfo('goal-tools')
|
||||
|
@ -16,6 +16,7 @@ from goal_tools import foundation
|
||||
|
||||
|
||||
class ShowMember(show.ShowOne):
|
||||
"Show a Foundation member's basic information."
|
||||
|
||||
def get_parser(self, prog_name):
|
||||
parser = super().get_parser(prog_name)
|
||||
|
Loading…
Reference in New Issue
Block a user