1
0
mirror of https://github.com/openSUSE/osc.git synced 2025-02-02 17:56:15 +01:00

Limit model attributes to predefined fields by forbidding creating new attributes on fly

This commit is contained in:
Daniel Mach 2024-01-03 21:21:38 +01:00
parent 3c733387af
commit 587c094f61
3 changed files with 21 additions and 14 deletions

View File

@ -126,7 +126,9 @@ HttpHeader = NewType("HttpHeader", Tuple[str, str])
class OscOptions(BaseModel):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.extra_fields = {}
self._allow_new_attributes = True
self._extra_fields = {}
self._allow_new_attributes = False
# compat function with the config dict
def _get_field_name(self, name):
@ -145,7 +147,7 @@ class OscOptions(BaseModel):
field_name = self._get_field_name(name)
if field_name is None and not hasattr(self, name):
return self.extra_fields[name]
return self._extra_fields[name]
field_name = field_name or name
try:
@ -158,7 +160,7 @@ class OscOptions(BaseModel):
field_name = self._get_field_name(name)
if field_name is None and not hasattr(self, name):
self.extra_fields[name] = value
self._extra_fields[name] = value
return
field_name = field_name or name

View File

@ -280,6 +280,13 @@ class ModelMeta(type):
class BaseModel(metaclass=ModelMeta):
__fields__: Dict[str, Field]
def __setattr__(self, name, value):
if getattr(self, "_allow_new_attributes", True) or hasattr(self.__class__, name) or hasattr(self, name):
# allow setting properties - test if they exist in the class
# also allow setting existing attributes that were previously initialized via __dict__
return super().__setattr__(name, value)
raise AttributeError(f"Setting attribute '{self.__class__.__name__}.{name}' is not allowed")
def __init__(self, **kwargs):
self._values = {}
self._parent = kwargs.pop("_parent", None)
@ -307,6 +314,8 @@ class BaseModel(metaclass=ModelMeta):
for name, field in self.__fields__.items():
field.validate_type(getattr(self, name))
self._allow_new_attributes = False
def dict(self, exclude_unset=False):
result = {}
for name, field in self.__fields__.items():

View File

@ -116,6 +116,9 @@ class TestExampleConfig(unittest.TestCase):
def tearDown(self):
shutil.rmtree(self.tmpdir)
def test_invalid_attribute(self):
self.assertRaises(AttributeError, setattr, self.config, "new_attribute", "123")
def test_apiurl(self):
self.assertEqual(self.config["apiurl"], "https://api.opensuse.org")
@ -407,26 +410,19 @@ class TestExampleConfig(unittest.TestCase):
def test_extra_fields(self):
self.assertEqual(self.config["plugin-option"], "plugin-general-option")
self.assertEqual(self.config.extra_fields, {"plugin-option": "plugin-general-option"})
# write to an existing attribute instead of extra_fields
self.config.attrib = 123
self.assertEqual(self.config["attrib"], 123)
self.config["attrib"] = 456
self.assertEqual(self.config["attrib"], 456)
self.assertEqual(self.config.extra_fields, {"plugin-option": "plugin-general-option"})
self.assertEqual(self.config._extra_fields, {"plugin-option": "plugin-general-option"})
self.config["new-option"] = "value"
self.assertEqual(self.config["new-option"], "value")
self.assertEqual(self.config.extra_fields, {"plugin-option": "plugin-general-option", "new-option": "value"})
self.assertEqual(self.config._extra_fields, {"plugin-option": "plugin-general-option", "new-option": "value"})
host_options = self.config["api_host_options"][self.config["apiurl"]]
self.assertEqual(host_options["plugin-option"], "plugin-host-option")
self.assertEqual(host_options.extra_fields, {"plugin-option": "plugin-host-option"})
self.assertEqual(host_options._extra_fields, {"plugin-option": "plugin-host-option"})
host_options["new-option"] = "value"
self.assertEqual(host_options["new-option"], "value")
self.assertEqual(host_options.extra_fields, {"plugin-option": "plugin-host-option", "new-option": "value"})
self.assertEqual(host_options._extra_fields, {"plugin-option": "plugin-host-option", "new-option": "value"})
def test_apiurl_aliases(self):
expected = {"https://api.opensuse.org": "https://api.opensuse.org", "osc": "https://api.opensuse.org"}