22

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()
Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
The Oracle
  • 2,373
  • 3
  • 26
  • 44
  • 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`](http://docs.sqlalchemy.org/en/latest/dialects/postgresql.html#sqlalchemy.dialects.postgresql.UUID) as well. – Ilja Everilä Nov 22 '17 at 08:51
  • 1
    @IljaEverilä: presumably because their project tries to support more than just postgresql. – Martijn Pieters Nov 22 '17 at 09:05

8 Answers8

28

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."

jrc
  • 20,354
  • 10
  • 69
  • 64
  • 6
    Changing `db.Column(UUID, primary_key=True)` to `db.Column(UUID(as_uuid=True), primary_key=True)` resolved my issue. – David Hariri Dec 30 '18 at 00:29
  • 1
    thanks @DavidHariri db.Column(UUID(as_uuid=True) solved my issue too – afiah Oct 14 '19 at 12:18
  • 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](https://docs.sqlalchemy.org/en/13/dialects/postgresql.html#sqlalchemy.dialects.postgresql.UUID): *The UUID type may not be supported on all DBAPIs. It is known to work on psycopg2 __and not pg8000.__* – Martijn Pieters Oct 15 '19 at 11:27
16

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).

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
2

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())

Arya
  • 1,382
  • 2
  • 15
  • 36
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.

Serguei Fedorov
  • 7,763
  • 9
  • 63
  • 94
  • 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):`. – Martijn Pieters Oct 15 '19 at 11:39
  • Note that the [Python core developers have rejected the idea of accepting `UUID` instances as input for `UUID()`](https://bugs.python.org/issue32112). – Martijn Pieters Oct 15 '19 at 11:43
  • 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.. – Martijn Pieters Oct 15 '19 at 11:46
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.

Dr. T.
  • 11
  • 5
-1

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.

cigien
  • 57,834
  • 11
  • 73
  • 112
Muhammadalive
  • 648
  • 5
  • 5
-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+

Luke Dupin
  • 2,275
  • 23
  • 30
-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.

  • 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. – snakecharmerb Jul 18 '22 at 11:43