classification
Title: Should uuid.UUID() accept another UUID() instance?
Type: enhancement Stage: resolved
Components: Versions:
process
Status: closed Resolution: rejected
Dependencies: Superseder:
Assigned To: Nosy List: eric.araujo, iritkatriel, mjpieters, serhiy.storchaka, ztane
Priority: normal Keywords:

Created on 2017-11-22 10:52 by mjpieters, last changed 2020-12-19 00:23 by iritkatriel. This issue is now closed.

Messages (6)
msg306719 - (view) Author: Martijn Pieters (mjpieters) * Date: 2017-11-22 10:52
When someone accidentally passes in an existing uuid.UUID() instance into uuid.UUID(), an attribute error is thrown because it is not a hex string:

>>> import uuid
>>> value = uuid.uuid4()
>>> uuid.UUID(value)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "/Users/mjpieters/Development/Library/buildout.python/parts/opt/lib/python2.7/uuid.py", line 133, in __init__
    hex = hex.replace('urn:', '').replace('uuid:', '')
AttributeError: 'UUID' object has no attribute 'replace'

This happened in the Stack Overflow question at https://stackoverflow.com/q/47429929/100297, because the code there didn't take into account that some database drivers may already have mapped the PostgreSQL UUID column to a Python uuid.UUID() object.

The fix could be as simple as:

    if hex is not None:
        if isinstance(hex, uuid.UUID):
            int = hex.int
        else:
            hex = hex.replace('urn:', '').replace('uuid:', '')
            hex = hex.strip('{}').replace('-', '')
            if len(hex) != 32:
                raise ValueError('badly formed hexadecimal UUID string')
            int = int_(hex, 16)

Or we could add a uuid=None keyword argument, and use 

    if hex is not None:
        if isinstance(hex, uuid.UUID):
            uuid = hex
        else:
            hex = hex.replace('urn:', '').replace('uuid:', '')
            hex = hex.strip('{}').replace('-', '')
            if len(hex) != 32:
                raise ValueError('badly formed hexadecimal UUID string')
            int = int_(hex, 16)
    if uuid is not None:
        int = uuid.int
msg306722 - (view) Author: Antti Haapala (ztane) * Date: 2017-11-22 11:12
I've been hit by this too, in similar contexts, and several times. It is really annoying that it is easier to coerce an UUID or UUID-string to a string than to coerce to a UUID. Usually when the copy semantics are clear and the class is plain old data, Python lets you execute the constructor with an instance of the same class:

    >>> bytes(bytes())
    b''
    >>> bytearray(bytearray())
    bytearray(b'')
    >>> int(int())
    0
    >>> complex(complex())
    0j
    >>> tuple(tuple())
    ()

I don't to see why this shouldn't be true with UUID as well.
msg306728 - (view) Author: Serhiy Storchaka (serhiy.storchaka) * (Python committer) Date: 2017-11-22 13:35
Not always the constructor accept an instance of the same class. I'm sure that this is not true in the majority of cases.

The constructor of int accepts an instance of int and the constructor of tuple accepts an instance of tuple because the constructor of int accepts an arbitrary real number, and the constructor of tuple accepts an arbitrary iterable, and int and tuple are a real number and an iterable correspondingly. There is no reason to forbid accepting an instance of the same class in these cases.

In contrary, the UUID constructor accepts a hexadecimal string, but UUID itself is not a hexadecimal string. Similarly the range constructor doesn't accept a range instance, and the file constructor doesn't accept a file instance.

>>> range(range(3))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: 'range' object cannot be interpreted as an integer
>>> io.FileIO(io.FileIO('/dev/null'))
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
TypeError: expected str, bytes or os.PathLike object, not _io.FileIO

For converting an existing UUID instance to UUID you can can first convert it to str:

    newvalue = uuid.UUID(str(oldvalue))

Or better avoid the conversion at all.

    if isisnstance(oldvalue, uuid.UUID):
        newvalue = oldvalue
    else:
        newvalue = uuid.UUID(oldvalue)
msg306909 - (view) Author: Éric Araujo (eric.araujo) * (Python committer) Date: 2017-11-24 17:36
I don’t see a downside in accepting the feature request here.

Maybe ask on python-ideas to get more feedback?
msg306910 - (view) Author: Serhiy Storchaka (serhiy.storchaka) * (Python committer) Date: 2017-11-24 17:56
Growing the size of the code and the documentation, complicating the mental model, slowing down all other cases, the risk of introducing bugs. This is the cost that we should pay for adding a new feature. The benefit of adding a feature should be larger than this cost. I'm not sure this is a case. But if you think it is worth, you can add it.

Note that by adding this feature you can open a can of worms and will need to add support of time(time), date(date), namedtuple(namedtuple), etc for the same reason, because some database drivers already do mapping for you.
msg377949 - (view) Author: Irit Katriel (iritkatriel) * (Python committer) Date: 2020-10-04 15:42
I agree with Serhiy that there needs to be a reason for adding this to a class constructor.

In the case of int, this is to implement conversion - it's not accepting int, it's accepting any number (it would be weird of int(3.4) worked and int(3) didn't).  Similarly, complex constructor accepts numbers.

In the case of tuple, bytes, bytearray - the constructor accepts any iterable, again in order to implement type conversion.


So it's not random - these constructors accept objects of the same type because they accept params of a more generic type, in order to provide type conversion functionality.

What functionality would we gain from increasing the API surface of the UUID constructor as you suggest?
History
Date User Action Args
2021-07-22 07:07:02serhiy.storchakalinkissue44706 superseder
2020-12-19 00:23:12iritkatrielsetstatus: open -> closed
resolution: rejected
type: enhancement
stage: resolved
2020-10-04 15:42:55iritkatrielsetnosy: + iritkatriel
messages: + msg377949
2017-11-24 17:56:06serhiy.storchakasetmessages: + msg306910
2017-11-24 17:36:45eric.araujosetnosy: + eric.araujo
messages: + msg306909
2017-11-22 13:35:50serhiy.storchakasetnosy: + serhiy.storchaka
messages: + msg306728
2017-11-22 11:12:55ztanesetnosy: + ztane
messages: + msg306722
2017-11-22 10:52:36mjpieterscreate