image: Add support for metadef property operations
Like placement's resource provider inventory API and keystone's old extensions API, this is yet another example of an API that returns an object with property names as keys and all other attributes as the values, e.g. we see: { "os_admin_user": { ... }, ... } rather than: [ { "name": "os_admin_user", ... }, ,,, ] Change-Id: I8e2ae8545cfaf32ced6d086a0921732f16282216 Co-authored-by: KIM SOJUNG <thwjd2717@gmail.com> Co-authored-by: GA EUM KIM <rkdms7220@naver.com> Co-authored-by: EunYoung Kim <lilac94.kim@gmail.com> Co-authored-by: hyemin Choi <dropmoon3523@gmail.com> Co-authored-by: Antonia Gaete <antoniagaete@osuosl.org> Co-authored-by: YeJun, Jung <yejun614@naver.com>
This commit is contained in:
parent
8b92fed92a
commit
c2600c35b7
@ -84,6 +84,16 @@ Metadef Resource Type Operations
|
||||
create_metadef_resource_type_association,
|
||||
delete_metadef_resource_type_association
|
||||
|
||||
|
||||
Metadef Property Operations
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
.. autoclass:: openstack.image.v2._proxy.Proxy
|
||||
:noindex:
|
||||
:members: create_metadef_property, update_metadef_property,
|
||||
delete_metadef_property, get_metadef_property
|
||||
|
||||
|
||||
Helpers
|
||||
^^^^^^^
|
||||
|
||||
|
@ -20,6 +20,7 @@ Image v2 Resources
|
||||
v2/metadef_namespace
|
||||
v2/metadef_object
|
||||
v2/metadef_resource_type
|
||||
v2/metadef_property
|
||||
v2/metadef_schema
|
||||
v2/task
|
||||
v2/service_info
|
||||
|
13
doc/source/user/resources/image/v2/metadef_property.rst
Normal file
13
doc/source/user/resources/image/v2/metadef_property.rst
Normal file
@ -0,0 +1,13 @@
|
||||
openstack.image.v2.metadef_property
|
||||
===================================
|
||||
|
||||
.. automodule:: openstack.image.v2.metadef_property
|
||||
|
||||
The MetadefProperty Class
|
||||
-------------------------
|
||||
|
||||
The ``MetadefProperty`` class inherits from
|
||||
:class:`~openstack.resource.Resource`.
|
||||
|
||||
.. autoclass:: openstack.image.v2.metadef_property.MetadefProperty
|
||||
:members:
|
@ -20,6 +20,7 @@ from openstack.image.v2 import image as _image
|
||||
from openstack.image.v2 import member as _member
|
||||
from openstack.image.v2 import metadef_namespace as _metadef_namespace
|
||||
from openstack.image.v2 import metadef_object as _metadef_object
|
||||
from openstack.image.v2 import metadef_property as _metadef_property
|
||||
from openstack.image.v2 import metadef_resource_type as _metadef_resource_type
|
||||
from openstack.image.v2 import metadef_schema as _metadef_schema
|
||||
from openstack.image.v2 import schema as _schema
|
||||
@ -1397,6 +1398,132 @@ class Proxy(proxy.Proxy):
|
||||
**query,
|
||||
)
|
||||
|
||||
# ====== METADEF PROPERTY ======
|
||||
def create_metadef_property(self, metadef_namespace, **attrs):
|
||||
"""Create a metadef property
|
||||
|
||||
:param metadef_namespace: The value can be either the name of metadef
|
||||
namespace or an
|
||||
:class:`~openstack.image.v2.metadef_property.MetadefNamespace`
|
||||
instance
|
||||
:param attrs: The attributes to create on the metadef property
|
||||
represented by ``metadef_property``.
|
||||
|
||||
:returns: The created metadef property
|
||||
:rtype: :class:`~openstack.image.v2.metadef_property.MetadefProperty`
|
||||
"""
|
||||
namespace_name = resource.Resource._get_id(metadef_namespace)
|
||||
return self._create(
|
||||
_metadef_property.MetadefProperty,
|
||||
namespace_name=namespace_name,
|
||||
**attrs,
|
||||
)
|
||||
|
||||
def update_metadef_property(
|
||||
self, metadef_property, metadef_namespace, **attrs
|
||||
):
|
||||
"""Update a metadef property
|
||||
|
||||
:param metadef_property: The value can be either the name of metadef
|
||||
property or an
|
||||
:class:`~openstack.image.v2.metadef_property.MetadefProperty`
|
||||
instance.
|
||||
:param metadef_namespace: The value can be either the name of metadef
|
||||
namespace or an
|
||||
:class:`~openstack.image.v2.metadef_namespace.MetadefNamespace`
|
||||
instance
|
||||
:param attrs: The attributes to update on the metadef property
|
||||
represented by ``metadef_property``.
|
||||
|
||||
:returns: The updated metadef property
|
||||
:rtype: :class:`~openstack.image.v2.metadef_property.MetadefProperty`
|
||||
"""
|
||||
namespace_name = resource.Resource._get_id(metadef_namespace)
|
||||
metadef_property = resource.Resource._get_id(metadef_property)
|
||||
return self._update(
|
||||
_metadef_property.MetadefProperty,
|
||||
metadef_property,
|
||||
namespace_name=namespace_name,
|
||||
**attrs,
|
||||
)
|
||||
|
||||
def delete_metadef_property(
|
||||
self, metadef_property, metadef_namespace, ignore_missing=True
|
||||
):
|
||||
"""Delete a metadef property
|
||||
|
||||
:param metadef_property: The value can be either the name of metadef
|
||||
property or an
|
||||
:class:`~openstack.image.v2.metadef_property.MetadefProperty`
|
||||
instance
|
||||
:param metadef_namespace: The value can be either the name of metadef
|
||||
namespace or an
|
||||
:class:`~openstack.image.v2.metadef_namespace.MetadefNamespace`
|
||||
instance
|
||||
:param bool ignore_missing: When set to
|
||||
``False`` :class:`~openstack.exceptions.ResourceNotFound` will be
|
||||
raised when the instance does not exist. When set to ``True``,
|
||||
no exception will be set when attempting to delete a nonexistent
|
||||
instance.
|
||||
|
||||
:returns: ``None``
|
||||
"""
|
||||
namespace_name = resource.Resource._get_id(metadef_namespace)
|
||||
metadef_property = resource.Resource._get_id(metadef_property)
|
||||
return self._delete(
|
||||
_metadef_property.MetadefProperty,
|
||||
metadef_property,
|
||||
namespace_name=namespace_name,
|
||||
ignore_missing=ignore_missing,
|
||||
)
|
||||
|
||||
def metadef_properties(self, metadef_namespace, **query):
|
||||
"""Return a generator of metadef properties
|
||||
|
||||
:param metadef_namespace: The value can be either the name of metadef
|
||||
namespace or an
|
||||
:class:`~openstack.image.v2.metadef_namespace.MetadefNamespace`
|
||||
instance
|
||||
:param kwargs query: Optional query parameters to be sent to limit
|
||||
the resources being returned.
|
||||
|
||||
:returns: A generator of property objects
|
||||
"""
|
||||
namespace_name = resource.Resource._get_id(metadef_namespace)
|
||||
return self._list(
|
||||
_metadef_property.MetadefProperty,
|
||||
requires_id=False,
|
||||
namespace_name=namespace_name,
|
||||
**query,
|
||||
)
|
||||
|
||||
def get_metadef_property(
|
||||
self, metadef_property, metadef_namespace, **query
|
||||
):
|
||||
"""Get a single metadef property
|
||||
|
||||
:param metadef_property: The value can be either the name of metadef
|
||||
property or an
|
||||
:class:`~openstack.image.v2.metadef_property.MetadefProperty`
|
||||
instance.
|
||||
:param metadef_namespace: The value can be either the name of metadef
|
||||
namespace or an
|
||||
:class:`~openstack.image.v2.metadef_namespace.MetadefNamespace`
|
||||
instance
|
||||
|
||||
:returns: One
|
||||
:class:`~~openstack.image.v2.metadef_property.MetadefProperty`
|
||||
:raises: :class:`~openstack.exceptions.ResourceNotFound` when no
|
||||
resource can be found.
|
||||
"""
|
||||
namespace_name = resource.Resource._get_id(metadef_namespace)
|
||||
return self._get(
|
||||
_metadef_property.MetadefProperty,
|
||||
metadef_property,
|
||||
namespace_name=namespace_name,
|
||||
**query,
|
||||
)
|
||||
|
||||
# ====== SCHEMAS ======
|
||||
def get_images_schema(self):
|
||||
"""Get images schema
|
||||
|
179
openstack/image/v2/metadef_property.py
Normal file
179
openstack/image/v2/metadef_property.py
Normal file
@ -0,0 +1,179 @@
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from openstack import exceptions
|
||||
from openstack import resource
|
||||
|
||||
|
||||
class MetadefProperty(resource.Resource):
|
||||
base_path = '/metadefs/namespaces/%(namespace_name)s/properties'
|
||||
|
||||
# capabilities
|
||||
allow_create = True
|
||||
allow_fetch = True
|
||||
allow_commit = True
|
||||
allow_delete = True
|
||||
allow_list = True
|
||||
|
||||
#: An identifier (a name) for the namespace.
|
||||
namespace_name = resource.URI('namespace_name')
|
||||
#: The name of the property
|
||||
name = resource.Body('name', alternate_id=True)
|
||||
#: The property type.
|
||||
type = resource.Body('type')
|
||||
#: The title of the property.
|
||||
title = resource.Body('title')
|
||||
#: Detailed description of the property.
|
||||
description = resource.Body('description')
|
||||
#: A list of operator
|
||||
operators = resource.Body('operators', type=list)
|
||||
#: Default property description.
|
||||
default = resource.Body('default')
|
||||
#: Indicates whether this is a read-only property.
|
||||
is_readonly = resource.Body('readonly', type=bool)
|
||||
#: Minimum allowed numerical value.
|
||||
minimum = resource.Body('minimum', type=int)
|
||||
#: Maximum allowed numerical value.
|
||||
maximum = resource.Body('maximum', type=int)
|
||||
#: Enumerated list of property values.
|
||||
enum = resource.Body('enum', type=list)
|
||||
#: A regular expression
|
||||
#: (`ECMA 262 <http://www.ecma-international.org/publications/standards/Ecma-262.htm>`_)
|
||||
#: that a string value must match.
|
||||
pattern = resource.Body('pattern')
|
||||
#: Minimum allowed string length.
|
||||
min_length = resource.Body('minLength', type=int, minimum=0, default=0)
|
||||
#: Maximum allowed string length.
|
||||
max_length = resource.Body('maxLength', type=int, minimum=0)
|
||||
#: Schema for the items in an array.
|
||||
items = resource.Body('items', type=dict)
|
||||
#: Indicates whether all values in the array must be distinct.
|
||||
require_unique_items = resource.Body(
|
||||
'uniqueItems', type=bool, default=False
|
||||
)
|
||||
#: Minimum length of an array.
|
||||
min_items = resource.Body('minItems', type=int, minimum=0, default=0)
|
||||
#: Maximum length of an array.
|
||||
max_items = resource.Body('maxItems', type=int, minimum=0)
|
||||
#: Describes extra items, if you use tuple typing. If the value of
|
||||
#: ``items`` is an array (tuple typing) and the instance is longer than
|
||||
#: the list of schemas in ``items``, the additional items are described by
|
||||
#: the schema in this property. If this value is ``false``, the instance
|
||||
#: cannot be longer than the list of schemas in ``items``. If this value
|
||||
#: is ``true``, that is equivalent to the empty schema (anything goes).
|
||||
allow_additional_items = resource.Body('additionalItems', type=bool)
|
||||
|
||||
# TODO(stephenfin): It would be nicer if we could do this in Resource
|
||||
# itself since the logic is also found elsewhere (e.g.
|
||||
# openstack.identity.v2.extension.Extension) but that code is a bit of a
|
||||
# rat's nest right now and needs a spring clean
|
||||
@classmethod
|
||||
def list(
|
||||
cls,
|
||||
session,
|
||||
paginated=True,
|
||||
base_path=None,
|
||||
allow_unknown_params=False,
|
||||
*,
|
||||
microversion=None,
|
||||
**params,
|
||||
):
|
||||
"""This method is a generator which yields resource objects.
|
||||
|
||||
A re-implementation of :meth:`~openstack.resource.Resource.list` that
|
||||
handles glance's single, unpaginated list implementation.
|
||||
|
||||
Refer to :meth:`~openstack.resource.Resource.list` for full
|
||||
documentation including parameter, exception and return type
|
||||
documentation.
|
||||
"""
|
||||
session = cls._get_session(session)
|
||||
|
||||
if microversion is None:
|
||||
microversion = cls._get_microversion(session, action='list')
|
||||
|
||||
if base_path is None:
|
||||
base_path = cls.base_path
|
||||
|
||||
# There is no server-side filtering, only client-side
|
||||
client_filters = {}
|
||||
# Gather query parameters which are not supported by the server
|
||||
for k, v in params.items():
|
||||
if (
|
||||
# Known attr
|
||||
hasattr(cls, k)
|
||||
# Is real attr property
|
||||
and isinstance(getattr(cls, k), resource.Body)
|
||||
# not included in the query_params
|
||||
and k not in cls._query_mapping._mapping.keys()
|
||||
):
|
||||
client_filters[k] = v
|
||||
|
||||
uri = base_path % params
|
||||
uri_params = {}
|
||||
|
||||
for k, v in params.items():
|
||||
# We need to gather URI parts to set them on the resource later
|
||||
if hasattr(cls, k) and isinstance(getattr(cls, k), resource.URI):
|
||||
uri_params[k] = v
|
||||
|
||||
def _dict_filter(f, d):
|
||||
"""Dict param based filtering"""
|
||||
if not d:
|
||||
return False
|
||||
for key in f.keys():
|
||||
if isinstance(f[key], dict):
|
||||
if not _dict_filter(f[key], d.get(key, None)):
|
||||
return False
|
||||
elif d.get(key, None) != f[key]:
|
||||
return False
|
||||
return True
|
||||
|
||||
response = session.get(
|
||||
uri,
|
||||
headers={"Accept": "application/json"},
|
||||
params={},
|
||||
microversion=microversion,
|
||||
)
|
||||
exceptions.raise_from_response(response)
|
||||
data = response.json()
|
||||
|
||||
for name, property_data in data['properties'].items():
|
||||
property = {
|
||||
'name': name,
|
||||
**property_data,
|
||||
**uri_params,
|
||||
}
|
||||
value = cls.existing(
|
||||
microversion=microversion,
|
||||
connection=session._get_connection(),
|
||||
**property,
|
||||
)
|
||||
|
||||
filters_matched = True
|
||||
# Iterate over client filters and return only if matching
|
||||
for key in client_filters.keys():
|
||||
if isinstance(client_filters[key], dict):
|
||||
if not _dict_filter(
|
||||
client_filters[key],
|
||||
value.get(key, None),
|
||||
):
|
||||
filters_matched = False
|
||||
break
|
||||
elif value.get(key, None) != client_filters[key]:
|
||||
filters_matched = False
|
||||
break
|
||||
|
||||
if filters_matched:
|
||||
yield value
|
||||
|
||||
return None
|
129
openstack/tests/functional/image/v2/test_metadef_property.py
Normal file
129
openstack/tests/functional/image/v2/test_metadef_property.py
Normal file
@ -0,0 +1,129 @@
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
import random
|
||||
import string
|
||||
|
||||
from openstack.image.v2 import metadef_namespace as _metadef_namespace
|
||||
from openstack.image.v2 import metadef_property as _metadef_property
|
||||
from openstack.tests.functional.image.v2 import base
|
||||
|
||||
|
||||
class TestMetadefProperty(base.BaseImageTest):
|
||||
def setUp(self):
|
||||
super().setUp()
|
||||
|
||||
# there's a limit on namespace length
|
||||
namespace = 'test_' + ''.join(
|
||||
random.choice(string.ascii_lowercase) for _ in range(75)
|
||||
)
|
||||
self.metadef_namespace = self.conn.image.create_metadef_namespace(
|
||||
namespace=namespace,
|
||||
)
|
||||
self.assertIsInstance(
|
||||
self.metadef_namespace,
|
||||
_metadef_namespace.MetadefNamespace,
|
||||
)
|
||||
self.assertEqual(namespace, self.metadef_namespace.namespace)
|
||||
|
||||
# there's a limit on property length
|
||||
property_name = 'test_' + ''.join(
|
||||
random.choice(string.ascii_lowercase) for _ in range(75)
|
||||
)
|
||||
self.attrs = {
|
||||
'name': property_name,
|
||||
'title': property_name,
|
||||
'type': 'string',
|
||||
'description': 'Web Server port',
|
||||
'enum': ["80", "443"],
|
||||
}
|
||||
self.metadef_property = self.conn.image.create_metadef_property(
|
||||
self.metadef_namespace.namespace, **self.attrs
|
||||
)
|
||||
self.assertIsInstance(
|
||||
self.metadef_property, _metadef_property.MetadefProperty
|
||||
)
|
||||
self.assertEqual(self.attrs['name'], self.metadef_property.name)
|
||||
self.assertEqual(self.attrs['title'], self.metadef_property.title)
|
||||
self.assertEqual(self.attrs['type'], self.metadef_property.type)
|
||||
self.assertEqual(
|
||||
self.attrs['description'], self.metadef_property.description
|
||||
)
|
||||
self.assertEqual(self.attrs['enum'], self.metadef_property.enum)
|
||||
|
||||
def tearDown(self):
|
||||
# we do this in tearDown rather than via 'addCleanup' since we want to
|
||||
# wait for the deletion of the resource to ensure it completes
|
||||
self.conn.image.delete_metadef_property(
|
||||
self.metadef_property, self.metadef_namespace
|
||||
)
|
||||
self.conn.image.delete_metadef_namespace(self.metadef_namespace)
|
||||
self.conn.image.wait_for_delete(self.metadef_namespace)
|
||||
|
||||
super().tearDown()
|
||||
|
||||
def test_metadef_property(self):
|
||||
# get metadef property
|
||||
metadef_property = self.conn.image.get_metadef_property(
|
||||
self.metadef_property, self.metadef_namespace
|
||||
)
|
||||
self.assertIsNotNone(metadef_property)
|
||||
self.assertIsInstance(
|
||||
metadef_property, _metadef_property.MetadefProperty
|
||||
)
|
||||
self.assertEqual(self.attrs['name'], metadef_property.name)
|
||||
self.assertEqual(self.attrs['title'], metadef_property.title)
|
||||
self.assertEqual(self.attrs['type'], metadef_property.type)
|
||||
self.assertEqual(
|
||||
self.attrs['description'], metadef_property.description
|
||||
)
|
||||
self.assertEqual(self.attrs['enum'], metadef_property.enum)
|
||||
|
||||
# (no find_metadef_property method)
|
||||
|
||||
# list
|
||||
metadef_properties = list(
|
||||
self.conn.image.metadef_properties(self.metadef_namespace)
|
||||
)
|
||||
self.assertIsNotNone(metadef_properties)
|
||||
self.assertIsInstance(
|
||||
metadef_properties[0], _metadef_property.MetadefProperty
|
||||
)
|
||||
|
||||
# update
|
||||
self.attrs['title'] = ''.join(
|
||||
random.choice(string.ascii_lowercase) for _ in range(10)
|
||||
)
|
||||
self.attrs['description'] = ''.join(
|
||||
random.choice(string.ascii_lowercase) for _ in range(10)
|
||||
)
|
||||
metadef_property = self.conn.image.update_metadef_property(
|
||||
self.metadef_property,
|
||||
self.metadef_namespace.namespace,
|
||||
**self.attrs
|
||||
)
|
||||
self.assertIsNotNone(metadef_property)
|
||||
self.assertIsInstance(
|
||||
metadef_property,
|
||||
_metadef_property.MetadefProperty,
|
||||
)
|
||||
metadef_property = self.conn.image.get_metadef_property(
|
||||
self.metadef_property.name, self.metadef_namespace
|
||||
)
|
||||
self.assertEqual(
|
||||
self.attrs['title'],
|
||||
metadef_property.title,
|
||||
)
|
||||
self.assertEqual(
|
||||
self.attrs['description'],
|
||||
metadef_property.description,
|
||||
)
|
82
openstack/tests/unit/image/v2/test_metadef_property.py
Normal file
82
openstack/tests/unit/image/v2/test_metadef_property.py
Normal file
@ -0,0 +1,82 @@
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
# not use this file except in compliance with the License. You may obtain
|
||||
# a copy of the License at
|
||||
#
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
#
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
|
||||
from openstack.image.v2 import metadef_property
|
||||
from openstack.tests.unit import base
|
||||
|
||||
EXAMPLE = {
|
||||
'namespace_name': 'CIM::StorageAllocationSettingData',
|
||||
'name': 'Access',
|
||||
'type': 'string',
|
||||
'title': 'Access',
|
||||
'description': (
|
||||
'Access describes whether the allocated storage extent is '
|
||||
'1 (readable), 2 (writeable), or 3 (both).'
|
||||
),
|
||||
'operators': ['<or>'],
|
||||
'default': None,
|
||||
'readonly': None,
|
||||
'minimum': None,
|
||||
'maximum': None,
|
||||
'enum': [
|
||||
'Unknown',
|
||||
'Readable',
|
||||
'Writeable',
|
||||
'Read/Write Supported',
|
||||
'DMTF Reserved',
|
||||
],
|
||||
'pattern': None,
|
||||
'min_length': 0,
|
||||
'max_length': None,
|
||||
'items': None,
|
||||
'unique_items': False,
|
||||
'min_items': 0,
|
||||
'max_items': None,
|
||||
'additional_items': None,
|
||||
}
|
||||
|
||||
|
||||
class TestMetadefProperty(base.TestCase):
|
||||
def test_basic(self):
|
||||
sot = metadef_property.MetadefProperty()
|
||||
self.assertEqual(
|
||||
'/metadefs/namespaces/%(namespace_name)s/properties', sot.base_path
|
||||
)
|
||||
self.assertTrue(sot.allow_create)
|
||||
self.assertTrue(sot.allow_fetch)
|
||||
self.assertTrue(sot.allow_commit)
|
||||
self.assertTrue(sot.allow_delete)
|
||||
self.assertTrue(sot.allow_list)
|
||||
|
||||
def test_make_it(self):
|
||||
sot = metadef_property.MetadefProperty(**EXAMPLE)
|
||||
self.assertEqual(EXAMPLE['namespace_name'], sot.namespace_name)
|
||||
self.assertEqual(EXAMPLE['name'], sot.name)
|
||||
self.assertEqual(EXAMPLE['type'], sot.type)
|
||||
self.assertEqual(EXAMPLE['title'], sot.title)
|
||||
self.assertEqual(EXAMPLE['description'], sot.description)
|
||||
self.assertListEqual(EXAMPLE['operators'], sot.operators)
|
||||
self.assertEqual(EXAMPLE['default'], sot.default)
|
||||
self.assertEqual(EXAMPLE['readonly'], sot.is_readonly)
|
||||
self.assertEqual(EXAMPLE['minimum'], sot.minimum)
|
||||
self.assertEqual(EXAMPLE['maximum'], sot.maximum)
|
||||
self.assertListEqual(EXAMPLE['enum'], sot.enum)
|
||||
self.assertEqual(EXAMPLE['pattern'], sot.pattern)
|
||||
self.assertEqual(EXAMPLE['min_length'], sot.min_length)
|
||||
self.assertEqual(EXAMPLE['max_length'], sot.max_length)
|
||||
self.assertEqual(EXAMPLE['items'], sot.items)
|
||||
self.assertEqual(EXAMPLE['unique_items'], sot.require_unique_items)
|
||||
self.assertEqual(EXAMPLE['min_items'], sot.min_items)
|
||||
self.assertEqual(EXAMPLE['max_items'], sot.max_items)
|
||||
self.assertEqual(
|
||||
EXAMPLE['additional_items'], sot.allow_additional_items
|
||||
)
|
@ -0,0 +1,4 @@
|
||||
---
|
||||
features:
|
||||
- |
|
||||
Added support for the ``MetadefProperty`` Image resource.
|
Loading…
Reference in New Issue
Block a user