SQLAlchemy-Utils UUIDType `TypeError: string argument without an encoding`
Checklist
- [X] The bug is reproducible against the latest release or
master. - [X] There are no similar issues or pull requests to fix it yet.
Describe the bug
Since I've changed company_uuid from str to UUID type I get following error once I want to get detailed view or edit record:
File "C:\mSpaceLock\mSpaceLock-www\env\Lib\site-packages\sqladmin\helpers.py", line 234, in object_identifier_values
values.append(get_column_python_type(pk)(part))
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
TypeError: string argument without an encoding
SQLAlchemy model:
class Company(Base):
company_uuid: Mapped[UUID] = mapped_column(
UUIDType(binary=False),
primary_key=True,
index=True,
default=uuid4,
)
company_name: Mapped[str] = mapped_column(String, unique=True, nullable=False)
address: Mapped[Optional[str]]
city: Mapped[Optional[str]]
country: Mapped[Optional[str]] = mapped_column(CountryType)
tax_id: Mapped[Optional[str]] = mapped_column(default=None)
contact_details: Mapped[Optional[str]] = mapped_column(Text, default=None)
is_active: Mapped[bool] = mapped_column(default=True)
location: Mapped[List["Location"]] = relationship(
Location,
back_populates="company",
)
user: Mapped[List["User"]] = relationship(User, back_populates="company")
def __str__(self) -> str:
return self.company_name
from admin.views.registry import register_view
from db.models.companies import Company
from sqladmin import ModelView
@register_view
class CompanyAdmin(ModelView, model=Company):
column_list = [
Company.company_name,
Company.address,
Company.city,
Company.country,
Company.tax_id,
Company.is_active,
Company.contact_details,
]
form_columns = [
Company.company_name,
Company.address,
Company.city,
Company.country,
Company.tax_id,
Company.is_active,
Company.contact_details,
]
name = "Company"
name_plural = "Companies"
icon = "fa-solid fa-building"
At first I thought that error is caused because of binary format UUID and added UUIDType(binary=False) but it didn't change anything also adding to main:
sys.stdin.reconfigure(encoding="utf-8")
sys.stdout.reconfigure(encoding="utf-8")
or PYTHONIOENCODING = 'uft-8' to environmental variables didn't help a bit.
Steps to reproduce the bug
-
Create model with primary key
Mapped[UUID] = mapped_column(UUIDType(binary=False), primary_key=True, index=True, default=uuid4) -
Initialize database with initialdata.
-
Create ModelView for this table
-
Access http://127.0.0.1:8000/admin/{modelname}/list --> all looks ok
-
Try to
VieworEditto show detailed view or form -->TypeError: string argument without an encoding
Expected behavior
No response
Actual behavior
No response
Debugging material
INFO: 127.0.0.1:50349 - "GET /company/details/9d97ad6c-6c62-4ea1-8e65-53b861b90601 HTTP/1.1" 500 Internal Server Error
ERROR: Exception in ASGI application
Traceback (most recent call last):
File "C:\mSpaceLock\mSpaceLock-www\env\Lib\site-packages\uvicorn\protocols\http\h11_impl.py", line 408, in run_asgi
result = await app( # type: ignore[func-returns-value]
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\mSpaceLock\mSpaceLock-www\env\Lib\site-packages\uvicorn\middleware\proxy_headers.py", line 84, in __call__
return await self.app(scope, receive, send)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\mSpaceLock\mSpaceLock-www\env\Lib\site-packages\fastapi\applications.py", line 292, in __call__
await super().__call__(scope, receive, send)
File "C:\mSpaceLock\mSpaceLock-www\env\Lib\site-packages\starlette\applications.py", line 122, in __call__
await self.middleware_stack(scope, receive, send)
File "C:\mSpaceLock\mSpaceLock-www\env\Lib\site-packages\starlette\middleware\errors.py", line 184, in __call__
raise exc
File "C:\mSpaceLock\mSpaceLock-www\env\Lib\site-packages\starlette\middleware\errors.py", line 162, in __call__
await self.app(scope, receive, _send)
File "C:\mSpaceLock\mSpaceLock-www\env\Lib\site-packages\starlette\middleware\exceptions.py", line 79, in __call__
raise exc
File "C:\mSpaceLock\mSpaceLock-www\env\Lib\site-packages\starlette\middleware\exceptions.py", line 68, in __call__
await self.app(scope, receive, sender)
File "C:\mSpaceLock\mSpaceLock-www\env\Lib\site-packages\fastapi\middleware\asyncexitstack.py", line 20, in __call__
raise e
File "C:\mSpaceLock\mSpaceLock-www\env\Lib\site-packages\fastapi\middleware\asyncexitstack.py", line 17, in __call__
await self.app(scope, receive, send)
File "C:\mSpaceLock\mSpaceLock-www\env\Lib\site-packages\starlette\routing.py", line 718, in __call__
await route.handle(scope, receive, send)
File "C:\mSpaceLock\mSpaceLock-www\env\Lib\site-packages\starlette\routing.py", line 443, in handle
await self.app(scope, receive, send)
File "C:\mSpaceLock\mSpaceLock-www\env\Lib\site-packages\starlette\applications.py", line 122, in __call__
await self.middleware_stack(scope, receive, send)
File "C:\mSpaceLock\mSpaceLock-www\env\Lib\site-packages\starlette\middleware\errors.py", line 184, in __call__
raise exc
File "C:\mSpaceLock\mSpaceLock-www\env\Lib\site-packages\starlette\middleware\errors.py", line 162, in __call__
await self.app(scope, receive, _send)
File "C:\mSpaceLock\mSpaceLock-www\env\Lib\site-packages\starlette\middleware\sessions.py", line 86, in __call__
await self.app(scope, receive, send_wrapper)
File "C:\mSpaceLock\mSpaceLock-www\env\Lib\site-packages\starlette\middleware\exceptions.py", line 79, in __call__
raise exc
File "C:\mSpaceLock\mSpaceLock-www\env\Lib\site-packages\starlette\middleware\exceptions.py", line 68, in __call__
await self.app(scope, receive, sender)
File "C:\mSpaceLock\mSpaceLock-www\env\Lib\site-packages\starlette\routing.py", line 718, in __call__
await route.handle(scope, receive, send)
File "C:\mSpaceLock\mSpaceLock-www\env\Lib\site-packages\starlette\routing.py", line 276, in handle
await self.app(scope, receive, send)
File "C:\mSpaceLock\mSpaceLock-www\env\Lib\site-packages\starlette\routing.py", line 66, in app
response = await func(request)
^^^^^^^^^^^^^^^^^^^
File "C:\mSpaceLock\mSpaceLock-www\env\Lib\site-packages\sqladmin\authentication.py", line 66, in wrapper_decorator
return await func(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\mSpaceLock\mSpaceLock-www\env\Lib\site-packages\sqladmin\application.py", line 454, in details
model = await model_view.get_object_for_details(request.path_params["pk"])
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\mSpaceLock\mSpaceLock-www\env\Lib\site-packages\sqladmin\models.py", line 834, in get_object_for_details
stmt = self._stmt_by_identifier(value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\mSpaceLock\mSpaceLock-www\env\Lib\site-packages\sqladmin\models.py", line 856, in _stmt_by_identifier
values = object_identifier_values(identifier, self.model)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "C:\mSpaceLock\mSpaceLock-www\env\Lib\site-packages\sqladmin\helpers.py", line 234, in object_identifier_values
values.append(get_column_python_type(pk)(part))
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
TypeError: string argument without an encoding
Environment
OS Windows 10 Python 3.11.5 pip.txt
fastapi 0.103.1 pydantic 2.3.0 pydantic_core 2.6.3 sqladmin 0.15.0 SQLAlchemy 2.0.21 SQLAlchemy-Utils 0.41.1
uvicorn 0.23.2 WTForms 3.0.1
Additional context
No response
Where is the UUIDType coming from? I think it should be from sqlalchemy import Uuid probably.
It's comming from SQLAlchemy-Utils
from sqlalchemy_utils import CountryType, UUIDType
I found in the change log that this tool should be working with SQLAdmin:
Add support for UUIDType from sqlalchemy_utils by @okapies in https://github.com/aminalaee/sqladmin/pull/183
Also there is such code in sqladmin/sqladmin /forms.py
@converts(
"sqlalchemy.dialects.postgresql.base.UUID",
"sqlalchemy.sql.sqltypes.UUID",
"sqlalchemy.sql.sqltypes.Uuid",
"sqlalchemy_utils.types.uuid.UUIDType",
)
def conv_uuid(
self, model: type, prop: ColumnProperty, kwargs: Dict[str, Any]
) -> UnboundField:
kwargs.setdefault("validators", [])
kwargs["validators"].append(validators.UUID())
return StringField(**kwargs)
I think that this problem raised due to issue on sqlalchemy_utils package such as
In helper.py file of sqladmin there is method that check wheather impl attribute of column.type or not
def get_column_python_type(column: Column) -> type:
try:
if hasattr(column.type, "impl"):
return column.type.impl.python_type
return column.type.python_type
except NotImplementedError:
return str
On using sqlalchemy_utils for uuid handling ,Here we used UUIDType where binary=False is passed
Mapped[UUID] = mapped_column(UUIDType(binary=False), primary_key=True, index=True, default=uuid4)
whether, we passed binary= False or binary=True , UUIDType class of (python_type attribute ) always return bytes.
on this code
values.append(get_column_python_type(pk)(part))
Bytes class is called with uuid as argument ,where
get_column_python_type(pk) return Bytes class
part is uuid value
so that, these error occur on using with UUIDTypes.
But, Is there any way to bypass these scenario from sqladmin side.
I think you are correct in the debugging, but I think a good step is to compare https://sqlalchemy-utils.readthedocs.io/en/latest/_modules/sqlalchemy_utils/types/uuid.html#UUIDType with sqlalchemy UUID type because I don't see that one raising any issues.
My first understanding was that the sqlalchemy_utils UUID type should return different impl if binary status changes.
I think you are correct in the debugging, but I think a good step is to compare https://sqlalchemy-utils.readthedocs.io/en/latest/_modules/sqlalchemy_utils/types/uuid.html#UUIDType with sqlalchemy UUID type because I don't see that one raising any issues. My first understanding was that the sqlalchemy_utils UUID type should return different
implif binary status changes.
Yes, I was thinking so, impl value should be different based on binary status.
I was expecting , it should be like
from pydantic import UUID4
class UUIDType(ScalarCoercible, types.TypeDecorator):
"""
Stores a UUID in the database natively when it can and falls back to
a BINARY(16) or a CHAR(32) when it can't.
::
from sqlalchemy_utils import UUIDType
import uuid
class User(Base):
__tablename__ = 'user'
# Pass `binary=False` to fallback to CHAR instead of BINARY
id = sa.Column(
UUIDType(binary=False),
primary_key=True,
default=uuid.uuid4
)
"""
class UUIDChar(CHAR):
python_type = UUID4 # type:
impl = types.BINARY(16)
python_type = uuid.UUID
cache_ok = True
def __init__(self, binary=True, native=True):
"""
:param binary: Whether to use a BINARY(16) or CHAR(32) fallback.
"""
self.binary = binary
self.native = native
if not self.binary:
self.impl = self.UUIDChar
by changing like this, it works on my computer locally. I take this reference from fastapi_users db sqlalchemy
Or even better it would be in load_dialect_impl which is specifically for this purpose to return impl dynamically. But I'm not really sure if this was changed in sqlalchemy_utils recently or even if it's a bug there.
this wasnot changed in sqlalchemy_utils currently. Once I decide to push the changes and leave it for review. But, problem is that I havenot found pydantic was used on sqlalchemy_utils . currently i leave it by creating issue on sqlqlchemy_utils.
and, what do you think. How this code handle if binary = True in UUIDTypes. means get_column_python_type(pk) return bytes class
values.append(get_column_python_type(pk)(part))
I think these raise same error because for bytes data to decode , we have to call decode method of bytes class.
Why are we returning first the impl.python_type and not python_type directly in the first place?
So, instead of this:
def get_column_python_type(column: Column) -> type:
try:
if hasattr(column.type, "impl"):
return column.type.impl.python_type
return column.type.python_type
except NotImplementedError:
return str
Perhaps doing something like this:
def get_column_python_type(column: Column) -> type:
try:
return column.type.python_type
except NotImplementedError:
if hasattr(column.type, "impl"):
try:
return column.type.impl.python_type
except NotImplementedError:
pass
return str
Hi! I faced with a problem I want to edit or delete an instance of the User model in the admin panel, but I got that error: TypeError: UUID objects are immutable The User model from fastapi_users lib:
import uuid
UUID_ID = uuid.UUID
id: Mapped[UUID_ID]
full traceback:
Traceback (most recent call last):
File "/Users/tailo/Library/Caches/pypoetry/virtualenvs/app-rGgr4MIE-py3.11/lib/python3.11/site-packages/uvicorn/protocols/http/httptools_impl.py", line 435, in run_asgi
result = await app( # type: ignore[func-returns-value]
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/tailo/Library/Caches/pypoetry/virtualenvs/app-rGgr4MIE-py3.11/lib/python3.11/site-packages/uvicorn/middleware/proxy_headers.py", line 78, in __call__
return await self.app(scope, receive, send)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/tailo/Library/Caches/pypoetry/virtualenvs/app-rGgr4MIE-py3.11/lib/python3.11/site-packages/fastapi/applications.py", line 292, in __call__
await super().__call__(scope, receive, send)
File "/Users/tailo/Library/Caches/pypoetry/virtualenvs/app-rGgr4MIE-py3.11/lib/python3.11/site-packages/starlette/applications.py", line 122, in __call__
await self.middleware_stack(scope, receive, send)
File "/Users/tailo/Library/Caches/pypoetry/virtualenvs/app-rGgr4MIE-py3.11/lib/python3.11/site-packages/starlette/middleware/errors.py", line 184, in __call__
raise exc
File "/Users/tailo/Library/Caches/pypoetry/virtualenvs/app-rGgr4MIE-py3.11/lib/python3.11/site-packages/starlette/middleware/errors.py", line 162, in __call__
await self.app(scope, receive, _send)
File "/Users/tailo/Library/Caches/pypoetry/virtualenvs/app-rGgr4MIE-py3.11/lib/python3.11/site-packages/starlette/middleware/base.py", line 109, in __call__
await response(scope, receive, send)
File "/Users/tailo/Library/Caches/pypoetry/virtualenvs/app-rGgr4MIE-py3.11/lib/python3.11/site-packages/starlette/responses.py", line 270, in __call__
async with anyio.create_task_group() as task_group:
File "/Users/tailo/Library/Caches/pypoetry/virtualenvs/app-rGgr4MIE-py3.11/lib/python3.11/site-packages/anyio/_backends/_asyncio.py", line 597, in __aexit__
raise exceptions[0]
File "/Users/tailo/Library/Caches/pypoetry/virtualenvs/app-rGgr4MIE-py3.11/lib/python3.11/site-packages/starlette/responses.py", line 273, in wrap
await func()
File "/Users/tailo/Library/Caches/pypoetry/virtualenvs/app-rGgr4MIE-py3.11/lib/python3.11/site-packages/starlette/middleware/base.py", line 134, in stream_response
return await super().stream_response(send)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/tailo/Library/Caches/pypoetry/virtualenvs/app-rGgr4MIE-py3.11/lib/python3.11/site-packages/starlette/responses.py", line 262, in stream_response
async for chunk in self.body_iterator:
File "/Users/tailo/Library/Caches/pypoetry/virtualenvs/app-rGgr4MIE-py3.11/lib/python3.11/site-packages/starlette/middleware/base.py", line 98, in body_stream
raise app_exc
File "/Users/tailo/Library/Caches/pypoetry/virtualenvs/app-rGgr4MIE-py3.11/lib/python3.11/site-packages/starlette/middleware/base.py", line 70, in coro
await self.app(scope, receive_or_disconnect, send_no_error)
File "/Users/tailo/Library/Caches/pypoetry/virtualenvs/app-rGgr4MIE-py3.11/lib/python3.11/site-packages/prometheus_fastapi_instrumentator/middleware.py", line 169, in __call__
raise exc
File "/Users/tailo/Library/Caches/pypoetry/virtualenvs/app-rGgr4MIE-py3.11/lib/python3.11/site-packages/prometheus_fastapi_instrumentator/middleware.py", line 167, in __call__
await self.app(scope, receive, send_wrapper)
File "/Users/tailo/Library/Caches/pypoetry/virtualenvs/app-rGgr4MIE-py3.11/lib/python3.11/site-packages/starlette/middleware/cors.py", line 91, in __call__
await self.simple_response(scope, receive, send, request_headers=headers)
File "/Users/tailo/Library/Caches/pypoetry/virtualenvs/app-rGgr4MIE-py3.11/lib/python3.11/site-packages/starlette/middleware/cors.py", line 146, in simple_response
await self.app(scope, receive, send)
File "/Users/tailo/Library/Caches/pypoetry/virtualenvs/app-rGgr4MIE-py3.11/lib/python3.11/site-packages/starlette/middleware/exceptions.py", line 79, in __call__
raise exc
File "/Users/tailo/Library/Caches/pypoetry/virtualenvs/app-rGgr4MIE-py3.11/lib/python3.11/site-packages/starlette/middleware/exceptions.py", line 68, in __call__
await self.app(scope, receive, sender)
File "/Users/tailo/Library/Caches/pypoetry/virtualenvs/app-rGgr4MIE-py3.11/lib/python3.11/site-packages/fastapi/middleware/asyncexitstack.py", line 20, in __call__
raise e
File "/Users/tailo/Library/Caches/pypoetry/virtualenvs/app-rGgr4MIE-py3.11/lib/python3.11/site-packages/fastapi/middleware/asyncexitstack.py", line 17, in __call__
await self.app(scope, receive, send)
File "/Users/tailo/Library/Caches/pypoetry/virtualenvs/app-rGgr4MIE-py3.11/lib/python3.11/site-packages/starlette/routing.py", line 718, in __call__
await route.handle(scope, receive, send)
File "/Users/tailo/Library/Caches/pypoetry/virtualenvs/app-rGgr4MIE-py3.11/lib/python3.11/site-packages/starlette/routing.py", line 443, in handle
await self.app(scope, receive, send)
File "/Users/tailo/Library/Caches/pypoetry/virtualenvs/app-rGgr4MIE-py3.11/lib/python3.11/site-packages/starlette/applications.py", line 122, in __call__
await self.middleware_stack(scope, receive, send)
File "/Users/tailo/Library/Caches/pypoetry/virtualenvs/app-rGgr4MIE-py3.11/lib/python3.11/site-packages/starlette/middleware/errors.py", line 184, in __call__
raise exc
File "/Users/tailo/Library/Caches/pypoetry/virtualenvs/app-rGgr4MIE-py3.11/lib/python3.11/site-packages/starlette/middleware/errors.py", line 162, in __call__
await self.app(scope, receive, _send)
File "/Users/tailo/Library/Caches/pypoetry/virtualenvs/app-rGgr4MIE-py3.11/lib/python3.11/site-packages/starlette/middleware/sessions.py", line 86, in __call__
await self.app(scope, receive, send_wrapper)
File "/Users/tailo/Library/Caches/pypoetry/virtualenvs/app-rGgr4MIE-py3.11/lib/python3.11/site-packages/starlette/middleware/exceptions.py", line 79, in __call__
raise exc
File "/Users/tailo/Library/Caches/pypoetry/virtualenvs/app-rGgr4MIE-py3.11/lib/python3.11/site-packages/starlette/middleware/exceptions.py", line 68, in __call__
await self.app(scope, receive, sender)
File "/Users/tailo/Library/Caches/pypoetry/virtualenvs/app-rGgr4MIE-py3.11/lib/python3.11/site-packages/starlette/routing.py", line 718, in __call__
await route.handle(scope, receive, send)
File "/Users/tailo/Library/Caches/pypoetry/virtualenvs/app-rGgr4MIE-py3.11/lib/python3.11/site-packages/starlette/routing.py", line 276, in handle
await self.app(scope, receive, send)
File "/Users/tailo/Library/Caches/pypoetry/virtualenvs/app-rGgr4MIE-py3.11/lib/python3.11/site-packages/starlette/routing.py", line 66, in app
response = await func(request)
^^^^^^^^^^^^^^^^^^^
File "/Users/tailo/Library/Caches/pypoetry/virtualenvs/app-rGgr4MIE-py3.11/lib/python3.11/site-packages/sqladmin/authentication.py", line 66, in wrapper_decorator
return await func(*args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/tailo/Library/Caches/pypoetry/virtualenvs/app-rGgr4MIE-py3.11/lib/python3.11/site-packages/sqladmin/application.py", line 482, in delete
model = await model_view.get_object_for_delete(pk)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/tailo/Library/Caches/pypoetry/virtualenvs/app-rGgr4MIE-py3.11/lib/python3.11/site-packages/sqladmin/models.py", line 839, in get_object_for_delete
stmt = self._stmt_by_identifier(value)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/tailo/Library/Caches/pypoetry/virtualenvs/app-rGgr4MIE-py3.11/lib/python3.11/site-packages/sqladmin/models.py", line 845, in _stmt_by_identifier
values = object_identifier_values(identifier, self.model)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Users/tailo/Library/Caches/pypoetry/virtualenvs/app-rGgr4MIE-py3.11/lib/python3.11/site-packages/sqladmin/helpers.py", line 234, in object_identifier_values
values.append(get_column_python_type(pk)(part))
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/typing.py", line 1263, in __call__
result.__orig_class__ = self
^^^^^^^^^^^^^^^^^^^^^
File "/Library/Frameworks/Python.framework/Versions/3.11/lib/python3.11/uuid.py", line 278, in __setattr__
raise TypeError('UUID objects are immutable')
TypeError: UUID objects are immutable
Thanks for any help
I don't think that's the same problem, the error clearly is different and you are not using sqlalchemy_utils. I think you should open a different issue.
I don't think that's the same problem, the error clearly is different and you are not using sqlalchemy_utils. I think you should open a different issue.
Okay, I did it
Hi! I faced with a problem I want to edit or delete an instance of the User model in the admin panel, but I got that error: TypeError: UUID objects are immutable The User model from fastapi_users lib:
I've got the same problem. Have you succeeded?
Hello, I wanted to know if there is any news regarding this issue? I am encountering the same thing at the moment.
Thanks
This seems like a good solution, implemented in https://github.com/aminalaee/sqladmin/pull/757