Tabla de mapeo de Sqlalchemy con columnas no ASCII a clase
-
27-10-2019 - |
Pregunta
item = Table('Item', metadata, autoload=True, autoload_with=engine, encoding = 'cp1257')
class Item(object):
pass
from sqlalchemy.orm import mapper
mapper(Item, item)
Recibo error:
line 43, in <module>
mapper(Item, item)
File "C:\Python27\lib\site-packages\sqlalchemy\orm\__init__.py", line 890, in mapper
return Mapper(class_, local_table, *args, **params)
File "C:\Python27\lib\site-packages\sqlalchemy\orm\mapper.py", line 211, in __init__
self._configure_properties()
File "C:\Python27\lib\site-packages\sqlalchemy\orm\mapper.py", line 578, in _configure_properties
setparent=True)
File "C:\Python27\lib\site-packages\sqlalchemy\orm\mapper.py", line 618, in _configure_property
self._log("_configure_property(%s, %s)", key, prop.__class__.__name__)
File "C:\Python27\lib\site-packages\sqlalchemy\orm\mapper.py", line 877, in _log
(self.non_primary and "|non-primary" or "") + ") " +
File "C:\Python27\lib\site-packages\sqlalchemy\util.py", line 1510, in __get__
obj.__dict__[self.__name__] = result = self.fget(obj)
File "C:\Python27\lib\site-packages\sqlalchemy\sql\expression.py", line 3544, in description
return self.name.encode('ascii', 'backslashreplace')
UnicodeDecodeError: 'ascii' codec can't decode byte 0xeb in position 7: ordinal not in range(128)
Me estoy conectando a MSSQL. Table Autoload parece funcionar. Solo recibo este error al intentar mapear. ¡Gracias a todos por la ayuda!
Solución
Mapear la tabla a una clase crea propiedades asignadas en la clase. Las propiedades tienen el mismo nombre de las columnas, de forma predeterminada. Dado que Python 2.x solo permite identificadores ASCII, eso falla si tiene nombres de columnas no ASCII.
La única solución que se me ocurre es dar a los identificadores un nombre diferente al asignar la tabla a una clase.
El ejemplo a continuación hace eso. Tenga en cuenta que estoy creando la tabla en el código por simplicidad, por lo que cualquiera puede ejecutar el código sin tener la tabla existente. Pero podrías hacer lo mismo con una tabla reflejada.
#-*- coding:utf-8 -*-
import sqlalchemy as sa
import sqlalchemy.orm
engine = sa.create_engine('sqlite://', echo=True) # new memory-only database
metadata = sa.MetaData(bind=engine)
# create a table. This could be reflected from the database instead:
tb = sa.Table('foo', metadata,
sa.Column(u'id', sa.Integer, primary_key=True),
sa.Column(u'nomé', sa.Unicode(100)),
sa.Column(u'ãéìöû', sa.Unicode(100))
)
tb.create()
class Foo(object):
pass
# maps the table to the class, defining different property names
# for some columns:
sa.orm.mapper(Foo, tb, properties={
'nome': tb.c[u'nomé'],
'aeiou': tb.c[u'ãéìöû']
})
Después de eso puedes usar Foo.nome
para referirse al nomé
columna y Foo.aeiou
para referirse al ãéìöû
columna.
Otros consejos
Me enfrenté al mismo problema y finalmente logré hacerlo reemplazando la tabla ['columna']. Clave después de que se acelere, solo haga que todas las clases de su tabla hereden esta y luego modifique el reemplazo del nombre de la columna en el método Mapto o anule manualmente los nombres deseados con Un método de diccionario y columna_descriptor. No sé si esta no es la forma correcta de hacerlo, pero después de buscar horas es el mejor abril que tengo.
class SageProxy(object):
@classmethod
def ismapped(cls, table_name=None):
if mappings:
if table_name:
if mappings.has_key(table_name):
tmap=mappings[table_name]
if tmap.has_key('class'):
tclass=tmap['class']
if tclass is cls:
return True
else:
for m in mappings:
if cls is m['class']:
return True
return False
@classmethod
def mappingprops(cls):
#override this to pass properties to sqlalchemy mapper function
return None
@classmethod
def columns_descriptors(cls):
#override this to map columns to different class properties names
#return dictionary where key is the column name and value is the desired property name
return {}
@classmethod
def mapTo(cls, table_name, map_opts=None):
if not cls.ismapped(table_name):
tab_obj=Table(table_name,sage_md,autoload=True)
for c in tab_obj.c:
#clean field names
tab_obj.c[c.name].key=c.key.replace(u'%',u'Porcentaje').replace(u'ñ',u'ny').replace(u'Ñ',u'NY').replace(u'-',u'_')
for k,v in cls.columns_descriptors():
if tab_obj.c[k]:
tab_obj.c[k].key=v
mapper(cls, tab_obj, properties=cls.mappingprops())
mappings[table_name]={'table':tab_obj,'class':cls}
return cls
Espero que sea útil
Descubrí que podía hacer esto con una simple adición a mi clase reflejada:
metadata = MetaData(bind=engine, reflect=True)
sm = sessionmaker(bind=engine)
class tblOrders(Base):
__table__ = metadata.tables['tblOrders']
meter = __table__.c['Meter#']
meter
ahora está asignado al subyacente Meter#
columna, que permite que este código funcione:
currOrder = tblOrders()
currOrder.meter = '5'
Sin el mapeo, Python lo ve como una declaración rota porque Meter
seguido de un comentario no existe en el objeto.