diff --git a/glare/objects/base.py b/glare/objects/base.py index b3cdafa..d7fbad0 100644 --- a/glare/objects/base.py +++ b/glare/objects/base.py @@ -87,20 +87,14 @@ class BaseArtifact(base.VersionedObject): nullable=False, sortable=True, description="Artifact status."), 'created_at': Field(fields.DateTimeField, system=True, - filter_ops=[attribute.FILTER_GT, - attribute.FILTER_LT], nullable=False, sortable=True, description="Datetime when artifact has " "been created."), 'updated_at': Field(fields.DateTimeField, system=True, - filter_ops=[attribute.FILTER_GT, - attribute.FILTER_LT], nullable=False, sortable=True, description="Datetime when artifact has " "been updated last time."), 'activated_at': Field(fields.DateTimeField, system=True, - filter_ops=[attribute.FILTER_GT, - attribute.FILTER_LT], required_on_activate=False, sortable=True, description="Datetime when artifact has became " "active."), @@ -121,20 +115,15 @@ class BaseArtifact(base.VersionedObject): description="List of tags added to Artifact."), 'metadata': DictField(fields.String, required_on_activate=False, element_validators=[validators.MinStrLen(1)], - filter_ops=(attribute.FILTER_EQ, - attribute.FILTER_IN, - attribute.FILTER_NEQ), description="Key-value dict with useful " "information about an artifact."), 'visibility': Field(fields.StringField, default='private', - nullable=False, filter_ops=(attribute.FILTER_EQ,), - sortable=True, + nullable=False, sortable=True, description="Artifact visibility that defines " "if artifact can be available to " "other users."), 'version': Field(glare_fields.VersionField, required_on_activate=False, - default=DEFAULT_ARTIFACT_VERSION, - filter_ops=attribute.FILTERS, nullable=False, + default=DEFAULT_ARTIFACT_VERSION, nullable=False, sortable=True, validators=[validators.Version()], description="Artifact version(semver).") } @@ -769,8 +758,7 @@ class BaseArtifact(base.VersionedObject): :param field_name: blob or blob dict field name :return: maximum blob size in bytes """ - return getattr(cls.fields[field_name], 'max_blob_size', - attribute.BlobAttribute.DEFAULT_MAX_BLOB_SIZE) + return getattr(cls.fields[field_name], 'max_blob_size') @classmethod def validate_upload_allowed(cls, af, field_name, blob_key=None): diff --git a/glare/objects/meta/attribute.py b/glare/objects/meta/attribute.py index 783973b..1697116 100644 --- a/glare/objects/meta/attribute.py +++ b/glare/objects/meta/attribute.py @@ -24,6 +24,8 @@ FILTERS = ( FILTER_EQ, FILTER_NEQ, FILTER_IN, FILTER_GT, FILTER_GTE, FILTER_LT, FILTER_LTE) = ('eq', 'neq', 'in', 'gt', 'gte', 'lt', 'lte') +DEFAULT_MAX_BLOB_SIZE = 10485760 + class Attribute(object): def __init__(self, field_class, mutable=False, required_on_activate=True, @@ -56,19 +58,41 @@ class Attribute(object): self.system = system self.sortable = sortable - if field_class is glare_fields.BlobField: - if filter_ops: - raise exc.IncorrectArtifactType( - "Cannot specify filters for blobs") - self.filter_ops = [] + try: + default_ops = self.get_allowed_filter_ops(self.element_type) + except AttributeError: + default_ops = self.get_allowed_filter_ops(field_class) + + if filter_ops is None: + self.filter_ops = default_ops else: - self.filter_ops = [FILTER_EQ, FILTER_NEQ, FILTER_IN] \ - if filter_ops is None else filter_ops + for op in filter_ops: + if op not in default_ops: + raise exc.IncorrectArtifactType( + "Incorrect filter operator '%s'. " + "Only %s are allowed" % (op, ', '.join(default_ops))) + self.filter_ops = filter_ops self.field_attrs = ['mutable', 'required_on_activate', 'system', 'sortable', 'filter_ops', 'description'] self.description = description + @staticmethod + def get_allowed_filter_ops(field): + if field in (fields.StringField, fields.String, + glare_fields.ArtifactStatusField): + return [FILTER_EQ, FILTER_NEQ, FILTER_IN] + elif field in (fields.IntegerField, fields.Integer, fields.FloatField, + fields.Float, glare_fields.VersionField): + return FILTERS + elif field in (fields.FlexibleBooleanField, fields.FlexibleBoolean, + glare_fields.Link, glare_fields.LinkFieldType): + return [FILTER_EQ, FILTER_NEQ] + elif field in (glare_fields.BlobField, glare_fields.BlobFieldType): + return [] + elif field is fields.DateTimeField: + return [FILTER_LT, FILTER_GT] + def get_default_validators(self): default = [] if issubclass(self.field_class, fields.StringField): @@ -125,19 +149,20 @@ class Attribute(object): class CompoundAttribute(Attribute): def __init__(self, field_class, element_type, element_validators=None, **kwargs): - super(CompoundAttribute, self).__init__(field_class, **kwargs) - if self.sortable: - raise exc.IncorrectArtifactType("'sortable' must be False for " - "compound type.") - if element_type is None: raise exc.IncorrectArtifactType("'element_type' must be set for " "compound type.") self.element_type = element_type + + super(CompoundAttribute, self).__init__(field_class, **kwargs) + self.vo_attrs.append('element_type') self.field_attrs.append('element_type') self.element_validators = element_validators or [] + if self.sortable: + raise exc.IncorrectArtifactType("'sortable' must be False for " + "compound type.") def get_element_validators(self): default_vals = [] @@ -178,8 +203,6 @@ class DictAttribute(CompoundAttribute): super(DictAttribute, self).__init__(glare_fields.Dict, element_type, **kwargs) self.validators.append(val_lib.MaxDictSize(max_size)) - if element_type is glare_fields.BlobFieldType: - self.filter_ops = [] def get_default_validators(self): default_vals = [] @@ -192,8 +215,6 @@ class DictAttribute(CompoundAttribute): class BlobAttribute(Attribute): - DEFAULT_MAX_BLOB_SIZE = 10485760 - def __init__(self, max_blob_size=DEFAULT_MAX_BLOB_SIZE, **kwargs): super(BlobAttribute, self).__init__( field_class=glare_fields.BlobField, **kwargs) @@ -202,8 +223,7 @@ class BlobAttribute(Attribute): class BlobDictAttribute(DictAttribute): - def __init__(self, max_blob_size=BlobAttribute.DEFAULT_MAX_BLOB_SIZE, - **kwargs): + def __init__(self, max_blob_size=DEFAULT_MAX_BLOB_SIZE, **kwargs): super(BlobDictAttribute, self).__init__( element_type=glare_fields.BlobFieldType, **kwargs) self.max_blob_size = int(max_blob_size) diff --git a/glare/tests/functional/test_sample_artifact.py b/glare/tests/functional/test_sample_artifact.py index 556d274..6e47cd7 100644 --- a/glare/tests/functional/test_sample_artifact.py +++ b/glare/tests/functional/test_sample_artifact.py @@ -210,18 +210,21 @@ class TestList(base.TestArtifact): result = sort_results(self.get(url=url)['sample_artifact']) self.assertEqual(art_list[5:], result) - # visibility=neq:private url = '/sample_artifact?visibility=neq:private' - self.get(url=url, status=400) + result = sort_results(self.get(url=url)['sample_artifact']) + self.assertEqual(art_list[5:], result) url = '/sample_artifact?visibility=neq:public' - self.get(url=url, status=400) + result = sort_results(self.get(url=url)['sample_artifact']) + self.assertEqual(art_list[:5], result) url = '/sample_artifact?visibility=blabla' - self.get(url=url, status=200) + result = sort_results(self.get(url=url)['sample_artifact']) + self.assertEqual([], result) url = '/sample_artifact?visibility=neq:blabla' - self.get(url=url, status=400) + result = sort_results(self.get(url=url)['sample_artifact']) + self.assertEqual(art_list, result) url = '/sample_artifact?name=eq:name0&name=name1&tags=tag1' result = self.get(url=url)['sample_artifact'] @@ -367,7 +370,7 @@ class TestList(base.TestArtifact): result = sort_results(self.get(url=url)['sample_artifact']) self.assertEqual([], result) - for op in ['gt', 'gte', 'lt', 'lte', 'neq']: + for op in ['gt', 'gte', 'lt', 'lte']: url = '/sample_artifact?dict_of_str.pr3=%s:val3' % op self.get(url=url, status=400) diff --git a/glare/tests/functional/test_schemas.py b/glare/tests/functional/test_schemas.py index 1a2435b..b959e2f 100644 --- a/glare/tests/functional/test_schemas.py +++ b/glare/tests/functional/test_schemas.py @@ -21,8 +21,7 @@ from glare.tests.functional import base fixture_base_props = { u'activated_at': { u'description': u'Datetime when artifact has became active.', - u'filter_ops': [u'gt', - u'lt'], + u'filter_ops': [u'lt', u'gt'], u'format': u'date-time', u'glareType': u'DateTime', u'readOnly': True, @@ -32,8 +31,7 @@ fixture_base_props = { u'null']}, u'created_at': { u'description': u'Datetime when artifact has been created.', - u'filter_ops': [u'gt', - u'lt'], + u'filter_ops': [u'lt', u'gt'], u'format': u'date-time', u'glareType': u'DateTime', u'readOnly': True, @@ -63,9 +61,7 @@ fixture_base_props = { u'default': {}, u'description': u'Key-value dict with useful information ' u'about an artifact.', - u'filter_ops': [u'eq', - u'in', - u'neq'], + u'filter_ops': [u'eq', u'neq', u'in'], u'glareType': u'StringDict', u'maxProperties': 255, u'required_on_activate': False, @@ -114,8 +110,7 @@ fixture_base_props = { u'unique': True}, u'updated_at': { u'description': u'Datetime when artifact has been updated last time.', - u'filter_ops': [u'gt', - u'lt'], + u'filter_ops': [u'lt', u'gt'], u'format': u'date-time', u'glareType': u'DateTime', u'readOnly': True, @@ -141,7 +136,7 @@ fixture_base_props = { u'description': u'Artifact visibility that defines if ' u'artifact can be available to other ' u'users.', - u'filter_ops': [u'eq'], + u'filter_ops': [u'eq', u'neq', u'in'], u'glareType': u'String', u'maxLength': 255, u'sortable': True, @@ -201,15 +196,13 @@ fixtures = { u'type': [u'string', u'null']}, u'link1': {u'filter_ops': [u'eq', - u'neq', - u'in'], + u'neq'], u'glareType': u'Link', u'required_on_activate': False, u'type': [u'string', u'null']}, u'link2': {u'filter_ops': [u'eq', - u'neq', - u'in'], + u'neq'], u'glareType': u'Link', u'required_on_activate': False, u'type': [u'string', @@ -423,11 +416,7 @@ fixtures = { u'null']}, u'str1': {u'filter_ops': [u'eq', u'neq', - u'in', - u'gt', - u'gte', - u'lt', - u'lte'], + u'in'], u'glareType': u'String', u'maxLength': 255, u'required_on_activate': False, @@ -436,11 +425,7 @@ fixtures = { u'null']}, u'string_mutable': {u'filter_ops': [u'eq', u'neq', - u'in', - u'gt', - u'gte', - u'lt', - u'lte'], + u'in'], u'glareType': u'String', u'maxLength': 255, u'mutable': True, @@ -450,11 +435,7 @@ fixtures = { u'string_required': { u'filter_ops': [u'eq', u'neq', - u'in', - u'gt', - u'gte', - u'lt', - u'lte'], + u'in'], u'glareType': u'String', u'maxLength': 255, u'type': [u'string', @@ -466,11 +447,7 @@ fixtures = { None], u'filter_ops': [u'eq', u'neq', - u'in', - u'gt', - u'gte', - u'lt', - u'lte'], + u'in'], u'glareType': u'String', u'maxLength': 10, u'required_on_activate': False, @@ -566,8 +543,7 @@ fixtures = { u'description': u'List of package dependencies for ' u'this package.', u'filter_ops': [u'eq', - u'neq', - u'in'], + u'neq'], u'glareType': u'LinkList', u'items': {u'type': u'string'}, u'maxItems': 255, @@ -751,7 +727,11 @@ fixtures = { u'description': u'Minimal disk space required to boot image.', u'filter_ops': [u'eq', u'neq', - u'in'], + u'in', + u'gt', + u'gte', + u'lt', + u'lte'], u'glareType': u'Integer', u'minimum': 0, u'required_on_activate': False, @@ -760,7 +740,11 @@ fixtures = { u'description': u'Minimal RAM required to boot image.', u'filter_ops': [u'eq', u'neq', - u'in'], + u'in', + u'gt', + u'gte', + u'lt', + u'lte'], u'glareType': u'Integer', u'minimum': 0, u'required_on_activate': False, @@ -828,8 +812,7 @@ fixtures = { u'that can be used with current ' u'template.', u'filter_ops': [u'eq', - u'neq', - u'in'], + u'neq'], u'glareType': u'LinkDict', u'maxProperties': 255, u'mutable': True, diff --git a/glare/tests/sample_artifact.py b/glare/tests/sample_artifact.py index bbe2ff0..1c9a96a 100644 --- a/glare/tests/sample_artifact.py +++ b/glare/tests/sample_artifact.py @@ -32,10 +32,10 @@ class SampleArtifact(base_artifact.BaseArtifact): VERSION = '1.0' fields = { - 'blob': Blob(required_on_activate=False, mutable=True, filter_ops=[], + 'blob': Blob(required_on_activate=False, mutable=True, description="I am Blob"), 'small_blob': Blob(max_blob_size=10, required_on_activate=False, - mutable=True, filter_ops=[]), + mutable=True), 'link1': Field(glare_fields.Link, required_on_activate=False), 'link2': Field(glare_fields.Link, @@ -50,24 +50,19 @@ class SampleArtifact(base_artifact.BaseArtifact): default=False), 'int1': Field(fields.IntegerField, required_on_activate=False, - sortable=True, - filter_ops=attribute.FILTERS), + sortable=True), 'int2': Field(fields.IntegerField, sortable=True, - required_on_activate=False, - filter_ops=attribute.FILTERS), + required_on_activate=False), 'float1': Field(fields.FloatField, sortable=True, - required_on_activate=False, - filter_ops=attribute.FILTERS), + required_on_activate=False), 'float2': Field(fields.FloatField, sortable=True, - required_on_activate=False, - filter_ops=attribute.FILTERS), + required_on_activate=False), 'str1': Field(fields.StringField, sortable=True, - required_on_activate=False, - filter_ops=attribute.FILTERS), + required_on_activate=False), 'list_of_str': List(fields.String, required_on_activate=False, filter_ops=(attribute.FILTER_EQ, @@ -97,14 +92,11 @@ class SampleArtifact(base_artifact.BaseArtifact): validators.MaxDictKeyLen(1000)]), 'string_mutable': Field(fields.StringField, required_on_activate=False, - mutable=True, - filter_ops=attribute.FILTERS), + mutable=True), 'string_required': Field(fields.StringField, - required_on_activate=True, - filter_ops=attribute.FILTERS), + required_on_activate=True), 'string_validators': Field(fields.StringField, required_on_activate=False, - filter_ops=attribute.FILTERS, validators=[ validators.AllowedValues( ['aa', 'bb', 'c' * 11]), @@ -112,7 +104,6 @@ class SampleArtifact(base_artifact.BaseArtifact): ]), 'int_validators': Field(fields.IntegerField, required_on_activate=False, - filter_ops=attribute.FILTERS, validators=[ validators.MinNumberSize(10), validators.MaxNumberSize(20)