'How to make sure a subclass has the same initialiser as the baseclass?

Basically, I am trying to define a subclass of string which conforms with the RFC UUID scheme. An object of this class can only exist if it conforms to the 8-4-4-4-12 hex string, else does not. So I need to check it during initialisation, but not sure how to account for all possible initialisation arguments of str.

This is how my code looks like:

import uuid

class UniversalID(str):
    def __init__(self, val:str):
        super().__init__()
        try:
            uuid.UUID(hex=val)
            self=val
        except ValueError as inv_str:
            logging.error(msg=f'Invalid id queried {val}')
            raise inv_str

sample= uuid.uuid1().hex
isinstance(UniversalID(sample), str) # Shows true, as expected

But not sure if this is the right approach, as there may be other arguments of str initialiser that I am not taking care of.

The question can be generalised to, if I want to modify the __init__ method with some validation check in the subclass, do I need to have total access to the initialiser in the base class, to make sure it accepts the same arguments and processes the same way? Worse still, do I have to, like, copy paste the code?



Solution 1:[1]

As I said in a comment you have to set the value of immutable types, like strings, in their __new__() method. I couldn't find a canonical source or example for you — I know about it from some books I read long ago, so decided just to make up one for you (based on your code):

import logging
import uuid


class UniversalID(str):
    # You don't really need to define this, because it's what would happen anyway.
    def __new__(cls, *args, **kwargs):
        return super().__new__(cls, *args, **kwargs)

    def __init__(self, *args, **kwargs):
        try:
            uuid.UUID(hex=self)
        except ValueError as inv_str:
            logging.error(msg=f'Invalid id queried {self}')
            raise inv_str


if __name__ == '__main__':

    sample1 = uuid.uuid1().hex
    try:
        isinstance(UniversalID(sample1), str)
    except ValueError as exc:
        print(f'ERROR: {exc} {sample1=!r}')
    else:
        print(f'{sample1=!r} is a valid hex uuid.')

    print()
    sample2 = 'not a hex uuid'
    try:
        isinstance(UniversalID(sample2), str)
    except ValueError as exc:
        print(f'ERROR: {exc} {sample2=!r}')
    else:
        print(f'{sample2=!r} is a valid hex uuid.')

Solution 2:[2]

You might want to create an instance that inherits from uuid.UUID and make the checks you want to the argument passed into your own constructor before you pass it to the super.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1
Solution 2 naoki914