1

is there a way I can create a function to prevent my unique key values from changing, or raise an error if there is an attempt to change unique keys values? i am writing an update function for a database to update entries, but i want to make it so if the "unique key values " are in the payload to be updated it will raise an error

mydict = {
'name': 'cameron', # natural keys
'age': '29',       #also natural key
'city': 'boston',
}
  • In what context? What have you tried? – Andrew Ryan Oct 04 '22 at 14:59
  • 4
    What is your definition of natural key? – Mechanic Pig Oct 04 '22 at 15:01
  • 1
    Instead of using a `dict` (which is generally intended to be mutable), consider a different data structure that better fits your problem statement. Maybe a `class` where `name` and `age` are `property`s whose `setter` function raises an exception? – 0x5453 Oct 04 '22 at 15:02
  • sorry for the poor explanation, a natural keys as i understand would be the values are unique. the dictionary is an entry for a database and i need the values on the first two keys to be unique per entry. – Cameron Bogucki Oct 04 '22 at 15:04
  • for better explanation i am writing an update function for a database to update entries, but i want to make it so if the "natural keys" are in the payload to be updated it will raise an error – Cameron Bogucki Oct 04 '22 at 15:07
  • 2
    @CameronBogucki: The preferred method is, to put additional information into the question via, since comments are more volatile. – guidot Oct 04 '22 at 15:10
  • 1
    if "natural keys" aren't included in the payload, how do you know which database entry to update? – Tim Oct 04 '22 at 15:50

2 Answers2

2

Looking at existing database designs, they typically only allow updating entire database entries, the dict in your case, rather than only a part of the entry.

An update function would typically look like:

natural_keys = ['name', 'age']
database: Dict[tuple, dict]: ...

def update(new_entry: dict):
    keys = tuple(new_entry[k] for k in natural_keys)
    if keys in database:  # update existing entries
       database[keys] = new_entry
    else:  # raise error if there are no entries with these natural keys
       raise ValueError(f"there are no entries with natural keys {keys}")

This way you will never really change the natural keys, only updating existing entries with the same natural keys.

Of course, you will need an insert function to insert new entries with new natural keys.

So there is never really the problem of preventing user from changing natural keys.

Alternatively

For detailed controls, you can create a class and control property setting via property setter

For example,

class MyDict:
    def __init__(self, name, age, city):
        self._name = name
        self._age = age
        self.city = city

    @property
    def name(self):
        return self._name

    @name.setter
    def name(self, value):
        raise ValueError("can't set name")

    @property
    def age(self):
        return self._age

    @age.setter
    def age(self, value):
        raise ValueError("can't set age")

So you can define

my_dict = MyDict(name='cameron', age=29, city='boston')

You can set city

my_dict.city = 'new york'

But you will get ValueError when you try to set name and age.

In more advanced usage, you can use descriptors to handle general attribute setting

import logging


class PreventSetting:

    def __set_name__(self, owner, name):
        self.public_name = name
        self.private_name = '_' + name

    def __get__(self, obj, objtype=None):
        value = getattr(obj, self.private_name)
        logging.info('Accessing %r giving %r', self.public_name, value)
        return value

    def __set__(self, obj, value):
        # if defining for 1st time
        if not hasattr(obj, self.private_name):
            logging.info('Updating %r to %r', self.public_name, value)
            setattr(obj, self.private_name, value)
        else:
            # raise error for setting value after 1st time
            raise ValueError(f"can't set {self.public_name}")


class MyDict:
    name = PreventSetting()  # First descriptor instance
    age = PreventSetting()  # Second descriptor instance

    def __init__(self, name, age, city):
        self.name = name  # Calls the first descriptor __set__
        self.age = age  # Calls the second descriptor __set__
        self.city = city

Tim
  • 3,178
  • 1
  • 13
  • 26
2

A simple example will be as follows:

class MyDict(dict):
    NATURAL_KEYS = ["name", "age"]

    def __setitem__(self, key, value):
        if key in self.NATURAL_KEYS:
            raise KeyError(f"My natural key `{key}`'s value is prevented from changing.")

        super().__setitem__(key, value)

enter image description here

You can read more info here: https://realpython.com/inherit-python-dict/#creating-dictionary-like-classes-in-python.

Ivan Sidaruk
  • 435
  • 3
  • 13