Merge pull request #1181 from jakedt/parseme

Refactor how parsed_args are passed to methods
This commit is contained in:
Jake Moshenko 2016-01-27 14:33:20 -05:00
commit fa18316fc4
13 changed files with 142 additions and 137 deletions

View file

@ -212,52 +212,55 @@ def query_param(name, help_str, type=reqparse.text_type, default=None,
return func
return add_param
def page_support(page_token_kwarg='page_token', parsed_args_kwarg='parsed_args'):
def inner(func):
""" Adds pagination support to an API endpoint. The decorated API will have an
added query parameter named 'next_page'. Works in tandem with the
modelutil paginate method.
"""
@wraps(func)
@query_param('next_page', 'The page token for the next page', type=str)
def wrapper(self, *args, **kwargs):
page_token = None
def page_support(func):
""" Adds pagination support to an API endpoint. The decorated API will have an
added query parameter named 'next_page'. Works in tandem with the
modelutil paginate method.
"""
@wraps(func)
@query_param('next_page', 'The page token for the next page', type=str)
def wrapper(self, query_args, *args, **kwargs):
page_token = None
if kwargs[parsed_args_kwarg]['next_page']:
# Decrypt the page token.
unencrypted = decrypt_string(kwargs[parsed_args_kwarg]['next_page'],
app.config['PAGE_TOKEN_KEY'],
ttl=_PAGE_TOKEN_TTL)
if unencrypted is not None:
try:
page_token = json.loads(unencrypted)
except ValueError:
pass
if query_args['next_page']:
# Decrypt the page token.
unencrypted = decrypt_string(query_args['next_page'], app.config['PAGE_TOKEN_KEY'],
ttl=_PAGE_TOKEN_TTL)
if unencrypted is not None:
try:
page_token = json.loads(unencrypted)
except ValueError:
pass
# Note: if page_token is None, we'll receive the first page of results back.
kwargs[page_token_kwarg] = page_token
(result, next_page_token) = func(self, *args, **kwargs)
if next_page_token is not None:
result['next_page'] = encrypt_string(json.dumps(next_page_token),
app.config['PAGE_TOKEN_KEY'])
# Note: if page_token is None, we'll receive the first page of results back.
(result, next_page_token) = func(self, query_args, page_token=page_token, *args, **kwargs)
if next_page_token is not None:
result['next_page'] = encrypt_string(json.dumps(next_page_token),
app.config['PAGE_TOKEN_KEY'])
return result
return result
return wrapper
return inner
return wrapper
def parse_args(kwarg_name='parsed_args'):
def inner(func):
@wraps(func)
def wrapper(self, *args, **kwargs):
if '__api_query_params' not in dir(func):
abort(500)
parser = reqparse.RequestParser()
for arg_spec in func.__api_query_params:
parser.add_argument(**arg_spec)
kwargs[kwarg_name] = parser.parse_args()
def parse_args(func):
@wraps(func)
def wrapper(self, *args, **kwargs):
if '__api_query_params' not in dir(func):
abort(500)
parser = reqparse.RequestParser()
for arg_spec in func.__api_query_params:
parser.add_argument(**arg_spec)
parsed_args = parser.parse_args()
return func(self, parsed_args, *args, **kwargs)
return wrapper
return func(self, *args, **kwargs)
return wrapper
return inner
def parse_repository_name(func):
@wraps(func)

View file

@ -185,14 +185,14 @@ class RepositoryBuildList(RepositoryParamResource):
}
@require_repo_read
@parse_args
@parse_args()
@query_param('limit', 'The maximum number of builds to return', type=int, default=5)
@query_param('since', 'Returns all builds since the given unix timecode', type=int, default=None)
@nickname('getRepoBuilds')
def get(self, args, namespace, repository):
def get(self, namespace, repository, parsed_args):
""" Get the list of repository builds. """
limit = args.get('limit', 5)
since = args.get('since', None)
limit = parsed_args.get('limit', 5)
since = parsed_args.get('since', None)
if since is not None:
since = datetime.datetime.utcfromtimestamp(since)

View file

@ -272,9 +272,9 @@ def swagger_route_data(include_internal=False, compact=False):
@resource('/v1/discovery')
class DiscoveryResource(ApiResource):
"""Ability to inspect the API for usage information and documentation."""
@parse_args
@parse_args()
@query_param('internal', 'Whether to include internal APIs.', type=truthy_bool, default=False)
@nickname('discovery')
def get(self, args):
def get(self, parsed_args):
""" List all of the API endpoints available in the swagger API format."""
return swagger_route_data(args['internal'])
return swagger_route_data(parsed_args['internal'])

View file

@ -113,19 +113,19 @@ class RepositoryLogs(RepositoryParamResource):
""" Resource for fetching logs for the specific repository. """
@require_repo_admin
@nickname('listRepoLogs')
@parse_args
@parse_args()
@query_param('starttime', 'Earliest time from which to get logs (%m/%d/%Y %Z)', type=str)
@query_param('endtime', 'Latest time to which to get logs (%m/%d/%Y %Z)', type=str)
@query_param('page', 'The page number for the logs', type=int, default=1)
@page_support
def get(self, args, namespace, repository, page_token):
@page_support()
def get(self, namespace, repository, page_token, parsed_args):
""" List the logs for the specified repository. """
repo = model.repository.get_repository(namespace, repository)
if not repo:
raise NotFound()
start_time = args['starttime']
end_time = args['endtime']
start_time = parsed_args['starttime']
end_time = parsed_args['endtime']
return get_logs(start_time, end_time, repository=repo, page_token=page_token)
@ -135,16 +135,16 @@ class UserLogs(ApiResource):
""" Resource for fetching logs for the current user. """
@require_user_admin
@nickname('listUserLogs')
@parse_args
@parse_args()
@query_param('starttime', 'Earliest time from which to get logs. (%m/%d/%Y %Z)', type=str)
@query_param('endtime', 'Latest time to which to get logs. (%m/%d/%Y %Z)', type=str)
@query_param('performer', 'Username for which to filter logs.', type=str)
@page_support
def get(self, args, page_token):
@page_support()
def get(self, parsed_args, page_token):
""" List the logs for the current user. """
performer_name = args['performer']
start_time = args['starttime']
end_time = args['endtime']
performer_name = parsed_args['performer']
start_time = parsed_args['starttime']
end_time = parsed_args['endtime']
user = get_authenticated_user()
return get_logs(start_time, end_time, performer_name=performer_name, namespace=user.username,
@ -157,20 +157,20 @@ class UserLogs(ApiResource):
class OrgLogs(ApiResource):
""" Resource for fetching logs for the entire organization. """
@nickname('listOrgLogs')
@parse_args
@parse_args()
@query_param('starttime', 'Earliest time from which to get logs. (%m/%d/%Y %Z)', type=str)
@query_param('endtime', 'Latest time to which to get logs. (%m/%d/%Y %Z)', type=str)
@query_param('performer', 'Username for which to filter logs.', type=str)
@query_param('page', 'The page number for the logs', type=int, default=1)
@page_support
@page_support()
@require_scope(scopes.ORG_ADMIN)
def get(self, args, orgname, page_token):
def get(self, orgname, page_token, parsed_args):
""" List the logs for the specified organization. """
permission = AdministerOrganizationPermission(orgname)
if permission.can():
performer_name = args['performer']
start_time = args['starttime']
end_time = args['endtime']
performer_name = parsed_args['performer']
start_time = parsed_args['starttime']
end_time = parsed_args['endtime']
return get_logs(start_time, end_time, namespace=orgname, performer_name=performer_name,
page_token=page_token)
@ -184,17 +184,17 @@ class RepositoryAggregateLogs(RepositoryParamResource):
""" Resource for fetching aggregated logs for the specific repository. """
@require_repo_admin
@nickname('getAggregateRepoLogs')
@parse_args
@parse_args()
@query_param('starttime', 'Earliest time from which to get logs (%m/%d/%Y %Z)', type=str)
@query_param('endtime', 'Latest time to which to get logs (%m/%d/%Y %Z)', type=str)
def get(self, args, namespace, repository):
def get(self, namespace, repository, parsed_args):
""" Returns the aggregated logs for the specified repository. """
repo = model.repository.get_repository(namespace, repository)
if not repo:
raise NotFound()
start_time = args['starttime']
end_time = args['endtime']
start_time = parsed_args['starttime']
end_time = parsed_args['endtime']
return get_aggregate_logs(start_time, end_time, repository=repo)
@ -204,15 +204,15 @@ class UserAggregateLogs(ApiResource):
""" Resource for fetching aggregated logs for the current user. """
@require_user_admin
@nickname('getAggregateUserLogs')
@parse_args
@parse_args()
@query_param('starttime', 'Earliest time from which to get logs. (%m/%d/%Y %Z)', type=str)
@query_param('endtime', 'Latest time to which to get logs. (%m/%d/%Y %Z)', type=str)
@query_param('performer', 'Username for which to filter logs.', type=str)
def get(self, args):
def get(self, parsed_args):
""" Returns the aggregated logs for the current user. """
performer_name = args['performer']
start_time = args['starttime']
end_time = args['endtime']
performer_name = parsed_args['performer']
start_time = parsed_args['starttime']
end_time = parsed_args['endtime']
user = get_authenticated_user()
return get_aggregate_logs(start_time, end_time, performer_name=performer_name,
@ -225,18 +225,18 @@ class UserAggregateLogs(ApiResource):
class OrgAggregateLogs(ApiResource):
""" Resource for fetching aggregate logs for the entire organization. """
@nickname('getAggregateOrgLogs')
@parse_args
@parse_args()
@query_param('starttime', 'Earliest time from which to get logs. (%m/%d/%Y %Z)', type=str)
@query_param('endtime', 'Latest time to which to get logs. (%m/%d/%Y %Z)', type=str)
@query_param('performer', 'Username for which to filter logs.', type=str)
@require_scope(scopes.ORG_ADMIN)
def get(self, args, orgname):
def get(self, orgname, parsed_args):
""" Gets the aggregated logs for the specified organization. """
permission = AdministerOrganizationPermission(orgname)
if permission.can():
performer_name = args['performer']
start_time = args['starttime']
end_time = args['endtime']
performer_name = parsed_args['performer']
start_time = parsed_args['starttime']
end_time = parsed_args['endtime']
return get_aggregate_logs(start_time, end_time, namespace=orgname,
performer_name=performer_name)

View file

@ -167,7 +167,7 @@ class RepositoryList(ApiResource):
@require_scope(scopes.READ_REPO)
@nickname('listRepos')
@parse_args
@parse_args()
@query_param('page', 'Offset page number. (int)', type=int)
@query_param('limit', 'Limit on the number of results (int)', type=int)
@query_param('namespace', 'Filters the repositories returned to this namespace', type=str)
@ -179,24 +179,26 @@ class RepositoryList(ApiResource):
type=truthy_bool, default=False)
@query_param('popularity', 'Whether to include the repository\'s popularity metric.',
type=truthy_bool, default=False)
def get(self, args):
def get(self, parsed_args):
""" Fetch the list of repositories visible to the current user under a variety of situations.
"""
if not args['namespace'] and not args['starred'] and not args['public']:
if not parsed_args['namespace'] and not parsed_args['starred'] and not parsed_args['public']:
raise InvalidRequest('namespace, starred or public are required for this API call')
repositories, star_lookup = self._load_repositories(args['namespace'], args['public'],
args['starred'], args['limit'],
args['page'])
repositories, star_lookup = self._load_repositories(parsed_args['namespace'],
parsed_args['public'],
parsed_args['starred'],
parsed_args['limit'],
parsed_args['page'])
# Collect the IDs of the repositories found for subequent lookup of popularity
# and/or last modified.
repository_ids = [repo.id for repo in repositories]
if args['last_modified']:
if parsed_args['last_modified']:
last_modified_map = model.repository.get_when_last_modified(repository_ids)
if args['popularity']:
if parsed_args['popularity']:
action_count_map = model.repository.get_action_counts(repository_ids)
def repo_view(repo_obj):
@ -209,10 +211,10 @@ class RepositoryList(ApiResource):
repo_id = repo_obj.id
if args['last_modified']:
if parsed_args['last_modified']:
repo['last_modified'] = last_modified_map.get(repo_id)
if args['popularity']:
if parsed_args['popularity']:
repo['popularity'] = action_count_map.get(repo_id, 0)
if get_authenticated_user():

View file

@ -75,14 +75,14 @@ class UserRobotList(ApiResource):
""" Resource for listing user robots. """
@require_user_admin
@nickname('getUserRobots')
@parse_args
@parse_args()
@query_param('permissions',
'Whether to include repostories and teams in which the robots have permission.',
type=truthy_bool, default=False)
def get(self, args):
def get(self, parsed_args):
""" List the available robots for the user. """
user = get_authenticated_user()
return robots_list(user.username, include_permissions=args.get('permissions', False))
return robots_list(user.username, include_permissions=parsed_args.get('permissions', False))
@resource('/v1/user/robots/<robot_shortname>')
@ -124,15 +124,15 @@ class OrgRobotList(ApiResource):
""" Resource for listing an organization's robots. """
@require_scope(scopes.ORG_ADMIN)
@nickname('getOrgRobots')
@parse_args
@parse_args()
@query_param('permissions',
'Whether to include repostories and teams in which the robots have permission.',
type=truthy_bool, default=False)
def get(self, args, orgname):
def get(self, orgname, parsed_args):
""" List the organization's robots. """
permission = OrganizationMemberPermission(orgname)
if permission.can():
return robots_list(orgname, include_permissions=args.get('permissions', False))
return robots_list(orgname, include_permissions=parsed_args.get('permissions', False))
raise Unauthorized()

View file

@ -19,18 +19,18 @@ import math
class EntitySearch(ApiResource):
""" Resource for searching entities. """
@path_param('prefix', 'The prefix of the entities being looked up')
@parse_args
@parse_args()
@query_param('namespace', 'Namespace to use when querying for org entities.', type=str,
default='')
@query_param('includeTeams', 'Whether to include team names.', type=truthy_bool, default=False)
@query_param('includeOrgs', 'Whether to include orgs names.', type=truthy_bool, default=False)
@nickname('getMatchingEntities')
def get(self, args, prefix):
def get(self, prefix, parsed_args):
""" Get a list of entities that match the specified prefix. """
teams = []
org_data = []
namespace_name = args['namespace']
namespace_name = parsed_args['namespace']
robot_namespace = None
organization = None
@ -42,11 +42,11 @@ class EntitySearch(ApiResource):
if permission.can():
robot_namespace = namespace_name
if args['includeTeams']:
if parsed_args['includeTeams']:
teams = model.team.get_matching_teams(prefix, organization)
if args['includeOrgs'] and AdministerOrganizationPermission(namespace_name) \
and namespace_name.startswith(prefix):
if (parsed_args['includeOrgs'] and AdministerOrganizationPermission(namespace_name) and
namespace_name.startswith(prefix)):
org_data = [{
'name': namespace_name,
'kind': 'org',
@ -217,13 +217,13 @@ def conduct_robot_search(username, query, results):
@resource('/v1/find/all')
class ConductSearch(ApiResource):
""" Resource for finding users, repositories, teams, etc. """
@parse_args
@parse_args()
@query_param('query', 'The search query.', type=str, default='')
@require_scope(scopes.READ_REPO)
@nickname('conductSearch')
def get(self, args):
def get(self, parsed_args):
""" Get a list of entities and resources that match the specified query. """
query = args['query']
query = parsed_args['query']
if not query:
return {'results': []}

View file

@ -62,10 +62,10 @@ class RepositoryImageVulnerabilities(RepositoryParamResource):
@require_repo_read
@nickname('getRepoImageVulnerabilities')
@parse_args
@parse_args()
@query_param('minimumPriority', 'Minimum vulnerability priority', type=str,
default='Low')
def get(self, args, namespace, repository, imageid):
def get(self, namespace, repository, imageid, parsed_args):
""" Fetches the vulnerabilities (if any) for a repository tag. """
repo_image = model.image.get_repo_image(namespace, repository, imageid)
if repo_image is None:
@ -80,7 +80,7 @@ class RepositoryImageVulnerabilities(RepositoryParamResource):
layer_id = '%s.%s' % (repo_image.docker_image_id, repo_image.storage.uuid)
data = _call_security_api('layers/%s/vulnerabilities', layer_id,
minimumPriority=args.minimumPriority)
minimumPriority=parsed_args.minimumPriority)
return {
'status': _get_status(repo_image),

View file

@ -91,14 +91,14 @@ class SuperUserAggregateLogs(ApiResource):
@require_fresh_login
@verify_not_prod
@nickname('listAllAggregateLogs')
@parse_args
@parse_args()
@query_param('starttime', 'Earliest time from which to get logs. (%m/%d/%Y %Z)', type=str)
@query_param('endtime', 'Latest time to which to get logs. (%m/%d/%Y %Z)', type=str)
def get(self, args):
def get(self, parsed_args):
""" Returns the aggregated logs for the current system. """
if SuperUserPermission().can():
start_time = args['starttime']
end_time = args['endtime']
start_time = parsed_args['starttime']
end_time = parsed_args['endtime']
return get_aggregate_logs(start_time, end_time)
@ -113,17 +113,17 @@ class SuperUserLogs(ApiResource):
@require_fresh_login
@verify_not_prod
@nickname('listAllLogs')
@parse_args
@parse_args()
@query_param('starttime', 'Earliest time from which to get logs (%m/%d/%Y %Z)', type=str)
@query_param('endtime', 'Latest time to which to get logs (%m/%d/%Y %Z)', type=str)
@query_param('page', 'The page number for the logs', type=int, default=1)
@page_support
@page_support()
@require_scope(scopes.SUPERUSER)
def get(self, args, page_token):
def get(self, parsed_args, page_token):
""" List the usage logs for the current system. """
if SuperUserPermission().can():
start_time = args['starttime']
end_time = args['endtime']
start_time = parsed_args['starttime']
end_time = parsed_args['endtime']
return get_logs(start_time, end_time, page_token=page_token)

View file

@ -17,12 +17,12 @@ class ListRepositoryTags(RepositoryParamResource):
""" Resource for listing full repository tag history, alive *and dead*. """
@require_repo_read
@parse_args
@parse_args()
@query_param('specificTag', 'Filters the tags to the specific tag.', type=str, default='')
@query_param('limit', 'Limit to the number of results to return per page. Max 100.', type=int, default=50)
@query_param('page', 'Page index for the results. Default 1.', type=int, default=1)
@nickname('listRepoTags')
def get(self, args, namespace, repository):
def get(self, namespace, repository, parsed_args):
repo = model.repository.get_repository(namespace, repository)
if not repo:
raise NotFound()
@ -42,10 +42,10 @@ class ListRepositoryTags(RepositoryParamResource):
return tag_info
specific_tag = args.get('specificTag') or None
specific_tag = parsed_args.get('specificTag') or None
page = max(1, args.get('page', 1))
limit = min(100, max(1, args.get('limit', 50)))
page = max(1, parsed_args.get('page', 1))
limit = min(100, max(1, parsed_args.get('limit', 50)))
# Note: We ask for limit+1 here, so we can check to see if there are
# additional pages of results.
@ -135,10 +135,10 @@ class RepositoryTagImages(RepositoryParamResource):
""" Resource for listing the images in a specific repository tag. """
@require_repo_read
@nickname('listTagImages')
@parse_args
@parse_args()
@query_param('owned', 'If specified, only images wholely owned by this tag are returned.',
type=truthy_bool, default=False)
def get(self, args, namespace, repository, tag):
def get(self, namespace, repository, tag, parsed_args):
""" List the images for the specified repository tag. """
try:
tag_image = model.tag.get_tag_image(namespace, repository, tag)
@ -158,7 +158,7 @@ class RepositoryTagImages(RepositoryParamResource):
# Filter the images returned to those not found in the ancestry of any of the other tags in
# the repository.
if args['owned']:
if parsed_args['owned']:
all_tags = model.tag.list_repository_tags(namespace, repository)
for current_tag in all_tags:
if current_tag.name == tag:
@ -174,7 +174,7 @@ class RepositoryTagImages(RepositoryParamResource):
return {
'images': [image_view(image, image_map_all) for image in all_images
if not args['owned'] or (str(image.id) in image_map)]
if not parsed_args['owned'] or (str(image.id) in image_map)]
}

View file

@ -175,11 +175,11 @@ class OrganizationTeam(ApiResource):
class TeamMemberList(ApiResource):
""" Resource for managing the list of members for a team. """
@require_scope(scopes.ORG_ADMIN)
@parse_args
@parse_args()
@query_param('includePending', 'Whether to include pending members', type=truthy_bool,
default=False)
@nickname('getOrganizationTeamMembers')
def get(self, args, orgname, teamname):
def get(self, orgname, teamname, parsed_args):
""" Retrieve the list of members for the specified team. """
view_permission = ViewTeamPermission(orgname, teamname)
edit_permission = AdministerOrganizationPermission(orgname)
@ -194,7 +194,7 @@ class TeamMemberList(ApiResource):
members = model.organization.get_organization_team_members(team.id)
invites = []
if args['includePending'] and edit_permission.can():
if parsed_args['includePending'] and edit_permission.can():
invites = model.team.get_organization_team_member_invites(team.id)
data = {

View file

@ -450,12 +450,12 @@ class ActivateBuildTrigger(RepositoryParamResource):
class TriggerBuildList(RepositoryParamResource):
""" Resource to represent builds that were activated from the specified trigger. """
@require_repo_admin
@parse_args
@parse_args()
@query_param('limit', 'The maximum number of builds to return', type=int, default=5)
@nickname('listTriggerRecentBuilds')
def get(self, args, namespace, repository, trigger_uuid):
def get(self, namespace, repository, trigger_uuid, parsed_args):
""" List the builds started by the specified trigger. """
limit = args['limit']
limit = parsed_args['limit']
builds = model.build.list_trigger_builds(namespace, repository, trigger_uuid, limit)
return {
'builds': [build_status_view(bld) for bld in builds]

View file

@ -691,13 +691,13 @@ class Recovery(ApiResource):
@internal_only
class UserNotificationList(ApiResource):
@require_user_admin
@parse_args
@parse_args()
@query_param('page', 'Offset page number. (int)', type=int, default=0)
@query_param('limit', 'Limit on the number of results (int)', type=int, default=5)
@nickname('listUserNotifications')
def get(self, args):
page = args['page']
limit = args['limit']
def get(self, parsed_args):
page = parsed_args['page']
limit = parsed_args['limit']
notifications = list(model.notification.list_notifications(get_authenticated_user(), page=page,
limit=limit + 1))
@ -832,14 +832,14 @@ class StarredRepositoryList(ApiResource):
}
@nickname('listStarredRepos')
@parse_args
@parse_args()
@query_param('page', 'Offset page number. (int)', type=int)
@query_param('limit', 'Limit on the number of results (int)', type=int)
@require_user_admin
def get(self, args):
def get(self, parsed_args):
""" List all starred repositories. """
page = args['page']
limit = args['limit']
page = parsed_args['page']
limit = parsed_args['limit']
starred_repos = model.repository.get_user_starred_repositories(get_authenticated_user(),
page=page, limit=limit)
def repo_view(repo_obj):