Skip to content

Commit

Permalink
Feature: allow to exclude relations on object get
Browse files Browse the repository at this point in the history
  • Loading branch information
psrok1 committed Oct 11, 2023
1 parent ab600c3 commit 796c25d
Show file tree
Hide file tree
Showing 11 changed files with 116 additions and 47 deletions.
46 changes: 19 additions & 27 deletions mwdb/model/object.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from sqlalchemy import and_, cast, distinct, exists, func, or_, select
from sqlalchemy.dialects.postgresql import JSONB
from sqlalchemy.exc import IntegrityError
from sqlalchemy.orm import aliased, column_property, contains_eager
from sqlalchemy.orm import column_property
from sqlalchemy.sql.expression import true

from mwdb.core.capabilities import Capabilities
Expand Down Expand Up @@ -668,6 +668,21 @@ def _get_or_create(

return created_object, is_new

def query_visible_parents(self, requestor=None):
"""
Queries for parents visible by specified requestor.
"""
if requestor is None:
requestor = g.auth_user

return (
db.session.query(Object)
.join(relation, relation.c.parent_id == Object.id)
.filter(relation.c.child_id == self.id)
.order_by(relation.c.creation_time.desc())
.filter(requestor.has_access_to_object(Object.id))
)

@classmethod
def access(cls, identifier, requestor=None):
"""
Expand All @@ -686,35 +701,12 @@ def access(cls, identifier, requestor=None):
if requestor is None:
requestor = g.auth_user

obj = cls.get(identifier)
obj_query = cls.get(identifier)
obj = obj_query.first()
# If object doesn't exist - it doesn't exist
if obj.first() is None:
if obj is None:
return None

# In that case we want only those parents to which requestor has access.
stmtp = (
db.session.query(Object)
.join(relation, relation.c.parent_id == Object.id)
.filter(
Object.id.in_(
db.session.query(relation.c.parent_id).filter(
relation.c.child_id == obj.first().id
)
)
)
.order_by(relation.c.creation_time.desc())
.filter(requestor.has_access_to_object(Object.id))
)
stmtp = stmtp.subquery()

parent = aliased(Object, stmtp)

obj = (
obj.outerjoin(parent, Object.parents)
.options(contains_eager(Object.parents, alias=parent))
.all()[0]
)

# Ok, now let's check whether requestor has explicit access
if obj.has_explicit_access(requestor):
return obj
Expand Down
2 changes: 2 additions & 0 deletions mwdb/resources/blob.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
from mwdb.schema.blob import (
BlobCreateRequestSchema,
BlobItemResponseSchema,
BlobItemAndRelationsResponseSchema,
BlobLegacyCreateRequestSchema,
BlobListResponseSchema,
)
Expand Down Expand Up @@ -186,6 +187,7 @@ def post(self):
class TextBlobItemResource(ObjectItemResource, TextBlobUploader):
ObjectType = TextBlob
ItemResponseSchema = BlobItemResponseSchema
ItemAndRelationsResponseSchema = BlobItemAndRelationsResponseSchema
CreateRequestSchema = BlobLegacyCreateRequestSchema

def call_specialised_remove_hook(self, text_blob):
Expand Down
3 changes: 3 additions & 0 deletions mwdb/resources/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
from mwdb.schema.config import (
ConfigCreateRequestSchema,
ConfigItemResponseSchema,
ConfigItemAndRelationsResponseSchema,
ConfigLegacyCreateRequestSchema,
ConfigListResponseSchema,
ConfigStatsRequestSchema,
Expand Down Expand Up @@ -166,6 +167,7 @@ class ConfigResource(ObjectResource, ConfigUploader):
ObjectType = Config
ListResponseSchema = ConfigListResponseSchema
ItemResponseSchema = ConfigItemResponseSchema
ItemAndRelationsResponseSchema = ConfigItemAndRelationsResponseSchema

@requires_authorization
def get(self):
Expand Down Expand Up @@ -309,6 +311,7 @@ class ConfigItemResource(ObjectItemResource, ConfigUploader):

ObjectType = Config
ItemResponseSchema = ConfigItemResponseSchema
ItemAndRelationsResponseSchema = ConfigItemAndRelationsResponseSchema
CreateRequestSchema = ConfigLegacyCreateRequestSchema

def call_specialised_remove_hook(self, config):
Expand Down
11 changes: 11 additions & 0 deletions mwdb/resources/file.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
FileCreateRequestSchema,
FileDownloadTokenResponseSchema,
FileItemResponseSchema,
FileItemAndRelationsResponseSchema,
FileLegacyCreateRequestSchema,
FileListResponseSchema,
)
Expand Down Expand Up @@ -203,6 +204,7 @@ def post(self):
class FileItemResource(ObjectItemResource, FileUploader):
ObjectType = File
ItemResponseSchema = FileItemResponseSchema
ItemAndRelationsResponseSchema = FileItemAndRelationsResponseSchema
CreateRequestSchema = FileLegacyCreateRequestSchema

def call_specialised_remove_hook(self, file):
Expand All @@ -225,6 +227,15 @@ def get(self, identifier):
schema:
type: string
description: File identifier (SHA256/SHA512/SHA1/MD5)
- in: query
name: exclude_relations
schema:
type: integer
description: |
If set, results doesn't include relations
which will be default behavior on next major release of MWDB
required: false
default: 0
responses:
200:
description: Information about file
Expand Down
19 changes: 18 additions & 1 deletion mwdb/resources/object.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,8 @@
from mwdb.schema.object import (
ObjectCountRequestSchema,
ObjectCountResponseSchema,
ObjectItemAndRelationsResponseSchema,
ObjectItemRequestSchema,
ObjectItemResponseSchema,
ObjectListRequestSchema,
ObjectListResponseSchema,
Expand Down Expand Up @@ -262,6 +264,7 @@ def get(self):
class ObjectItemResource(Resource, ObjectUploader):
ObjectType = Object
ItemResponseSchema = ObjectItemResponseSchema
ItemAndRelationsResponseSchema = ObjectItemAndRelationsResponseSchema
CreateRequestSchema = None

def call_specialised_remove_hook(self, obj):
Expand All @@ -284,6 +287,15 @@ def get(self, identifier):
schema:
type: string
description: Object identifier
- in: query
name: exclude_relations
schema:
type: integer
description: |
If set, results doesn't include relations
which will be default behavior on next major release of MWDB
required: false
default: 0
responses:
200:
description: Information about object
Expand All @@ -298,10 +310,15 @@ def get(self, identifier):
description: |
Request canceled due to database statement timeout.
"""
args = load_schema(request.args, ObjectItemRequestSchema())
obj = self.ObjectType.access(identifier)
if obj is None:
raise NotFound("Object not found")
schema = self.ItemResponseSchema()
schema = (
self.ItemResponseSchema()
if args["exclude_relations"]
else self.ItemAndRelationsResponseSchema()
)
return schema.dump(obj)

def _get_upload_args(self, parent_identifier):
Expand Down
2 changes: 1 addition & 1 deletion mwdb/resources/relations.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from mwdb.core.plugins import hooks
from mwdb.core.rate_limit import rate_limited_resource
from mwdb.model import Object, db
from mwdb.schema.relations import RelationsResponseSchema
from mwdb.schema.object import RelationsResponseSchema

from . import access_object, logger, requires_authorization, requires_capabilities

Expand Down
10 changes: 10 additions & 0 deletions mwdb/schema/blob.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
ObjectLegacyMetakeysMixin,
ObjectListItemResponseSchema,
ObjectListResponseSchemaBase,
RelationsResponseSchema,
)
from .utils import UTCDateTime

Expand Down Expand Up @@ -47,3 +48,12 @@ class BlobItemResponseSchema(ObjectItemResponseSchema):
latest_config = fields.Nested(
ConfigItemResponseSchema, required=True, allow_none=True
)


class BlobItemAndRelationsResponseSchema(
BlobItemResponseSchema, RelationsResponseSchema
):
"""
This is legacy schema that returns object item along with relations
It is awfully slow when object is bound with lots of relatives
"""
10 changes: 10 additions & 0 deletions mwdb/schema/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
ObjectLegacyMetakeysMixin,
ObjectListItemResponseSchema,
ObjectListResponseSchemaBase,
RelationsResponseSchema,
)


Expand Down Expand Up @@ -47,6 +48,15 @@ class ConfigItemResponseSchema(ObjectItemResponseSchema):
cfg = fields.Dict(required=True, allow_none=False)


class ConfigItemAndRelationsResponseSchema(
ConfigItemResponseSchema, RelationsResponseSchema
):
"""
This is legacy schema that returns object item along with relations
It is awfully slow when object is bound with lots of relatives
"""


class ConfigStatsItemResponseSchema(Schema):
family = fields.Str(required=True, allow_none=False)
last_upload = fields.Date(required=True, allow_none=False)
Expand Down
10 changes: 10 additions & 0 deletions mwdb/schema/file.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
ObjectLegacyMetakeysMixin,
ObjectListItemResponseSchema,
ObjectListResponseSchemaBase,
RelationsResponseSchema,
)


Expand Down Expand Up @@ -71,5 +72,14 @@ class FileItemResponseSchema(ObjectItemResponseSchema):
)


class FileItemAndRelationsResponseSchema(
FileItemResponseSchema, RelationsResponseSchema
):
"""
This is legacy schema that returns object item along with relations
It is awfully slow when object is bound with lots of relatives
"""


class FileDownloadTokenResponseSchema(Schema):
token = fields.Str(required=True, allow_none=False)
38 changes: 32 additions & 6 deletions mwdb/schema/object.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,17 @@ def validate_key(self, data, **kwargs):
)


class ObjectItemRequestSchema(Schema):
exclude_relations = fields.Boolean(
truthy={
"1", "",
},
falsy={"0"},
missing=False,
allow_none=False,
)


class ObjectCountRequestSchema(Schema):
query = fields.Str(missing=None)

Expand Down Expand Up @@ -103,12 +114,6 @@ class ObjectItemResponseSchema(Schema):
upload_time = UTCDateTime(required=True, allow_none=False)
favorite = fields.Boolean(required=True, allow_none=False)

parents = fields.Nested(
ObjectListItemResponseSchema, many=True, required=True, allow_none=False
)
children = fields.Nested(
ObjectListItemResponseSchema, many=True, required=True, allow_none=False
)
attributes = fields.Nested(
AttributeItemResponseSchema, many=True, required=True, allow_none=False
)
Expand All @@ -125,5 +130,26 @@ def get_accessible_attributes(self, data, object, **kwargs):
return {**data, "attributes": attributes_serialized}


class RelationsResponseSchema(Schema):
parents = fields.Method("get_parents")
children = fields.Nested(
ObjectListItemResponseSchema, many=True, required=True, allow_none=False
)

def get_parents(self, obj):
parents = obj.query_visible_parents().all()
schema = ObjectListItemResponseSchema()
return schema.dump(parents, many=True)


class ObjectItemAndRelationsResponseSchema(
ObjectItemResponseSchema, RelationsResponseSchema
):
"""
This is legacy schema that returns object item along with relations
It is awfully slow when object is bound with lots of relatives
"""


class ObjectCountResponseSchema(Schema):
count = fields.Int()
12 changes: 0 additions & 12 deletions mwdb/schema/relations.py

This file was deleted.

0 comments on commit 796c25d

Please sign in to comment.