2014-06-19 24 views
5

Pracuję na bazie danych PostgreSQL przez sqlalchemy. Muszę pracować z datami BC z postgresql "date" typ kolumny (obsługuje daty BC http://www.postgresql.org/docs/9.2/static/datatype-datetime.html). Ale chociaż mogę wstawić daty BC w postaci łańcucha znaków (na przykład "2000-01-01 BC"), nie mogę ich odzyskać. Próba uzyskania daty BC z bazy danych podnosi Błąd wartości: rok jest poza zakresem. Na początku myślałem, że powodem jest to, że sqlalchemy w swoim typie Date używa python datetime.date modułu, który nie obsługuje dat BC, ale nie jestem do końca pewien. Próbowałem znaleźć sposób na mapowanie typu "date" w bazie danych na niestandardową klasę Pythona (aby obejść użycie modułu datetime.date), ale nie udało mi się jeszcze (znalazłem sposoby na dodanie logiki do różnych kroków przetwarzania w sqlalchemy, np. result_processor lub bind_expression z column_expression, ale nie udało mi się sprawić, żeby działało).sqlalchemy BC daty z postgresql

Oto przykład odwzorowania problemu.

from sqlalchemy import create_engine, Column, Integer, String, Date 
    from sqlalchemy.orm import sessionmaker 
    from sqlalchemy.ext.declarative import declarative_base 

    engine = create_engine('postgresql://database:[email protected]:5432/sqlalchemy_test', echo=True) 
    Base = declarative_base() 

    class User(Base): 
     __tablename__ = 'users' 

     id = Column(Integer, primary_key=True) 
     name = Column(String) 
     date = Column(Date) 

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

    Base.metadata.create_all(engine) 
    Session = sessionmaker(bind=engine) 
    session = Session() 

    # remove objects from previous launches 
    session.query(User).delete() 

    import datetime 
    a_date = datetime.date.today() 
    # Insert valid date in datetime.date format, it works. 
    ed_user = User(name='ed', date=a_date) 
    # Insert BC date in string format, it works. 
    al_user = User(name='al', date='1950-12-16 BC') 

    session.add_all([ed_user, al_user]) 
    session.commit() 

    # Insert data via raw sql, it works 
    conn = engine.connect() 
    conn.execute('INSERT INTO users (name, date) VALUES (%s, %s)', ("Lu", "0090-04-25 BC")) 

    # Check that all 3 objects are present 
    user_names = session.query(User.name).all() 
    print user_names 


    # Check entry with AD date, it works. 
    user = session.query(User).filter_by(name='ed').first() 
    print '-- user vith valid date: ', user, user.date 

    # But when trying to fetch date fields in any way, it raises Value error 

    # Trying to fetch model, it raises Value error 
    users = session.query(User).all() 
    print users 

    # Trying to fetch only field, it raises Value error 
    user_dates = session.query(User.date).all() 
    print user_dates 

    # Even trying to fetch dates in raw sql raises Value error 
    a = conn.execute('SELECT * FROM users') 
    print [c for c in a] 

i wyjście z cienia:

2014-06-19 16:06:20,466 INFO sqlalchemy.engine.base.Engine select version() 
    2014-06-19 16:06:20,466 INFO sqlalchemy.engine.base.Engine {} 
    2014-06-19 16:06:20,468 INFO sqlalchemy.engine.base.Engine select current_schema() 
    2014-06-19 16:06:20,468 INFO sqlalchemy.engine.base.Engine {} 
    2014-06-19 16:06:20,470 INFO sqlalchemy.engine.base.Engine SELECT CAST('test plain returns' AS VARCHAR(60)) AS anon_1 
    2014-06-19 16:06:20,470 INFO sqlalchemy.engine.base.Engine {} 
    2014-06-19 16:06:20,471 INFO sqlalchemy.engine.base.Engine SELECT CAST('test unicode returns' AS VARCHAR(60)) AS anon_1 
    2014-06-19 16:06:20,471 INFO sqlalchemy.engine.base.Engine {} 
    2014-06-19 16:06:20,473 INFO sqlalchemy.engine.base.Engine show standard_conforming_strings 
    2014-06-19 16:06:20,473 INFO sqlalchemy.engine.base.Engine {} 
    2014-06-19 16:06:20,475 INFO sqlalchemy.engine.base.Engine select relname from pg_class c join pg_namespace n on n.oid=c.relnamespace where n.nspname=current_schema() and relname=%(name)s 
    2014-06-19 16:06:20,475 INFO sqlalchemy.engine.base.Engine {'name': u'users'} 
    2014-06-19 16:06:20,477 INFO sqlalchemy.engine.base.Engine BEGIN (implicit) 
    2014-06-19 16:06:20,478 INFO sqlalchemy.engine.base.Engine DELETE FROM users 
    2014-06-19 16:06:20,478 INFO sqlalchemy.engine.base.Engine {} 
    2014-06-19 16:06:20,480 INFO sqlalchemy.engine.base.Engine INSERT INTO users (name, date) VALUES (%(name)s, %(date)s) RETURNING users.id 
    2014-06-19 16:06:20,481 INFO sqlalchemy.engine.base.Engine {'date': datetime.date(2014, 6, 19), 'name': 'ed'} 
    2014-06-19 16:06:20,482 INFO sqlalchemy.engine.base.Engine INSERT INTO users (name, date) VALUES (%(name)s, %(date)s) RETURNING users.id 
    2014-06-19 16:06:20,482 INFO sqlalchemy.engine.base.Engine {'date': '1950-12-16 BC', 'name': 'al'} 
    2014-06-19 16:06:20,483 INFO sqlalchemy.engine.base.Engine COMMIT 
    2014-06-19 16:06:20,494 INFO sqlalchemy.engine.base.Engine INSERT INTO users (name, date) VALUES (%s, %s) 
    2014-06-19 16:06:20,494 INFO sqlalchemy.engine.base.Engine ('Lu', '0090-04-25 BC') 
    2014-06-19 16:06:20,495 INFO sqlalchemy.engine.base.Engine COMMIT 
    2014-06-19 16:06:20,517 INFO sqlalchemy.engine.base.Engine BEGIN (implicit) 
    2014-06-19 16:06:20,517 INFO sqlalchemy.engine.base.Engine SELECT users.name AS users_name 
    FROM users 
    2014-06-19 16:06:20,517 INFO sqlalchemy.engine.base.Engine {} 
    -- user names: [(u'ed',), (u'al',), (u'Lu',)] 
    2014-06-19 16:06:20,520 INFO sqlalchemy.engine.base.Engine SELECT users.id AS users_id, users.name AS users_name, users.date AS users_date 
    FROM users 
    WHERE users.name = %(name_1)s 
    LIMIT %(param_1)s 
    2014-06-19 16:06:20,520 INFO sqlalchemy.engine.base.Engine {'name_1': 'ed', 'param_1': 1} 
    -- user with valid date: <User(name='ed', date='2014-06-19')> 2014-06-19 
    2014-06-19 16:06:20,523 INFO sqlalchemy.engine.base.Engine SELECT users.id AS users_id, users.name AS users_name, users.date AS users_date 
    FROM users 
    2014-06-19 16:06:20,523 INFO sqlalchemy.engine.base.Engine {} 
    Traceback (most recent call last): 
     File "test_script.py", line 49, in <module> 
     users = session.query(User).all() 
     File "/home/pavel/.virtualenvs/common/local/lib/python2.7/site-packages/sqlalchemy/orm/query.py", line 2292, in all 
     return list(self) 
     File "/home/pavel/.virtualenvs/common/local/lib/python2.7/site-packages/sqlalchemy/orm/loading.py", line 65, in instances 
     fetch = cursor.fetchall() 
     File "/home/pavel/.virtualenvs/common/local/lib/python2.7/site-packages/sqlalchemy/engine/result.py", line 788, in fetchall 
     self.cursor, self.context) 
     File "/home/pavel/.virtualenvs/common/local/lib/python2.7/site-packages/sqlalchemy/engine/base.py", line 1111, in _handle_dbapi_exception 
     util.reraise(*exc_info) 
     File "/home/pavel/.virtualenvs/common/local/lib/python2.7/site-packages/sqlalchemy/engine/result.py", line 782, in fetchall 
     l = self.process_rows(self._fetchall_impl()) 
     File "/home/pavel/.virtualenvs/common/local/lib/python2.7/site-packages/sqlalchemy/engine/result.py", line 749, in _fetchall_impl 
     return self.cursor.fetchall() 
    ValueError: year is out of range 

Używam PostgreSQL 9.1.11, SQLAlchemy 0.9.4, Python 2.7

Moje pytanie brzmi: czy jest jakiś sposób, aby współpracować z BC datuje kolumnę typu "date" w postgresql poprzez sqlalchemy (przynajmniej pobiera ją jako ciąg, ale w pewnym sensie odwzorowuje ją na jakiś moduł, który może pracować z datami BC, takimi jak FlexiDate lub astropy.time)?

Odpowiedz

3

Kopiowałem ten problem i odkryłem, że ten wyjątek błędu wartości jest generowany nie przez sqlalchemy, ale przez sterownik bazy danych (psycopg2 w tym przypadku). Tutaj, jeśli wykonywany z bazą danych, utworzony z góry, ten kod spowoduje zgłoszenie tego samego wyjątku błędu wartości.

import psycopg2 
    conn = psycopg2.connect("dbname=sqlalchemy_test user=database password=database host=localhost port=5432") 
    cursor = conn.cursor() 
    cursor.execute("SELECT * FROM users") 
    cursor.fetchall() 

wyjściowa:

Traceback (most recent call last): 
     File "test_script.py", line 5, in <module> 
     values = cursor.fetchall() 
    ValueError: year is out of range 

Wygląda na to, że stara się stworzyć datetime.date obiektu z wartości pobranej z bazy danych i nie zawodzi.

Próbowałem również dialekt pg8000, nie podnosi błędu wartości, ale zjada części BC, zwracając obiekt datetime.date z roku, miesiąca i dnia (mam na myśli wiersz z bazy danych o wartości '1990-11-25 pne "zwraca datetime.date (1990, 11, 25), czyli faktycznie 1990-11-25). Oto przykład:

import pg8000 
    conn = pg8000.connect(user='postgres', host='localhost', port=5432, 
          database='sqlalchemy_test', password='postgres') 
    cursor = conn.cursor() 
    cursor.execute('SELECT * FROM users') 
    values = cursor.fetchall() 
    print values 
    print [str(val[2]) for val in values] 

wyjściowa:

([1, u'ed', datetime.date(2014, 6, 19)], [2, u'al', datetime.date(1950, 12, 16)], [3, u'Lu', datetime.date(90, 4, 25)]) 
    ['2014-06-19', '1950-12-16', '0090-04-25'] 

Również chciałem spróbować PY-postgresql dialektem, ale to tylko dla pytona 3+, która nie wchodzi w grę (produkcja wykorzystuje Pythona 2.7.6 dla tego projektu).

Tak, odpowiedź na moje pytanie brzmi: "Nie, nie jest to możliwe w sqlalchemy.", Ponieważ konwersja typu między literałem bazy danych i obiektem Pythona dzieje się w sterowniku bazy danych, a nie w części sqlalchemy.

UPDATE:

Chociaż jest to rzeczywiście niemożliwe do zrobienia w SQLAlchemy, możliwe jest, aby określić własny typ obsady w psycopg2 (i nadpisać domyślne zachowanie) i upewnij datę typ od powrotu bazy watever chcesz .Oto przykład:

# Cast PostgreSQL date into string 
    # http://initd.org/psycopg/docs/advanced.html#type-casting-from-sql-to-python 

    import psycopg2 

    BcDate = None 

    # This function dafines types cast, and as returned database literal is already a string, no 
    # additional logic required. 
    def cast_bc_date(value, cursor): 
     return value 

    def register_bc_date(connection): 
     global BcDate 
     if not BcDate: 
      cursor = connection.cursor() 
      cursor.execute('SELECT NULL::date') 
      psql_date_oid = cursor.description[0][1] 

      BcDate = psycopg2.extensions.new_type((psql_date_oid,), 'DATE', cast_bc_date) 
      psycopg2.extensions.register_type(BcDate) 

    conn = psycopg2.connect(database='sqlalchemy_test', user='database', password='database', host='localhost', port=5432) 
    register_bc_date(conn) 

Przy tym przykładzie zada działa jak urok. I otwórz sposoby na dodatkowe przetwarzanie danych w sqlalchemy.