Package pyperry :: Module base
[frames] | no frames]

Source Code for Module pyperry.base

   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 
9 10 -class BaseMeta(type):
11 """ 12 The Metaclass for Base 13 14 This allows for tracking all models defined using perry.Base as well as 15 dynamic class level methods. Class methods are delegated to an instance of 16 L{pyperry.relation.Relation} if it knows how to handle them. 17 18 """ 19 20 defined_models = {} 21
22 - def __new__(mcs, name, bases, dict_):
23 """ 24 Called any time a new Base class is created using this metaclass 25 26 Models names are tracked in order to allow quick lookup of the model's 27 class by its name. 28 29 """ 30 31 # Interpret _config as __configmodel__ effectively obfiscating it 32 if dict_.has_key('_config'): 33 dict_['__configmodel__'] = classmethod(dict_['_config']) 34 del dict_['_config'] 35 else: 36 # If the subclass does not define a _config method, it will inherit 37 # the superclass's _config method causing the same _config method 38 # to get called twice, which can cause errors later on. To prevent 39 # this, we can just set the __configmodel__ for the subclass to a 40 # no-op method. 41 dict_['__configmodel__'] = classmethod(lambda cls: None) 42 43 # Create the new class 44 new = type.__new__(mcs, name, bases, dict_) 45 46 # Deepcopy adapter configs from ancestor or create a base configset 47 if hasattr(new, 'adapter_config'): 48 new.adapter_config = deepcopy(new.adapter_config) 49 else: 50 new.adapter_config = {} 51 52 # Create a default primary_key value 53 if not hasattr(new, '_primary_key'): 54 new._primary_key = 'id' 55 56 # Create fresh adapter dict 57 new._adapters = {} 58 59 # Define any attributes set during class definition 60 if not hasattr(new, 'defined_attributes'): 61 new.defined_attributes = set() 62 else: 63 new.defined_attributes = deepcopy(new.defined_attributes) 64 65 # Define any associations set during class definition 66 if not hasattr(new, 'defined_associations'): 67 new.defined_associations = {} 68 else: 69 new.defined_associations = deepcopy(new.defined_associations) 70 71 if hasattr(new, '__configmodel__'): 72 new.__configmodel__() 73 74 # Track model definitions by name 75 if not mcs.defined_models.has_key(name): 76 mcs.defined_models[name] = [] 77 mcs.defined_models[name].append(new) 78 79 return new
80
81 - def __getattr__(cls, key):
82 """Allow delegation to Relation or raise AttributeError""" 83 relation_delegation = ( 84 Relation.singular_query_methods + 85 Relation.plural_query_methods + 86 ['modifiers', 'all', 'first', 'find'] ) 87 88 if key in relation_delegation: 89 return getattr(cls.scoped(), key) 90 else: 91 raise AttributeError("Undefined attribute '%s'" % key)
92
93 - def resolve_name(cls, name):
94 """ 95 Lookup class by the given name 96 97 Returns all models that match the given name. To avoid ambiguous 98 matches you can pass any section of the preceding namespace or a full 99 absolute path name to the class. For example, to find the class 100 foo.bar.Baz you could specify:: 101 102 # Matches all models named 'Baz' 103 Base.resolve_name('Baz') 104 # Matches all models named 'Baz' in a 'bar' namespace 105 Base.resolve_name('bar.Baz') 106 # Or specify absolutely 107 Base.resolve_name('foo.bar.Baz') 108 109 @param name: string representation of a class with or without the 110 partial or full namespace in dot notation 111 @return: a list of classes matching C{name} 112 113 """ 114 name = name.rsplit('.', 1) 115 class_name = name[-1] 116 if len(name) == 2: 117 namespace = name[0] 118 119 if cls.defined_models.has_key(class_name): 120 classes = copy(cls.defined_models[class_name]) 121 122 # Filter by namespace if a namespace is queried 123 if 'namespace' in locals(): 124 classes = [ 125 kls 126 for kls in classes 127 if kls.__module__.endswith(namespace) ] 128 129 return classes 130 else: 131 return []
132
133 -class Base(object):
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 #{ Attribute access
309 - def __getitem__(self, key):
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 # Using get() here to avoid KeyError on uninitialized attrs 328 return self.attributes.get(key) 329 else: 330 raise KeyError("Undefined attribute '%s'" % key)
331
332 - def __setitem__(self, key, value):
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
352 - def __getattr__(self, key):
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
380 - def __setattr__(self, key, value):
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
404 - def pk_attr(self):
405 """ 406 A shortcut method from retrieving the name of this model's primary key 407 attribute. 408 """ 409 return self.primary_key()
410
411 - def pk_value(self):
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 #{ Persistence
420 - def set_attributes(self, attributes):
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
433 - def save(self):
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
457 - def update_attributes(self, attributes=None, **kwargs):
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
481 - def delete(self):
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
508 - def reload(self):
509 """Refetch the attributes for this object from the read adapter""" 510 pk_condition = {self.pk_attr(): self.pk_value()} 511 relation = self.scoped().where(pk_condition).fresh() 512 self.attributes = relation.first().attributes
513
514 - def frozen(self):
515 """Returns True if this instance is frozen and cannot be saved.""" 516 return self._frozen
517
518 - def freeze(self):
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 #{ Configuration 526 @classmethod
527 - def configure(cls, adapter_type, config=None, **kwargs):
528 """ 529 Method for setting adapter configuration options. 530 531 Accepts a dictionary argument or keyword arguments, but not both. 532 Configuration specified will be merged with all previous calls to 533 C{configure} for this C{adapter_type} 534 535 @param adapter_type: specify the type of adapter ('read' or 'write') 536 @param config: dictionary of configuration parameters 537 @param kwargs: alternate specification of configuration 538 539 """ 540 if adapter_type not in AbstractAdapter.adapter_types: 541 raise errors.ConfigurationError( 542 "Unrecognized adapter type: %s" % adapter_type) 543 544 new_dict = config or kwargs 545 546 if not cls.adapter_config.has_key(adapter_type): 547 cls.adapter_config[adapter_type] = {} 548 549 cls.adapter_config[adapter_type].update(new_dict)
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
604 - def adapter(cls, adapter_type):
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
633 - def define_attributes(cls, *attributes):
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
653 - def primary_key(cls):
654 """ 655 Returns the attribute name of the model's primary key. 656 """ 657 return cls._primary_key
658 659 @classmethod
660 - def set_primary_key(cls, attr_name):
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
673 - def fetch_records(cls, relation):
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 #{ Scoping 685 @classmethod
686 - def relation(cls):
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
701 - def current_scope(cls):
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
717 - def scoped(cls):
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
733 - def default_scope(cls, *args, **kwargs):
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
760 - def unscoped(cls, function):
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() # Ensure _scoped_methods set 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 #{ Association Declaration 859 @classmethod
860 - def belongs_to(cls, id, **kwargs):
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
892 - def has_many(cls, id, **kwargs):
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
926 - def has_one(cls, id, **kwargs):
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
956 - def _create_external_association(cls, association):
957 cls.defined_associations[association.id] = association
958
959 - def _association_method(self, association_id):
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
976 - def _has_writer_property(self, key):
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
985 - def _parse_scope_options(cls, *args, **kwargs):
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
1004 - def _apply_scope_options(cls, relation, options):
1005 if isinstance(options, dict): 1006 return relation.apply_finder_options(options) 1007 elif isinstance(options, Relation): 1008 return relation.merge(options)
1009 1010
1011 - def __repr__(self):
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
1018 - def __eq__(self, compare):
1019 """Compare equality of an object by its attributes""" 1020 return( self.attributes == compare.attributes 1021 and type(self) == type(compare) )
1022