Skip to content

Feature: allow to exclude relations on object get #881

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 1 commit into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -8,6 +8,7 @@
from mwdb.model.object import ObjectTypeConflictError
from mwdb.schema.blob import (
BlobCreateRequestSchema,
BlobItemAndRelationsResponseSchema,
BlobItemResponseSchema,
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 @@ -13,6 +13,7 @@
from mwdb.schema.blob import BlobCreateSpecSchema
from mwdb.schema.config import (
ConfigCreateRequestSchema,
ConfigItemAndRelationsResponseSchema,
ConfigItemResponseSchema,
ConfigLegacyCreateRequestSchema,
ConfigListResponseSchema,
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 @@ -11,6 +11,7 @@
from mwdb.schema.file import (
FileCreateRequestSchema,
FileDownloadTokenResponseSchema,
FileItemAndRelationsResponseSchema,
FileItemResponseSchema,
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)
39 changes: 33 additions & 6 deletions mwdb/schema/object.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,18 @@ 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 +115,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 +131,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.