1 import datetime
2 import types
3 from copy import deepcopy, copy
4
5 from pyperry import errors
6 from pyperry.relation import Relation
7 from pyperry.adapter.abstract_adapter import AbstractAdapter
8 from pyperry.association import BelongsTo, HasMany, HasOne, HasManyThrough
132
134 """
135 The Base class for all models using pyperry.
136
137 L{Base} defines the functionality of a pyperry model. All models should
138 inherit (directly or indirectly) from L{Base}. For a basic overview of
139 usage, check out the docs on the L{pyperry} module.
140
141 Configuration Overview
142 ======================
143
144 Configuration is done through various class methods on the model. These
145 could be set anywhere, but conventionally they are set in a C{config}
146 method on the class. This method is run when a class is created with the
147 newly created class as an argument. Thus, configuration can be done like
148 this::
149
150 class Animal(pyperry.base.Base):
151 def config(cls):
152 cls.attributes 'id', 'name', 'mammal'
153 cls.configure('read', type='bertrpc')
154 cls.add_middleware('read', MyMiddleware, config1='val')
155
156 cls.has_many('friendships', class_name='Friendship')
157
158 cls.scope('mammal', where={ 'mammal': true })
159
160 Any class configuration can be done in this method.
161
162 Querying
163 ========
164
165 Queries can be built by chaining calls to scopes or query methods. For
166 example::
167
168 Person.where({ 'first_name': 'Bob' }).order('last_name')
169
170 This generates a query for all Person objects with the first name "Bob"
171 ordered by their last name. Each query method returns a new L{Relation}
172 object with the new query parameters applied. This allows the continued
173 chaining.
174
175 A L{Relation} object is a sequence-like object behaving much like a
176 C{list}. The query will be run the first time the object is treated like a
177 list, and the records will be used for the expression. For example::
178
179 query = Animal.where({ 'type': 'Platypus' })
180
181 # Array comprehensions and for loops
182 for animal in query:
183 print animal.name + " is a platypus. They don't do much."
184
185 # Compares type and attributes of objects for equality
186 object in query
187 object not in query
188
189 # Indexing, slicing, step slicing
190 query[0]
191 query[0:3]
192 query[0:3:2]
193
194 # Other
195 len(query)
196
197 In this example the query is executed before running the for loop, and all
198 subsequent calls use that result. Queries will not be run until you need
199 them so unused queries don't hurt performance. If you would like to force
200 a query to run and retreive the records in a C{list} object use the
201 L{Relation.all()} method, or to receive the object itself in single result
202 queries use the L{Relation.first()} method.
203
204 Persistence
205 ===========
206
207 Pyperry provides an interface for creating, updating, and deleting models
208 when a write adapter is configured. A model can be initialized as a "new"
209 record (default) or a "stored" record and is determined by the
210 C{new_record} attribute. This changes the behavior of the save operation.
211 A "new" record is created and a "stored" record is updated. Also, a "new"
212 record cannot be deleted as it does not yet exist in the database. See the
213 individual L{persistence methods<save>} for more information
214
215 Scopes
216 ======
217
218 Scopes allow you to specify prebuilt views of your data. Scopes, like
219 query methods, can be applied simply by chaining calls on instances of
220 L{Relation} or the L{Base} class. Scopes are created through the
221 L{scope()} class method (conventionally within the _config method)::
222
223 cls.scope('ordered', order='type, name')
224 cls.scope('platypus', where={ 'type': 'platypus' })
225 cls.scope('perrys', where={ 'name': 'perry' })
226 cls.scope('agentp', cls.perrys().platypus())
227
228 These scopes can now be used in queries along with query methods and
229 chained together to make powerful queries::
230
231 # Ordered animals with type 'platypus' named 'perry'
232 Animal.ordered().agentp()
233
234 Scopes can also accept arguments by defining a lambda or function to be
235 called when the scope is invoked::
236
237 @cls.scope
238 def name_is(rel, name):
239 return rel.where({ 'name': name })
240
241 # This can also be written:
242 cls.scope('name_is', lambda(rel, name): rel.where({ 'name': name }))
243
244 # This allows:
245 Animal.name_is('perry')
246
247
248 Associations
249 ============
250
251 Associations allow you to define foreign key relationships between two
252 models, the target (model on which the association is defined) and the
253 source (model from which the data will come). There are two basic kinds of
254 associations: has and belongs. A has relationship means the foreign_key
255 lives on the source model. A belongs relationship means the foreign_key
256 lives on target model.
257
258 Imagine a blog. A blog has many articles and an article belongs to an
259 author. You might model this structure with Blog, Article and Person
260 classes. Associations are conventionally defined in the C{_config} method
261 for each class, but to save space we'll just show the association
262 definition for each class::
263
264 Blog.has_many('articles', class_name='Article')
265 Article.belongs_to('blog', class_name='Blog')
266 Article.belongs_to('author', class_name='Person')
267
268 Assuming you have an instance of C{Blog} called C{blog} you could then
269 reference these associations like this::
270
271 # An ordered list of articles on this blog
272 articles = blog.authors().ordered()
273 # The author of the first article
274 articles[0].author()
275
276 Note that the L{has_many} association returns a L{Relation} object allowing
277 you to apply query methods and scopes to the association before executing
278 the query.
279
280 For more information on Associations see the individual L{association
281 methods<belongs_to>}.
282
283 """
284
285 __metaclass__ = BaseMeta
286
287 - def __init__(self, attributes={}, new_record=True, **kwargs):
288 """
289 Initialize a new pyperry object with attributes
290
291 Uses C{attributes} dictionary to set attributes on a new instance.
292 Only keys that have been defined for the model will be set. Defaults
293 to a new record but can be overriden with C{new_record} param. You can
294 also use C{kwargs} to specify the attributes.
295
296 @param attributes: dictionary of attributes to set on the new instance
297 @param new_record: set new_record flag to C{True} or C{False}.
298
299 """
300 self.attributes = {}
301 self.set_attributes(attributes)
302 self.set_attributes(kwargs)
303 self.new_record = new_record
304 self.saved = None
305 self.errors = {}
306 self._frozen = False
307
308
310 """
311 Adds C{dict} like attribute reading
312
313 Allows::
314
315 person['id']
316 person['name']
317
318 Developer Note: This method of accessing attributes is used internally
319 and should never be overridden by subclasses.
320
321 @raise KeyError: If C{key} is not a defined attribute.
322
323 @param key: name of the attribute to get
324
325 """
326 if key in self.defined_attributes:
327
328 return self.attributes.get(key)
329 else:
330 raise KeyError("Undefined attribute '%s'" % key)
331
333 """
334 Adds C{dict} like attribute writing
335
336 Allows::
337
338 animal['name'] = 'Perry'
339 animal['type'] = 'Platypus'
340
341 @raise KeyError: If C{key} is not a defined attribute.
342
343 @param key: name of the attribute to set
344 @param value: value to set C{key} attribute to
345
346 """
347 if key in self.defined_attributes:
348 self.attributes[key] = value
349 else:
350 raise KeyError("Undefined attribute '%s'" % key)
351
353 """
354 Dynamic attribute / association reading
355
356 Properties or Methods are not created for attributes or associations,
357 and are instead handled by this method. This allows a model to
358 override the default behavior of attribute or association access by
359 creating a property or method (respectively) of the same name.
360
361 Allows::
362
363 animal.name
364 animal.friends()
365
366 @raise AttributeError: if key is not a defined attribute or
367 association.
368
369 @param key: name of the attribute attempting to be accessed
370
371 """
372 if key in self.defined_attributes:
373 return self[key]
374 elif key in self.defined_associations:
375 return self._association_method(key)
376 else:
377 raise AttributeError("object '%s' has no attribute '%s'" %
378 (self.__class__.__name__, key))
379
381 """
382 Dynamic attribute setting
383
384 Properties are not created for setting attributes. This method allows
385 setting any defined attributes through the standard writer interface.
386
387 Allows::
388
389 animal.name = "Perry"
390 animal.type = "Platypus
391
392 @param key: name of the attribute to set
393 @param value: value to set the C{key} attribute to
394
395 """
396 if (key in self.defined_attributes
397 and not self._has_writer_property(key)):
398 self[key] = value
399 elif key in self.defined_associations and not callable(value):
400 setattr(self, '_' + key, value)
401 else:
402 object.__setattr__(self, key, value)
403
405 """
406 A shortcut method from retrieving the name of this model's primary key
407 attribute.
408 """
409 return self.primary_key()
410
412 """
413 A shortcut method for retrieving the value stored in this model's
414 primary key attribute.
415 """
416 return getattr(self, self.primary_key())
417
418
419
421 """
422 Set the attributes of the object using the provided dictionary.
423
424 Only attributes defined using define_attributes will be set.
425
426 @param attributes: dictionary of attributes
427
428 """
429 for field in attributes.keys():
430 if field in self.defined_attributes:
431 self[field] = attributes[field]
432
434 """
435 Save the current value of the model's data attributes through the write
436 adapter.
437
438 If the save succeeds, the model's C{saved} attribute will be set to
439 True. Also, if a read adapter is configured, the models data attributes
440 will be refreshed to ensure that you have the current values.
441
442 If the save fails, the model's C{errors} will be set to a
443 dictionary containing error messages and the C{saved} attribute will be
444 set to False.
445
446 @return: Returns C{True} on success or C{False} on failure
447
448 """
449 if self.frozen():
450 raise errors.PersistenceError('cannot save a frozen model')
451 elif self.pk_value() is None and not self.new_record:
452 raise errors.PersistenceError(
453 'cannot save model without a primary key value')
454
455 return self.adapter('write')(model=self).success
456
458 """
459 Update the attributes with the given dictionary or keywords and save
460 the model.
461
462 Has the same effect as calling::
463 obj.set_attributes(attributes)
464 obj.save()
465
466 Requires either C{attributes} or keyword arguments. If both are
467 provicded, C{attributes} will be used and C{kwargs} will be ignored.
468
469 @param attributes: dictionary of attributes to set
470 @param kwargs: Optionally use keyword syntax instead of C{attributes}
471 @return: Returns C{True} on success or C{False} on failure
472
473 """
474 if not attributes:
475 attributes = kwargs
476
477 self.set_attributes(attributes)
478
479 return self.save()
480
482 """
483 Removes this model from the data store
484
485 If the call succeeds, the model will be marked as frozen and calling
486 C{frozen} on the model will return True. Once a model is frozen, an
487 exception will be raised if you attempt to call one of the persistence
488 methods on it.
489
490 If the call fails, the model's C{errors} attribute will be set to a
491 dictionary of error messages describing the error.
492
493 @return: C{True} on success or C{False} on failure
494
495 """
496 if self.frozen():
497 raise errors.PersistenceError('cannot delete a frozen model')
498 elif self.new_record:
499 raise errors.PersistenceError('cannot delete a new model')
500 elif self.pk_value() is None:
501 raise errors.PersistenceError(
502 'cannot delete a model without a primary key value')
503
504 return self.adapter('write')(model=self, mode='delete').success
505
506
507
513
515 """Returns True if this instance is frozen and cannot be saved."""
516 return self._frozen
517
519 """
520 Marks this instance as being frozen, which will cause all future
521 writes and deletes to fail.
522 """
523 self._frozen = True
524
525
526 @classmethod
550
551 @classmethod
552 - def add_middleware(cls, adapter_type, klass, options=None, **kwargs):
553 """
554 Add a middleware to the given adapter
555
556 Interface for appending a middleware to an adapter stack. For more
557 information on middlewares see docs on
558 L{pyperry.adapter.abstract_adapter.AbstractAdapter}.
559
560 @param adapter_type: specify type of adapter ('read' or 'write')
561 @param klass: specify the class to use as the middleware
562 @param options: specify an options dictionary to pass to middleware
563 @param kwargs: specify options with keyword arguments instead of
564 options.
565
566 """
567 if cls.adapter_config.has_key(adapter_type):
568 middlewares = cls.adapter_config[adapter_type].get('_middlewares')
569 if not 'middlewares' in locals() or not middlewares:
570 middlewares = []
571
572 middlewares.append( (klass, options or kwargs or {}) )
573
574 cls.configure(adapter_type, _middlewares=middlewares)
575
576 @classmethod
577 - def add_processor(cls, adapter_type, klass, options=None, **kwargs):
578 """
579 Add a processor to the given adapter
580
581 Interface for adding a processor to the adapter stack. Processors come
582 before the middleware in the adapter stack. For more information on
583 processors see docs on
584 L{pyperry.adapter.abstract_adapter.AbstractAdapter}.
585
586 @param adapter_type: specify type of adapter ('read' or 'write')
587 @param klass: specify the class to use as the processor
588 @param options: specify an options dictionary to pass to processor
589 @param kwargs: specify options with keyword arguments instead of
590 options.
591
592 """
593 processors = []
594 if cls.adapter_config.has_key(adapter_type):
595 processors = (cls.adapter_config[adapter_type].get('_processors')
596 or [])
597
598 processor_config = (klass, options or kwargs or {})
599 processors.append(processor_config)
600
601 cls.configure(adapter_type, _processors=processors)
602
603 @classmethod
605 """
606 Returns the adapter specified by C{adapter_type}
607
608 If the adapter has not been configured correctly C{ConfigurationError}
609 will be raised.
610
611 @param adapter_type: type of adapter ('read' or 'write')
612 @return: the adapter specified by C{adapter_type}
613
614 """
615 if cls._adapters.has_key(adapter_type):
616 return cls._adapters[adapter_type]
617
618 if not cls.adapter_config.has_key(adapter_type):
619 raise errors.ConfigurationError("You must configure the %s adapter"
620 % (adapter_type) )
621
622 adapter_klass = cls.adapter_config[adapter_type].get('adapter')
623 if not adapter_klass:
624 raise errors.ConfigurationError("You must specify the 'adapter' "
625 "option in the %s configuration" % adapter_type)
626
627 cls._adapters[adapter_type] = adapter_klass(
628 cls.adapter_config[adapter_type], mode=adapter_type)
629
630 return cls._adapters[adapter_type]
631
632 @classmethod
634 """
635 Define available attributes for a model.
636
637 This method is automatically called when the attributes var is set on
638 the class during definition. Each call will union any new attributes
639 into the set of defined attributes.
640
641 aliased as C{attributes}
642
643 @param attributes: list parameters as strings, or the first argument is
644 a list of strings.
645
646 """
647 if attributes[0].__class__ in [list, set, tuple]:
648 attributes = attributes[0]
649 cls.defined_attributes |= set(attributes)
650 attributes = define_attributes
651
652 @classmethod
654 """
655 Returns the attribute name of the model's primary key.
656 """
657 return cls._primary_key
658
659 @classmethod
661 """
662 Set the name of the primary key attribute for the model. The new
663 primary key attribute must be one of the definted attributes otherwise
664 set_primary_key will raise an AttributeError.
665 """
666 if attr_name not in cls.defined_attributes:
667 raise AttributeError(
668 'an attribute must be defined to make it the primary key')
669 cls._primary_key = attr_name
670
671
672 @classmethod
674 """
675 Execute query using relation on the read adapter stack
676
677 @param relation: An instance of C{Relation} describing the query
678 @return: list of records from adapter query data each with new_record
679 set to false. C{None} items are removed.
680
681 """
682 return cls.adapter('read')(relation=relation)
683
684
685 @classmethod
687 """
688 A base instance of C{Relation} for this model.
689
690 All query scopes originate from this instance, and should not change
691 this instance.
692
693 @return: C{Relation} instance for this model
694
695 """
696 if not hasattr(cls, '_relation') or cls._relation.klass != cls:
697 cls._relation = Relation(cls)
698 return cls._relation
699
700 @classmethod
702 """
703 Base instance of C{Relation} after default scopes are applied.
704
705 Returns an instance of Relation after applying the latest scope in
706 the class variable _scoped_methods.
707
708 @return: C{Relation} instance with default scopes applied if default
709 scopes are present. Otherwise returns C{None}.
710
711 """
712 if not hasattr(cls, '_scoped_methods'): cls._scoped_methods = []
713 if len(cls._scoped_methods) > 0:
714 return cls._scoped_methods[-1]
715
716 @classmethod
718 """
719 Unique instance of C{Relation} to build queries on.
720
721 If you want an instance of relation on this model you most likely want
722 this method.
723
724 @return: Cloned return value from C{current_scope} or C{relation}
725
726 """
727 if cls.current_scope():
728 return cls.relation().merge(cls.current_scope())
729 else:
730 return cls.relation().clone()
731
732 @classmethod
734 """
735 Add a default scoping for this model.
736
737 All queries will be built based on the default scope of this model.
738 Only specify a default scope if you I{always} want the scope
739 applied. Calls to C{default_scope} aggregate. So each call will append
740 to options from previous calls.
741
742 Note: You can bypass default scopings using the L{unscoped} method.
743
744 Similar to arguments accepted by L{scope}. The only thing not
745 supported is lambdas/functions accepting additional arguments. Here are
746 some examples::
747
748 Model.default_scope(where={'type': 'Foo'})
749 Model.default_scope({ 'order': 'name DESC' })
750
751 """
752 options = cls._parse_scope_options(*args, **kwargs)
753 base_scope = cls.current_scope() or cls.relation()
754 rel = cls._apply_scope_options(base_scope, options)
755
756 if rel:
757 cls._scoped_methods.append(rel)
758
759 @classmethod
761 """
762 Execute C{function} without default scopes
763
764 All default scoping is temporarily removed and the given function is
765 then executed. After the function is executed all previous default
766 scopes are applied.
767
768 @param function: function to execute
769
770 """
771 cls.current_scope()
772 current_scopes = cls._scoped_methods
773 try:
774 cls._scoped_methods = []
775 function()
776 finally:
777 cls._scoped_methods = current_scopes
778
779 @classmethod
780 - def scope(cls, name_or_func, *args, **kwargs):
781 """
782 Defines a scope on the given model.
783
784 A scope can be defined in one of several ways:
785
786 Dictionary or Keyword Arguments
787
788 If your scope is simply setting a few static query arguments than this
789 is the easiest option. Here are a few examples::
790
791 # With a dictionary
792 Model.scope('ordered', { 'order': "name" })
793
794 # With keyword arguments
795 Model.scope('awesome', where={'awesome': 1})
796 Model.scope('latest', order="created_at DESC", limit=1)
797
798 With a Lambda or Function
799
800 When your scope involves chaining other scopes, delayed values (such as
801 a relative time), or if it takes arguments then this is the preferred
802 method. Here are a few examples::
803
804 Model.scope('awesome_ordered', lambda(cls): cls.ordered().awesome())
805
806 # Returns a scope dynamically generating condition using fictional
807 # minutes_ago function. Without the lambda this wouldn't update
808 # each time the scope is used, but only when the code was reloaded.
809 Model.scope('recent', lambda(cls): cls.where(
810 'created_at > %s' % minutes_ago(5))
811
812 # You can also use the method as a decorator! Whatever you call
813 # your method will be the name of the scope. Make sure it's unique.
814 @Model.scope
815 def name_like(cls, word):
816 return cls.where(["name LIKE '%?%", word])
817
818 These scopes can be chained. Like so::
819
820 # Returns a max of 5 records that have a name containing 'bob'
821 # ordered
822 Model.name_like('bob').ordered().limit(5)
823
824 """
825 if not hasattr(cls, 'scopes'): cls.scopes = {}
826
827 if type(name_or_func).__name__ == 'function':
828 name = name_or_func.__name__
829 elif isinstance(name_or_func, str):
830 name = name_or_func
831
832 def scope(cls, *inargs, **inkwargs):
833 if type(name_or_func).__name__ == 'function':
834 delayed = name_or_func
835 elif len(args) > 0 and type(args[0]).__name__ == 'function':
836 delayed = args[0]
837 else:
838 delayed = None
839
840 if delayed:
841 options = cls._parse_scope_options(
842 delayed(cls, *inargs, **inkwargs))
843 elif isinstance(name_or_func, str):
844 options = cls._parse_scope_options(*args, **kwargs)
845
846 rel = cls._apply_scope_options(cls.scoped(), options)
847
848 return rel
849
850 scope.__name__ = name
851
852 cls.scopes[name] = scope
853 setattr(cls, name, types.MethodType(scope, cls, cls.__class__))
854
855 return scope
856
857
858
859 @classmethod
861 """
862 Create a belongs association
863
864 Defines a belongs association by the name specified by C{id}, and you
865 can access the association through a method by this name will be
866 created. A call to this method will run the query for this association
867 and return the result, or, if this query has been run previously (or if
868 it was eager loaded), it will return the cached result.
869
870 In addition to keywords listed below this method also accepts all of
871 the query finder options specified on L{Relation}
872
873 @param id: name of the association
874 @keyword class_name: Unambiguous name (string) of source class
875 (required).
876 @keyword klass: Can be used in place of C{class_name} -- the source
877 class.
878 @keyword primary_key: Primary key of source model (default: primary key
879 of source model)
880 @keyword foreign_key: Foreign key of the target model (default: id + '_id')
881 @keyword polymorphic: Set to True if this is a polymorphic association.
882 Class name will be looked for in the (id + '_type') field. (default:
883 False)
884 @keyword namespace: For polymorphic associations set the full or
885 partial namespace to prepend to the '_type' field. (default: None)
886 @return: None
887
888 """
889 cls._create_external_association(BelongsTo(cls, id, **kwargs))
890
891 @classmethod
893 """
894 Create has collection association
895
896 Defines a has association by the name specified by C{id}. After adding
897 this association you will be able to access it through a method named
898 the same as the association. This method will return an instance of
899 L{Relation} representing the query to be run. You can treat the
900 resulting object as a list of results, and the query will be executed
901 whenever necessary. This allows you to chain additional scopes on the
902 query before executing (e.g. person.addresses().primary()).
903
904 In addition to keywords listed below this method also accepts all of
905 the query finder options specified on L{Relation}
906
907 @param id: name of the association
908 @keyword class_name: Unambiguous name (string) of source class
909 (required).
910 @keyword klass: Can be used in place of C{class_name} -- the source
911 class.
912 @keyword primary_key: Primary key of target model (default: primary key
913 of target model)
914 @keyword foreign_key: Foreign key on the source model (default: id + '_id')
915 @keyword as_: When source is polymorphic this will specify the class
916 name to use (required when source is polymorphic).
917 @return: None
918
919 """
920 if 'through' in kwargs:
921 cls._create_external_association(HasManyThrough(cls, id, **kwargs))
922 else:
923 cls._create_external_association(HasMany(cls, id, **kwargs))
924
925 @classmethod
927 """
928 Create singular has association
929
930 Defines a has association by the name specified by C{id}, and allows
931 access to the association by a method of the same name. A call to that
932 method will run the query for this association and return the resulting
933 object, or, if the query has been run previously (or it was eager
934 loaded), it will return the cached result.
935
936 In addition to keywords listed below this method also accepts all of
937 the query finder options specified on L{Relation}
938
939 @param id: name of the association
940 @keyword class_name: Unambiguous name (string) of source class
941 (required).
942 @keyword klass: Can be used in place of C{class_name} -- the source
943 class.
944 @keyword primary_key: Primary key of target model (default: primary key
945 of target model)
946 @keyword foreign_key: Foreign key on the source model (default: id + '_id')
947 @keyword as_: When source is polymorphic this will specify the class
948 name to use (required when source is polymorphic).
949 @return: None
950
951 """
952 cls._create_external_association(HasOne(cls, id, **kwargs))
953
954
955 @classmethod
958
960 """
961 Defines an association method on this instance and returns that method.
962 The association method calls the matching association in
963 self.defined_associations then caches and returns the result.
964
965 """
966 def method():
967 cache_attr = '_' + association_id
968 if not hasattr(self, cache_attr):
969 association = self.defined_associations[association_id]
970 setattr(self, cache_attr, association(self))
971 return getattr(self, cache_attr)
972
973 setattr(self, association_id, method)
974 return getattr(self, association_id)
975
977 """Return True iff key is a property with a setter"""
978 value = self.__class__.__dict__.get(key)
979 if value and hasattr(value, 'fset') and getattr(value, 'fset'):
980 return True
981 else:
982 return False
983
984 @classmethod
986 """Common method for parsing out scope options"""
987 if len(args) > 0 and not kwargs:
988 if isinstance(args[0], dict) or isinstance(args[0], Relation):
989 options = args[0]
990 else:
991 options = None
992 elif len(args) == 0 and kwargs:
993 options = kwargs
994 else:
995 options = None
996
997 if not options:
998 raise errors.ArgumentError("Invalid scoping arguments (%s, %s)"
999 % (args, kwargs))
1000
1001 return options
1002
1003 @classmethod
1009
1010
1012 """Return a string representation of the object"""
1013 return "<%s object %s new_record=%s>" % (
1014 self.__class__.__name__,
1015 self.attributes,
1016 self.new_record)
1017
1019 """Compare equality of an object by its attributes"""
1020 return( self.attributes == compare.attributes
1021 and type(self) == type(compare) )
1022