class FooResource(Resource): @Route.POST() def bar(self, value): pass bar.request_schema = FieldSet({"value": fields.Boolean(nullable=True)}) @bar.GET() def bar(self): pass bar.response_schema = FieldSet({"value": fields.Boolean(nullable=True)}) class Meta: name = 'foo'
class FooResource(Resource): @Route.POST() def foo(self): return True foo.response_schema = fields.Boolean() class Meta: name = 'foo'
False).keys()) SPECIFIC_VALUES_SCHEMA = fields.List( fields.String(description='An individual dimension value.'), nullable=True, default=EMPTY_LIST, description= 'A list of dimension values that the policy holder will be allowed to or prevented ' 'from querying on. To allow the user to query against all values, you would omit ' 'this field and instead set `allowAllValues` to true.', ) ALL_VALUES_SCHEMA = fields.Boolean( description= 'Set to true if the policy holder should be allowed to query across ALL values ' 'for the specified dimension.', default=False, nullable=True, ) DIMENSION_VALUES_SCHEMA = fields.Object({ 'excludeValues': SPECIFIC_VALUES_SCHEMA, 'includeValues': SPECIFIC_VALUES_SCHEMA, 'allValues': ALL_VALUES_SCHEMA, }) POLICY_FILTERS_SCHEMA = fields.Object( properties=DIMENSION_VALUES_SCHEMA, pattern_properties={ fields.String(title='dimensionType', description='The dimension to filter on'):
def change_password( self, user, new_password: fields.String()) -> fields.Boolean(): user.password_hash = new_password db.session.commit() return True
def activate(self, user) -> fields.Boolean(): user.is_active = True db.session.commit() return True
fields.String( io='r', description= 'The resource that the role is tied to. If null, this role is applicable ' 'sitewide on all resources of the specified type.', nullable=True, ), 'resourceType': fields.String( io='r', description='The type of resource the role is associated with.'), 'applyToUnregistered': fields.Boolean( io='r', description= 'Indicates whether or not this role only applies to registered users. If ' 'set to `false` it only applies to registered users. If set to `true`, it ' 'applies to unregistered/anonymous users as well as long as public access ' 'is enabled.', ), }), converter=None, formatter=lambda user_role: default_role_as_dictionary(user_role, True), ) USERNAME_SCHEMA = fields.Email( description='The e-mail address/username that the user uses to sign-in.', pattern=EMAIL_PATTERN, ) _USER_FIELDS = { 'username':
def activate(self, domain) -> fields.Boolean(): domain.is_active = True db.session.commit() return True
class Schema: description = fields.String() is_open = fields.Boolean(default=False)
ROLE_PERMISSIONS_FIELDS) ROLE_LIST_SCHEMA = fields.List( ROLE_NAME_SCHEMA, title='roles', description='A listing of roles held by a user or security group on an ' 'indvidual resource or all resources of a specific type.', ) DEFAULT_ROLE_SCHEMA = fields.Object({ 'roleName': ROLE_NAME_SCHEMA, 'applyToUnregistered': fields.Boolean( nullable=False, description= 'Indicates whether or not this role only applies to registered users. If ' 'set to `false` it only applies to registered users. If set to `false`, it ' 'applies to unregistered/anonymous users as well as long as public access ' 'is enabled.', ), }) USER_ROLES_MAPPING = fields.Object( properties=ROLE_LIST_SCHEMA, pattern_properties={ fields.String(title='username', description='The user\'s username'): ROLE_LIST_SCHEMA }, default=None, nullable=True, description= 'A mapping of usernames to a list of roles that a user should have for a given '
'web.server.api.permission_api_models.BackendResource', attribute='resource_id' ) CREATED_SCHEMA = fields.DateTimeString( description='When the dashboard was created.', attribute='created', io='r' ) LAST_MODIFIED_SCHEMA = fields.DateTimeString( description='The last time the dashboard was modified.', attribute='last_modified_real', io='r', ) IS_OFFICIAL_SCHEMA = fields.Boolean( attribute='is_official', description='Indicates whether or not an administrator has flagged the ' 'dashboard as "official" or not.', io='r', ) IS_FAVORITE_SCHEMA = fields.Boolean( description='Indicates whether or not the dashboard has been favorited by the current user.', attribute='is_favorite', io='r', ) LAST_ACCESSED_BY_USER_SCHEMA = fields.DateTimeString( description='The last time the dashboard was accessed (if ever) by the current user.', nullable=True, attribute='last_accessed_by_user', io='r', )
class StandardResponse(dict): '''A response type that all Zenysis APIs should use ''' def __init__(self, description, code, success, **additional_fields): super(StandardResponse, self).__init__() self['message'] = description self['code'] = code self['success'] = success for key, value in list(additional_fields.items()): self[key] = value STANDARD_RESPONSE_FIELDS = { 'success': fields.Boolean( description='Indicates whether the requested operation was successful or not.', nullable=True, ), 'message': fields.String(min_length=1, description='The response from the server.'), 'code': fields.Integer(description='The HTTP response code from the server.'), } STANDARD_RESPONSE_SCHEMA = FieldSet(STANDARD_RESPONSE_FIELDS) def augment_standard_schema(additional_fields): '''Augments the schema of the `StandardResponse` class with additional fields that are to be included in the response to an API request. Example ---------- ```
def is_recent(self, book) -> fields.Boolean(): return datetime.date.today().year <= book.year_published + 10
def deactivate(self, permission) -> fields.Boolean(): return False
def test_boolean_non_nullable(self): field = fields.Boolean() assert field.format(None) is False
def test_boolean_nullable(self): field = fields.Boolean(nullable=True) assert field.format(None) is None
class DashboardResource(PrincipalResource): '''The potion class for performing CRUD operations on the `Dashboard` class. ''' resource = Relation('resource', io='r') author = Relation('user', io='r') class Meta(object): manager = principals(DashboardManager) model = Dashboard natural_key = 'slug' excluded_fields = ('id',) id_attribute = 'resource_id' permissions = {'read': 'view_resource'} filters = { 'slug': True, 'title': True, 'created': True, 'author': {'eq': UserFilter, None: UserFilter}, 'isOfficial': True, } class Schema(object): title = TITLE_SCHEMA slug = NULLABLE_SLUG_SCHEMA description = DESCRIPTION_SCHEMA specification = SPECIFICATION_SCHEMA authorUsername = AUTHOR_USERNAME_SCHEMA author = AUTHOR_URI_SCHEMA resource = RESOURCE_URI_SCHEMA created = CREATED_SCHEMA isOfficial = IS_OFFICIAL_SCHEMA # NOTE(stephen): These fields are now pulled **from metadata** not from # the Dashboard model directly. They are only represented here to make # flask potion happy EVEN THOUGH THEY WILL NEVER EVER BE SENT BY THE # CLIENT, FLASK POTION STILL COMPLAINS. lastModified = UNUSED_LAST_MODIFIED_SCHEMA totalViews = UNUSED_TOTAL_VIEWS_SCHEMA # HACK(stephen): Attaching dashboard metadata to the base dashboard model # response is like fitting a square peg into a round hole. To add that # information in ways that Flask-Potion would naturally work causes us to # issue a huge number of queries per dashboard (previously 14 queries per # dashboard with a naive implementation). This query encapsulates all the # information needed for the dashboard response into a single query. # TODO(stephen): If we have to write workarounds like this, it probably # means we shouldn't be jamming too much information into a single API. def _attach_metadata_to_query(self, query): # NOTE(stephen): I don't think a transaction is necessary for this # read only query, but it is an easy way to access the session. with Transaction() as transaction: session = transaction._session subquery = DashboardUserMetadata.summary_by_dashboard_for_user( session, current_user.id ).subquery() return ( query # Join in the summarized metadata for each dashboard. .outerjoin(subquery, Dashboard.id == subquery.c.dashboard_id) # Attach user info so we can extract the author username. .outerjoin(User, Dashboard.author_id == User.id) # Make sure all the metadata columns are included. .add_columns(subquery) # Also include all dashboard columns since otherwise a new query # will be issued EACH TIME we access a dashboard in the query # result. .add_columns(Dashboard.__table__) # Manually set up author_username since hybrid properties weren't # transferring. .add_columns(User.username.label('author_username')) ) # HACK(stephen): To ensure endpoints that return a single dashboard also # include the appropriate metadata, we must join in the dashboard metadata # to our single dashboard query. def _get_single_dashboard_with_metadata(self, resource_id): # NOTE(stephen): The resource_id being filtered on here is **not** # Dashboard.id. This is because we use the `resource_id` column for # lookups and reference. query = self.manager._query().filter(self.manager.id_column == resource_id) return self._attach_metadata_to_query(query).one() # pylint: disable=R0201 # pylint: disable=E1101 # Flask Potion does not allow class methods. # Override the default "get all dashboards" route to augment the response # with dashboard specific metadata. @Route.GET( '', rel='instances', title='Something', description='Something', schema=Instances(), response_schema=fields.Array(fields.Object(DASHBOARD_SIMPLE_FIELDS)), ) def get_instances(self, page, per_page, where, sort): base_query = self.manager.instances(where, sort) # NOTE(stephen): I'm not sure why this wouldn't exist, but I think it # only happens when there are no dashboards in the DB. if not base_query: return [] return self._attach_metadata_to_query(base_query).paginate(page, per_page).items @Route.POST( '/upgrade', title='Upgrade Dashboard Specification', description='Upgrades the provided dashboard specification to the ' 'latest schema version supported by the server.', schema=fields.Any(), rel='upgrade', ) def upgrade_dashboard(self, dashboard_specification): # The validation is being done in the conversion defined by SPECIFICATION_SCHEMA. If there # are any errors, they will be thrown as an exception to the client. No action needs to be # taken here. return format_and_upgrade_specification(dashboard_specification) @ItemRoute.POST( '/visualization', title='Add Simple Query Visualization', description='Adds an item from Simple Query Tool to the dashboard.', schema=ADD_QUERY_TO_DASHBOARD_SCHEMA, response_schema=DETAILED_DASHBOARD_SCHEMA, rel='addVisualization', ) def add_visualization(self, dashboard, request): resource_id = dashboard.resource.id with AuthorizedOperation('edit_resource', 'dashboard', resource_id): _add_visualization(dashboard, request, False) track_dashboard_access(dashboard.id, True) return self._get_single_dashboard_with_metadata(resource_id) @ItemRoute.POST( '/visualization/advanced', title='Add Advanced Query Visualization', description='Adds an item from Advanced Query Tool to the dashboard.', schema=ADD_QUERY_TO_DASHBOARD_SCHEMA, response_schema=DETAILED_DASHBOARD_SCHEMA, rel='addAdvancedVisualization', ) def add_advanced_visualization(self, dashboard, request): resource_id = dashboard.resource.id with AuthorizedOperation('edit_resource', 'dashboard', resource_id): _add_visualization(dashboard, request, True) track_dashboard_access(dashboard.id, True) return self._get_single_dashboard_with_metadata(resource_id) @ItemRoute.POST( '/transfer', title='Transfer Ownership', description='Transfers the ownership of this dashboard from the current' 'owner to the one specified. ', schema=USER_URI_SCHEMA, ) def transfer_ownership(self, dashboard, new_author): with AuthorizedOperation( 'update_users', 'dashboard', dashboard.resource_id ), AuthorizedOperation('view_resource', 'user', dashboard.author.id): new_author = lookup_author(author_id=new_author) api_transfer_dashboard_ownership(dashboard, new_author) return None, NO_CONTENT @ItemRoute.POST( '/transfer/username', title='Transfer Ownership', description='Transfers the ownership of this dashboard from the current' 'owner to the one specified. ', schema=USERNAME_SCHEMA, ) def transfer_ownership_by_username(self, dashboard, new_author): with AuthorizedOperation( 'update_users', 'dashboard', dashboard.resource_id ), AuthorizedOperation('view_resource', 'user', dashboard.author.id): new_author = lookup_author(author_username=new_author) api_transfer_dashboard_ownership(dashboard, new_author) return None, NO_CONTENT @Route.POST( '/transfer', title='Transfer Ownership', description='Transfers the ownership of ALL dashboards from one user ' 'to another.', schema=fields.Object( {'sourceAuthor': USER_URI_SCHEMA, 'targetAuthor': USER_URI_SCHEMA} ), ) @authorization_required('update_users', 'dashboard') def transfer_bulk_ownership(self, request): source_author = lookup_author(author_id=request['sourceAuthor']) target_author = lookup_author(author_id=request['targetAuthor']) api_bulk_transfer_dashboard_ownership(source_author, target_author) return None, NO_CONTENT @Route.POST( '/transfer/username', title='Transfer Ownership', description='Transfers the ownership of ALL dashboards from one user ' 'to another.', schema=fields.Object( {'sourceAuthor': USERNAME_SCHEMA, 'targetAuthor': USERNAME_SCHEMA} ), ) @authorization_required('update_users', 'dashboard') def transfer_bulk_ownership_by_username(self, request): source_author = lookup_author(author_username=request['sourceAuthor']) target_author = lookup_author(author_username=request['targetAuthor']) api_bulk_transfer_dashboard_ownership(source_author, target_author) return None, NO_CONTENT @ItemRoute.POST( '/official', title='Update Dashboard \'official\' flag', description='Marks a Dashboard as official or not.', schema=fields.Boolean( description='The updated value of the "isOfficial" flag for the ' 'dashboard.' ), ) @authorization_required('publish_resource', 'dashboard') def set_official(self, dashboard, is_official): self.manager.update(dashboard, {'is_official': is_official}) return None, NO_CONTENT @ItemRoute.POST( '/favorite', title='Update Dashboard \'favorite\' flag', description='Marks a Dashboard as a user favorite or not.', schema=fields.Boolean( description='The updated value of the "isFavorite" flag for the ' 'dashboard.' ), ) def set_favorite(self, dashboard, is_favorite): with AuthorizedOperation( 'view_resource', 'dashboard', dashboard.id ), Transaction() as transaction: metadata = get_or_create_metadata(transaction, dashboard.id) metadata.is_favorite = is_favorite transaction.add_or_update(metadata) return None, NO_CONTENT # NOTE(vedant): Why are we overriding the default potion route for GET <{}:id> and # PATCH <{}:id>? # # We want to only display the dashboard specification when an individual dashboard is # requested. To do this, we have to tailor the response schema to include the specification # field which would otherwise NOT be included in the default Schema specified in the `Schema` # subclass of `DashboardResource`. # # The rationale for only display the complete dashboard specification is that specifications # have a tendency to be very large and we don't want to send a lot of useless data over the # wire unless it is specifically asked for by the client. It is also very unlikely that a # client will ever be loading more than one dashboard specification at a given time. @Route.GET( lambda r: '/<{}:id>'.format(r.meta.id_converter), rel='self', attribute='instance', response_schema=DETAILED_DASHBOARD_SCHEMA, ) def read(self, id): with Transaction() as transaction: dashboard = super(DashboardResource, self).read(id) dashboard.total_views += 1 track_dashboard_access(dashboard.id) dashboard = transaction.add_or_update(dashboard, flush=True) return self._get_single_dashboard_with_metadata(id) @Route.PATCH( lambda r: '/<{}:id>'.format(r.meta.id_converter), rel='update', schema=fields.Inline('self', patchable=True), response_schema=DETAILED_DASHBOARD_SCHEMA, ) def update(self, properties, id): dashboard = super(DashboardResource, self).update(properties, id) track_dashboard_access(dashboard.id, edited=True) return self._get_single_dashboard_with_metadata(id) @ItemRoute.GET( '/history', title='Dashboard Update History', schema=Instances(), description='Gets dashboard history data', rel='getDashboardHistory', response_schema=DASHBOARD_CHANGES_SCHEMA, ) # pylint: disable=W0613 # Method signature requires where and sort params def get_history(self, dashboard, page, per_page, where, sort): records = [] with Transaction() as transaction: records = ( transaction.find_all_by_fields( HistoryRecord, {'object_id': dashboard.resource_id, 'object_type': self.meta.name}, ) .paginate(page, per_page) .items ) return records
class Schema: id = fields.String(min_length=3, max_length=20) network = fields.ToOne('networks') title = fields.String(min_length=3) login_ask_name = fields.Boolean(default=False) login_require_name = fields.Boolean(default=False)
def activate(self, project) -> fields.Boolean(): project.is_active = True db.session.commit() return True