2012-03-02 137 views
8

Tôi đang cố sử dụng phiên bản SQLAlchemy để lưu trữ các đối tượng của mình trong cơ sở dữ liệu. Tôi có một chức năng save(...) cho mục đích đó:SQLAlchemy: trình ánh xạ có thay đổi đối tượng của tôi không?

#!/usr/bin/env python 
# encoding: utf-8 

from sqlalchemy  import Column, Integer, MetaData, String, Table, create_engine 
from sqlalchemy.orm import mapper, sessionmaker 

class MyClass(object): 
    def __init__(self, title): 
     self.title = title 
    def __str__(self): 
     return '%s' % (self.title) 

def save(object_list): 
    metadata = MetaData() 
    my_class_table = Table('my_class', 
          metadata, 
          Column('id', Integer,  primary_key=True), 
          Column('title', String(255), nullable=False)) 

    # everything is OK here, output: 
    # some title 
    # another title 
    # yet another title 

    for obj in object_list: 
     print obj 

    mapper(MyClass, my_class_table) 

    # on Linux/SQLAlchemy 0.6.8, this fails with 
    # Traceback (most recent call last): 
    # File "./test.py", line 64, in <module> 
    #  save(my_objects) 
    # File "./test.py", line 57, in save 
    #  print obj 
    # File "./test.py", line 11, in __str__ 
    #  return '%s' % (self.title) 
    # File "/usr/lib/python2.7/dist-packages/sqlalchemy/orm/attributes.py", line 167, in __get__ 
    #  return self.impl.get(instance_state(instance), 
    # AttributeError: 'NoneType' object has no attribute 'get' 

    # on Mac OSX/SQLAlchemy 0.7.5, this fails with 
    # Traceback (most recent call last): 
    # File "./test.py", line 64, in <module> 
    #  save(my_objects) 
    # File "./test.py", line 57, in save 
    #  print obj 
    # File "./test.py", line 11, in __str__ 
    #  return '%s' % (self.title) 
    # File "/Library/Python/2.7/site-packages/sqlalchemy/orm/attributes.py", line 165, in __get__ 
    #  if self._supports_population and self.key in dict_: 
    # File "/Library/Python/2.7/site-packages/sqlalchemy/orm/attributes.py", line 139, in __getattr__ 
    #  key) 
    # AttributeError: Neither 'InstrumentedAttribute' object nor 'Comparator' object has an attribute '_supports_population' 

    for obj in object_list: 
     print obj 

    # (more code to set up engine and session...) 


if __name__ == '__main__': 
    my_objects = [MyClass('some title'), MyClass('another title'), MyClass('yet another title')] 
    save(my_objects) 

Dường như với tôi mapper làm điều gì đó với các đối tượng của tôi mặc nhiên khi tôi tạo ra các ánh xạ và tôi không thể sử dụng chúng nữa. Từ những gì tôi đọc in similar questions, điều này không hoàn toàn không rõ.

Hành vi này có được mong đợi không?
Tôi có gặp phải điều gì đó cơ bản sai ở đây không?
Cách thích hợp để lập bản đồ và lưu trữ các đối tượng của tôi là gì?


Thông tin thêm: Tôi đang sử dụng SQLAlchemy 0.7.5 với hệ thống mặc định Python 2.7.1 trên Mac OSX 10.7.3 Lion và SQLAlchemy 0.6.8 với Python hệ thống mặc định 2.7.2+ trên Kubuntu 11.10 máy ảo.


Cập nhật: Có vẻ như mapper SQLAlchemy là well known to change objects để 'SQLAlchemy cần'. Giải pháp trong bài viết được liên kết là tạo các đối tượng sau khi mapper(...) được gọi.
Tôi đã có các đối tượng hợp lệ mặc dù - nhưng tôi không thể sử dụng chúng nữa ...

Làm cách nào để lấy SQLAlchemy để lưu trữ các đối tượng của tôi?

Cập nhật 2: Tôi nhận được ấn tượng tôi đang hiểu lầm gì đó cơ bản về ORMs:
Tôi nghĩ khái niệm SQLAlchemy mapper mang lại cho tôi một cách để xác định đối tượng của tôi và làm việc với họ trong ứng dụng của tôi tách rời khỏi cơ sở dữ liệu bất cứ điều gì - và chỉ một lần tôi muốn tồn tại chúng, tôi mang SQLAlchemy và làm cho nó làm tất cả công việc nặng nề của việc ánh xạ một đối tượng lớp tới một bảng cơ sở dữ liệu. Ngoài ra, tôi có thể thấy không có lý do kiến ​​trúc tại sao SQLAlchemy thậm chí nên được đề cập ở bất kỳ nơi nào khác trong mã của tôi hơn là trong các hàm liên tục save(...)load(...).

Tuy nhiên, khi nhìn vào phản hồi đầu tiên bên dưới, tôi hiểu tôi phải ánh xạ lớp vào bảng cơ sở dữ liệu trước khi tôi sử dụng bất kỳ đối tượng nào của mình; đây sẽ là lúc bắt đầu chương trình của tôi. Có lẽ tôi đang nhận được một cái gì đó sai ở đây, nhưng điều này có vẻ là một hạn chế thiết kế khá mạnh mẽ từ một phần của SQLAlchemy - tất cả các khớp nối lỏng lẻo đã biến mất. Với ý nghĩ đó, tôi cũng không thấy lợi ích của việc có một người lập bản đồ ở nơi đầu tiên như 'khớp nối trễ' mà tôi muốn dường như không thể thực hiện được với SQLAlchemy và tôi cũng có thể làm 'kiểu khai báo' này và trộn và trộn lẫn logic kinh doanh với mã persistency :-(

cập nhật 3: tôi quay trở lại với một hình vuông và đọc lên trên SQLAlchemy một lần nữa Nó nói ngay trên front page:.

SQLAlchemy là nổi tiếng nhất cho trình ánh xạ đối tượng-quan hệ của nó (ORM), một thành phần tùy chọn cung cấp mẫu trình ánh xạ dữ liệu, nơi các lớp có thể được ánh xạ tới cơ sở dữ liệu trong kết thúc mở, theo nhiều cách - cho phép mô hình đối tượng và lược đồ cơ sở dữ liệu phát triển theo cách được tách riêng sạch từ đầu.

Tuy nhiên, dựa trên kinh nghiệm của tôi cho đến nay, SQLAlchemy dường như không cung cấp lời hứa đó vì nó buộc tôi phải ghép nối mô hình đối tượng và lược đồ cơ sở dữ liệu ngay từ đầu.Sau khi tất cả tôi đã nghe nói về SQLAlchemy, tôi thực sự có một thời gian khó tin rằng đây thực sự là trường hợp và tôi thà cho rằng tôi chưa hiểu điều gì đó cơ bản.

Tôi đang làm gì sai?

Cập nhật 4:

Đối với đầy đủ sake, tôi muốn thêm mã mẫu làm việc mà làm tất cả các bản đồ trước khi tạo bất kỳ đối tượng kinh doanh:

#!/usr/bin/env python 
# encoding: utf-8 

from sqlalchemy  import Column, Integer, MetaData, String, Table, create_engine 
from sqlalchemy.orm import mapper, sessionmaker 

class MyClass(object): 
    def __init__(self, title): 
     self.title = title 
    def __str__(self): 
     return '%s' % (self.title) 

def save(object_list, metadata): 

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

    for obj in object_list: 
     try: 
      session.add(obj) 
      session.commit() 
     except Exception as e: 
      print e 
      session.rollback() 

    # query objects to test they're in there 
    all_objs = session.query(MyClass).all() 
    for obj in all_objs: 
     print 'object id :', obj.id 
     print 'object title:', obj.title 


if __name__ == '__main__': 

    metadata = MetaData() 
    my_class_table = Table('my_class', 
          metadata, 
          Column('id', Integer,  primary_key=True), 
          Column('title', String(255), nullable=False)) 
    mapper(MyClass, my_class_table) 

    my_objects = [MyClass('some title'), MyClass('another title'), MyClass('yet another title')] 
    save(my_objects, metadata) 

Trong của riêng tôi (không mẫu), tôi nhập MyClass từ mô-đun riêng của mình và thực hiện ánh xạ trong lớp 'kho lưu trữ đối tượng' riêng biệt tạo ra MetaData trong phương pháp __init__(...) và lưu trữ nó trong thành viên, do đó, save(...)load(...) persiste phương pháp ncy có thể truy cập nó theo yêu cầu. Tất cả ứng dụng chính cần làm là tạo đối tượng kho lưu trữ ngay từ đầu; điều này chứa tác động của SQLAlchemy trên thiết kế đến một lớp, nhưng cũng phân phối đối tượng nghiệp vụ và các định nghĩa bảng được ánh xạ tới các vị trí riêng biệt trong mã. Không chắc chắn nếu tôi sẽ đi với điều đó lâu dài, nhưng nó có vẻ làm việc cho thời điểm này.

Gợi ý nhanh cuối cùng cho SQLAlchemy noobs giống như tôi: Bạn phải làm việc với một đối tượng siêu dữ liệu giống nhau, nếu không bạn sẽ nhận được ngoại lệ như no such table hoặc class not mapped.

Trả lời

2

SQLAlchemy hoàn toàn sửa đổi lớp được ánh xạ. SQLAlchemy gọi thiết bị này là .

Quan sát:

>>> print(MyClass.__dict__) 
{'__module__': '__main__', '__str__': <function __str__ at 0x106d50398>, '__dict__': 
<attribute '__dict__' of 'MyClass' objects>, '__weakref__': <attribute '__weakref__' of 
'MyClass' objects>, '__doc__': None, '__init__': <function __init__ at 0x106d4b848>} 

>>> mapper(MyClass, my_class_table) 
Mapper|MyClass|my_class 
>>> print(MyClass.__dict__) 
{'__module__': '__main__', '_sa_class_manager': <ClassManager of <class 
'__main__.MyClass'> at 7febea6a7f40>, 'title': 
<sqlalchemy.orm.attributes.InstrumentedAttribute object at 0x10fba4f90>, '__str__': 
<function __str__ at 0x10fbab398>, 'id': <sqlalchemy.orm.attributes.InstrumentedAttribute 
object at 0x10fba4e90>, '__dict__': <attribute '__dict__' of 'MyClass' objects>, 
'__weakref__': <attribute '__weakref__' of 'MyClass' objects>, '__doc__': None, '__init__': 
<function __init__ at 0x10fbab410>} 

Tương tự như vậy có sự khác biệt giữa đồng bằng MyClass trường và instrumented MyClass trường hợp:

>>> prem = MyClass('premapper') 
>>> mapper(MyClass, my_class_table) 
Mapper|MyClass|my_class 
>>> postm = MyClass('postmapper') 
>>> print prem 
{'title': 'premapper'} 
>>> print postm 
{'_sa_instance_state': <sqlalchemy.orm.state.InstanceState object at 0x10e5b6d50>, 'title': 'postmapper'} 

lỗi NoneType của bạn là bởi vì bây giờ instrumented MyClass.title (được thay thế bằng một InstrumentedAttribute descriptor) đang cố gắng truy cập tài sản _sa_instance_state qua số instance_state(instance). _sa_instance_state được tạo bởi công cụ MyClass về việc tạo đối tượng, nhưng không phải do chưa được biên soạn MyClass.

Thiết bị đo đạc là điều cần thiết. Nó được thực hiện để truy cập thuộc tính, gán và các thay đổi trạng thái đối tượng quan trọng khác có thể được truyền đạt tới người lập bản đồ bằng cách sử dụng các trình mô tả. (Ví dụ, làm thế nào để trì hoãn việc tải cột hoặc truy cập bộ sưu tập ngay cả khi có thể sửa đổi lớp để giám sát quyền truy cập thuộc tính trong đối tượng?) MyClass đối tượng không bị ràng buộc với bảng , nhưng chúng cần được ràng buộc với mapper . Bạn có thể thay đổi tùy chọn ánh xạ và bảng mà không thay đổi các đối tượng tên miền ', nhưng không nhất thiết đối tượng miền của bạn là mình. (Sau khi tất cả, bạn có thể kết nối một đối tượng duy nhất với nhiều người lập bản đồ và nhiều bảng.)

Hãy suy nghĩ về thiết bị như minh bạch thêm việc thực hiện "Chủ đề" vào một lớp được ánh xạ cho người lập bản đồ "Quan sát" trong một mẫu người quan sát chủ thể. Rõ ràng bạn không thể thực hiện một mẫu quan sát chủ thể trên một đối tượng tùy ý - bạn cần phải tuân thủ đối tượng quan sát được đối với giao diện Chủ đề.

Tôi cho rằng có thể thiết lập một cá thể tại chỗ bằng cách tạo đối tượng _sa_instance_state cho nó (Tôi không biết làm cách nào - tôi sẽ phải đọc mã mapper()), nhưng tôi không hiểu tại sao Điều này là cần thiết. Nếu có khả năng đối tượng của bạn sẽ được SQLAlchemy tiếp tục tồn tại thì chỉ cần xác định ánh xạ và bảng cho sự kiên trì đó trước khi bạn tạo bất kỳ trường hợp nào của đối tượng. Bạn không cần phải tạo một công cụ hoặc phiên hoặc có bất kỳ cơ sở dữ liệu nào để xác định ánh xạ.

Cách duy nhất bạn thậm chí có thể thoát khỏi bằng cách sử dụng các đối tượng ứng dụng hoàn toàn không được chỉnh sửa là trường hợp sử dụng của bạn vô cùng tầm thường, tương tự như một số pickle hoặc unpickle của mã lệnh. Ví dụ: nếu không có thiết bị đo đạc, bạn không bao giờ có thể tải hoặc lưu các đối tượng liên quan trong thuộc tính bộ sưu tập. Bạn có thực sự không bao giờ có ý định làm điều này? Nếu điều này là như vậy, có lẽ bạn sẽ hạnh phúc hơn khi không sử dụng sqlalchemy.orm chút nào và chỉ sử dụng phần cơ bản Expression API để duy trì trường hợp của bạn?

+0

Cảm ơn bạn rất nhiều vì đã giải thích. Tôi đã không nhận thức được một ORM sẽ áp đặt các hạn chế thiết kế mạnh mẽ như vậy trên ứng dụng muốn sử dụng nó. Bây giờ tôi chạy mã mapper trước bất cứ điều gì khác và làm việc với phiên bản cụ thể của các đối tượng kinh doanh của tôi trong suốt. Tôi chắc chắn hy vọng tất cả những hoops trả hết trong thời gian dài ... – ssc

+0

Tôi thực sự không hiểu tại sao bạn đang càu nhàu. Sự bền bỉ của bất kỳ loại nào không phải là tầm thường và sẽ luôn yêu cầu thêm chức năng hoặc áp đặt một giao diện trên các đối tượng bền vững. Ngay cả sự bền bỉ nhất có thể, việc tẩy đơn giản, yêu cầu thêm [nhiều phương pháp ma thuật vào các đối tượng có thể chọn] (http://docs.python.org/library/pickle.html#pickle-protocol). Có thể cho rằng ánh xạ SQLAlchemy áp đặt * ít hơn * đối với các đối tượng kinh doanh hơn cả giao thức pickle! –

+0

Ồ, tôi không có ý nói chuyện càu nhàu: Đây là lần đầu tiên tôi làm việc với SQLAlchemy và tôi đoán tôi đã ở dưới giả định rằng tôi có thể 'thêm vào' sự kiên trì cho các đối tượng của mình với ít nỗ lực hơn. .. – ssc

0

Bạn phải thực hiện ánh xạ trước khi tạo đối tượng MyClass, ở đầu 'if' hoặc trước đó.

Có lý do cụ thể nào bạn cần để tạo các phiên bản trước khi quyết định xem bản đồ nào sẽ được ánh xạ tới?

+0

Vâng, các trường hợp MyClass này là ứng dụng của tôi hoạt động; chúng được tạo, sửa đổi, sắp xếp và xóa bởi người dùng. SQLAlchemy chỉ xuất hiện khi tôi muốn lưu chúng vào cơ sở dữ liệu và sau đó tải lại chúng. Bảng mà chúng được ánh xạ tới luôn có thể giống nhau. – ssc

Các vấn đề liên quan