AttributeError: 'UUID' object has no attribute 'replace' when using backend-agnostic GUID type
Asked Answered
O

8

25

I want to have a primary key id with type uuid in a Postgresql database using SQLAlchemy 1.1.5, connecting to the database with the pg8000 adapter. I used the Backend-agnostic GUID Type recipe from the SQLAlchemy documentation.

When I want to insert into the database, I get the following error

  File ".../guid.py", line ???, in process_result_value
    return uuid.UUID(value)
  File "/usr/lib/python2.7/uuid.py", line 131, in __init__
    hex = hex.replace('urn:', '').replace('uuid:', '')
AttributeError: 'UUID' object has no attribute 'replace'

my model looks like this

from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy import Column, Integer, String
from guid import GUID
import uuid

base = declarative_base()

class Item(base):
    __tablename__ = 'item'

    id = Column(GUID(), default=uuid.uuid4, nullable=False, unique=True, primary_key=True)
    name = Column(String)
    description = Column(String)

    def __repr__(self):
        return "<Item(name='%s', description='%s')>" % (self.name, self.description)

My resource or controller looks like this

data = req.params
item = Item(name=data['name'], description=data['description'])

self.session.add(item)
self.session.commit()
Ordinance answered 22/11, 2017 at 8:27 Comment(2)
A note: why do you need to use the backend agnostic custom GUID type, though you're using Postgresql. I might be missing something, but SQLAlchemy offers postgresql.UUID as well.Unsung
@IljaEverilä: presumably because their project tries to support more than just postgresql.Radiocarbon
R
17

The pg8000 PostgreSQL database adapter is returning a uuid.UUID() object (see their type mapping documentation, and SQLAlchemy has passed that to the TypeDecorator.process_result_value() method.

The implementation given in the documentation expected a string, however, so this fails:

>>> 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'

The quick work-around is to force the value to be a string anyway:

def process_result_value(self, value, dialect):
    if value is None:
        return value
    else:
        return uuid.UUID(str(value))

or you can test for the type first:

def process_result_value(self, value, dialect):
    if value is None:
        return value
    else:
        if not isinstance(value, uuid.UUID):
            value = uuid.UUID(value)
        return value

I've submited pull request #403 to fix this in the documentation (since merged).

Radiocarbon answered 22/11, 2017 at 8:53 Comment(0)
C
32

This should fix it:

id = Column(GUID(as_uuid=True), ...)

from https://bitbucket.org/zzzeek/sqlalchemy/issues/3323/in-099-uuid-columns-are-broken-with:

"If you want to pass a UUID() object, the as_uuid flag must be set to True."

Crumpler answered 27/10, 2018 at 9:54 Comment(3)
Changing db.Column(UUID, primary_key=True) to db.Column(UUID(as_uuid=True), primary_key=True) resolved my issue.Accuracy
thanks @DavidHariri db.Column(UUID(as_uuid=True) solved my issue tooVulgarian
This is documented as not working on pg8000, which is the driver that the OP is using. as_uuid is great when using psycopg2, but this answer is wrong for this specific question. From the UUID documentation: The UUID type may not be supported on all DBAPIs. It is known to work on psycopg2 and not pg8000.Radiocarbon
R
17

The pg8000 PostgreSQL database adapter is returning a uuid.UUID() object (see their type mapping documentation, and SQLAlchemy has passed that to the TypeDecorator.process_result_value() method.

The implementation given in the documentation expected a string, however, so this fails:

>>> 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'

The quick work-around is to force the value to be a string anyway:

def process_result_value(self, value, dialect):
    if value is None:
        return value
    else:
        return uuid.UUID(str(value))

or you can test for the type first:

def process_result_value(self, value, dialect):
    if value is None:
        return value
    else:
        if not isinstance(value, uuid.UUID):
            value = uuid.UUID(value)
        return value

I've submited pull request #403 to fix this in the documentation (since merged).

Radiocarbon answered 22/11, 2017 at 8:53 Comment(0)
E
3

The actual issue here is that you have default=uuid.uuid4, where postgres expects a string in UUID format. You'll want something like default=lambda: str(uuid.uuid4())

Experiment answered 20/10, 2021 at 17:47 Comment(0)
J
0

This can be fairly frustrating when using UUIDs across a system. Under certain conditions, it might be difficult to control whether a UUID comes in as a string, or as a raw UUID. To work around this, a solution like this might work. I've attached the examples of the doc to make sure everything else still holds true.

# TODO: Set this up such that the normal uuid interface is available as a pass through
import uuid

class UUID(uuid.UUID):

    def __init__(self, hex=None, bytes=None, bytes_le=None, fields=None,
                       int=None, version=None):

        if hex and (issubclass(type(hex), uuid.UUID) or isinstance(hex, uuid.UUID)):
            hex = str(hex)

        super(UUID, self).__init__(hex=hex, bytes=bytes, bytes_le=bytes_le, fields=fields, int=int, version=version)

print(UUID(uuid4())) # Now this works!

print(UUID('{12345678-1234-5678-1234-567812345678}'))
print(UUID('12345678123456781234567812345678'))
print(UUID('urn:uuid:12345678-1234-5678-1234-567812345678'))
print(UUID(bytes=b'\x12\x34\x56\x78' * 4)) # Python 3 requires this to be prefixed with b''. Docs appear to be mainly for Python 2
print(UUID(bytes_le=b'\x78\x56\x34\x12\x34\x12\x78\x56' +
              b'\x12\x34\x56\x78\x12\x34\x56\x78'))
print(UUID(fields=(0x12345678, 0x1234, 0x5678, 0x12, 0x34, 0x567812345678)))
print(UUID(int=0x12345678123456781234567812345678))

Please use this your own discretion, this is just an example.

Jehoash answered 12/6, 2018 at 18:24 Comment(3)
The issubclass(type(hex), uuid.UUID) test is entirely redundant. isinstance(hex, uuid.UUID) is true for instances of subclases of uuid.UUID, without the need to explicitly test for that. Note that None is also not an instance of UUID so the hex and component can also be dropped: if isinstance(hex, uuid.UUID):.Radiocarbon
Note that the Python core developers have rejected the idea of accepting UUID instances as input for UUID().Radiocarbon
Last but not least, rather than use hex = str(hex), consider using if isinstance(hex, uuid.UUID): hex, int = None, hex.int, as a faster alternative that doesn't require formatting the .int attribute as hex then splitting it out into a larger string, only to then be parsed out into an integer again..Radiocarbon
A
0

I was encountering the same problem and searched for two days before finding out that the code preceeding the error contained an error:

it was getting the following error, referring to an error in python and sqlalchemy

offertemodule_1  |   File "/opt/packages/database/models.py", line 79, in process_bind_param
offertemodule_1  |     return "%.32x" % uuid.UUID(value).int
offertemodule_1  |   File "/usr/local/lib/python3.7/uuid.py", line 157, in __init__
offertemodule_1  |     hex = hex.replace('urn:', '').replace('uuid:', '')
offertemodule_1  | sqlalchemy.exc.StatementError: (builtins.AttributeError) 'builtin_function_or_method' object has no attribute 'replace'
offertemodule_1  | [SQL: SELECT product.id AS product_id, product.supplier_id AS product_supplier_id, product.supplier_product_url AS product_supplier_product_url, product.supplier_product_id AS product_supplier_product_id, product.title AS product_title, product.description AS product_description, product.brand AS product_brand, product.product_line AS product_product_line, product.buying_price_ex_vat AS product_buying_price_ex_vat, product.buying_vat AS product_buying_vat, product.vat_pct AS product_vat_pct, product.advise_price AS product_advise_price, product.estimated_days_leadtime AS product_estimated_days_leadtime, product.product_category AS product_product_category, product.nestedproducts AS product_nestedproducts, product.atttibutes_meta AS product_atttibutes_meta, product.statistics_meta AS product_statistics_meta, product.active AS product_active, product.created AS product_created, product.created_by AS product_created_by, product.modified AS product_modified, product.modified_by AS product_modified_by
offertemodule_1  | FROM product
offertemodule_1  | WHERE product.id = %(id_1)s]
offertemodule_1  | [parameters: [immutabledict({})]]

But it turned out that a process before this was sending the wrong object to my database function

@ns_products.route('/<string:product_id>')
@api.response(404, 'product not found.')
class Details(Resource):
    @api.marshal_with(product)
    @api.doc(security='jwt')
    @jwt_required
    def get(self, product_id):
        '''Returns a single product instance'''
        return Product.get(id)`

Should be (note 'product_id')

@ns_products.route('/<string:product_id>')
@api.response(404, 'product not found.')
class Details(Resource):
    @api.marshal_with(product)
    @api.doc(security='jwt')
    @jwt_required
    def get(self, product_id):
        '''Returns a single product instance'''
        return Product.get(product_id)

Hence the uuid-string stored in product_id was actually a native python object 'id'. Hence it tried to process the string to a uuid and it failed.

Activist answered 8/12, 2020 at 19:33 Comment(0)
G
-2

I had this issue affecting my ORM without using forms. I was running psycopg2. The fix for me was to:

sudo pip install psycopg2-binary

After restarting apache, I haven't see the error again as of psycopg2-binary version 2.7.5+

Glassco answered 30/5, 2019 at 3:37 Comment(2)
That's an entirely different issue, however.Radiocarbon
I was getting the error listed above, and this solution worked for my code. How is that entirely different?Glassco
D
-2

Solution is very easy if you used models.py like in django you need to change your models field for example.

from partner_trx_id = models.UUIDField(null=True, db_column="partner_trx_id") to partner_trx_id = models.CharField(max_length=35,null=True, db_column="partner_trx_id")

Django query can not read UUIDfield so we changed to Charfield.

If you want to learn about more you can visit to https://code.djangoproject.com/ticket/30526.

Demented answered 17/6, 2022 at 1:14 Comment(0)
B
-2

AttributeError: 'UUID' object has no attribute 'get_hex'""

Solution In python2 using - uuid.uuid4().get_hex() Above is not work well in python3. Must use - uuid.uuid4().hex remove get_hex().It`s works well.

Butta answered 18/7, 2022 at 11:38 Comment(1)
The API for UUID instances in Python 2 and Python3 is the same, modulo any additional methods in Python3. Neither implementation has a get_hex method.Blackmore

© 2022 - 2024 — McMap. All rights reserved.