Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Unicode Problem with SQLAlchemy

I know I'm having a problem with a conversion from Unicode but I'm not sure where it's happening.

I'm extracting data about a recent Eruopean trip from a directory of HTML files. Some of the location names have non-ASCII characters (such as é, ô, ü). I'm getting the data from a string representation of the the file using regex.

If i print the locations as I find them, they print with the characters so the encoding must be ok:

Le Pré-Saint-Gervais, France
Hôtel-de-Ville, France

I'm storing the data in a SQLite table using SQLAlchemy:

Base = declarative_base()
class Point(Base):
    __tablename__ = 'points'

    id = Column(Integer, primary_key=True)
    pdate = Column(Date)
    ptime = Column(Time)
    location = Column(Unicode(32))
    weather = Column(String(16))
    high = Column(Float)
    low = Column(Float)
    lat = Column(String(16))
    lon = Column(String(16))
    image = Column(String(64))
    caption = Column(String(64))

    def __init__(self, filename, pdate, ptime, location, weather, high, low, lat, lon, image, caption):
        self.filename = filename
        self.pdate = pdate
        self.ptime = ptime
        self.location = location
        self.weather = weather
        self.high = high
        self.low = low
        self.lat = lat
        self.lon = lon
        self.image = image
        self.caption = caption

    def __repr__(self):
        return "<Point('%s','%s','%s')>" % (self.filename, self.pdate, self.ptime)

engine = create_engine('sqlite:///:memory:', echo=False)
Base.metadata.create_all(engine)
Session = sessionmaker(bind = engine)
session = Session()

I loop through the files and insert the data from each one into the database:

for filename in filelist:

    # open the file and extract the information using regex such as:
    location_re = re.compile("<h2>(.*)</h2>",re.M)
    # extract other data

    newpoint = Point(filename, pdate, ptime, location, weather, high, low, lat, lon, image, caption)
    session.add(newpoint)
    session.commit()

I see the following warning on each insert:

/usr/lib/python2.5/site-packages/SQLAlchemy-0.5.4p2-py2.5.egg/sqlalchemy/engine/default.py:230: SAWarning: Unicode type received non-unicode bind param value 'Spitalfields, United Kingdom'
  param.append(processors[key](compiled_params[key]))

And when I try to do anything with the table such as:

session.query(Point).all()

I get:

Traceback (most recent call last):
  File "./extract_trips.py", line 131, in <module>
    session.query(Point).all()
  File "/usr/lib/python2.5/site-packages/SQLAlchemy-0.5.4p2-py2.5.egg/sqlalchemy/orm/query.py", line 1193, in all
    return list(self)
  File "/usr/lib/python2.5/site-packages/SQLAlchemy-0.5.4p2-py2.5.egg/sqlalchemy/orm/query.py", line 1341, in instances
    fetch = cursor.fetchall()
  File "/usr/lib/python2.5/site-packages/SQLAlchemy-0.5.4p2-py2.5.egg/sqlalchemy/engine/base.py", line 1642, in fetchall
    self.connection._handle_dbapi_exception(e, None, None, self.cursor, self.context)
  File "/usr/lib/python2.5/site-packages/SQLAlchemy-0.5.4p2-py2.5.egg/sqlalchemy/engine/base.py", line 931, in _handle_dbapi_exception
    raise exc.DBAPIError.instance(statement, parameters, e, connection_invalidated=is_disconnect)
sqlalchemy.exc.OperationalError: (OperationalError) Could not decode to UTF-8 column 'points_location' with text 'Le Pré-Saint-Gervais, France' None None

I would like to be able to correctly store and then return the location names with the original characters intact. Any help would be much appreciated.

like image 419
Dave Forgac Avatar asked Jun 08 '09 18:06

Dave Forgac


People also ask

How make SQLAlchemy faster?

Instead of returning entire model instances, SQLAlchemy can fetch only the columns we're interested in. This not only reduces the amount of data sent, but also avoids the need to instantiate entire objects. Working with tuples of column data instead of models can be quite a bit faster.

Is SQLAlchemy efficient?

SQLAlchemy leverages powerful common statements and types to ensure its SQL statements are crafted efficiently and properly for each database type and vendor without you having to think about it. This makes it easy to migrate logic from Oracle to PostgreSQL or from an application database to a data warehouse.

What is Create_engine in SQLAlchemy?

The create_engine() method of sqlalchemy library takes in the connection URL and returns a sqlalchemy engine that references both a Dialect and a Pool, which together interpret the DBAPI's module functions as well as the behavior of the database.


3 Answers

I found this article that helped explain my troubles somewhat:

http://www.amk.ca/python/howto/unicode#reading-and-writing-unicode-data

I was able to get the desired results by using the 'codecs' module and then changing my program as follows:

When opening the file:

infile = codecs.open(filename, 'r', encoding='iso-8859-1')

When printing the location:

print location.encode('ISO-8859-1')

I can now query and manipulate the data from the table without the error from before. I just have to specify the encoding when I output the text.

(I still don't entirely understand how this is working so I guess it's time to learn more about Python's unicode handling...)

like image 50
Dave Forgac Avatar answered Oct 14 '22 22:10

Dave Forgac


From sqlalchemy.org

See section 0.4.2

added new flag to String and create_engine(), assert _unicode=(True|False|'warn'|None). Defaults to False or None on create _engine() and String, 'warn' on the Unicode type. When True, results in all unicode conversion operations raising an exception when a non-unicode bytestring is passed as a bind parameter. 'warn' results in a warning. It is strongly advised that all unicode-aware applications make proper use of Python unicode objects (i.e. u'hello' and not 'hello') so that data round trips accurately.

I think you are trying to input a non-unicode bytestring. Perhaps this might lead you on the right track? Some form of conversion is needed, compare 'hello' and u'hello'.

Cheers

like image 40
ralphtheninja Avatar answered Oct 14 '22 22:10

ralphtheninja


Try using a column type of Unicode rather than String for the unicode columns:

Base = declarative_base()
class Point(Base):
    __tablename__ = 'points'

    id = Column(Integer, primary_key=True)
    pdate = Column(Date)
    ptime = Column(Time)
    location = Column(Unicode(32))
    weather = Column(String(16))
    high = Column(Float)
    low = Column(Float)
    lat = Column(String(16))
    lon = Column(String(16))
    image = Column(String(64))
    caption = Column(String(64))

Edit: Response to comment:

If you're getting warnings about unicode encodings then there are two things you can try:

  1. Convert your location to unicode. This would mean having your Point created like this:

    newpoint = Point(filename, pdate, ptime, unicode(location), weather, high, low, lat, lon, image, caption)

    The unicode conversion will produce a unicode string when passed either a string or a unicode string, so you don't need to worry about what you pass in.

  2. If that doesn't solve the encoding issues, try calling encode on your unicode objects. That would mean using code like:

    newpoint = Point(filename, pdate, ptime, unicode(location).encode('utf-8'), weather, high, low, lat, lon, image, caption)

    This step probably won't be necessary but what it essentially does is converts a unicode object from unicode code-points to a specific byte representation (in this case, utf-8). I'd expect SQLAlchemy to do this for you when you pass in unicode objects but it may not.

like image 22
workmad3 Avatar answered Oct 14 '22 22:10

workmad3