55# This module is part of Kim and is released under
66# the MIT License: http://www.opensource.org/licenses/mit-license.php
77
8+ import warnings
89import weakref
910import six
1011import inspect
1314
1415from .exception import MapperError , MappingInvalid
1516from .field import Field , FieldError , FieldInvalid
16- from .role import whitelist , Role
17+ from .role import whitelist , blacklist , Role
1718from .utils import recursive_defaultdict , attr_or_key
1819from .pipelines .base import Pipe
1920
@@ -407,34 +408,66 @@ def _get_obj(self):
407408 else :
408409 return self ._get_mapper_type ()()
409410
410- def _get_role (self , name_or_role ):
411+ def _get_role (self , name_or_role , deferred_role = None ):
411412 """Resolve a string to a role and check it exists, or check a
412413 directly passed role is a Role instance and return it.
413414
415+ You may also affect the fields returned from a role at read time
416+ using ``deferred_role``. deferred_role is used to provide the intersection
417+ between the role specified at ``name_or_role`` and the ``deferred_role``.
418+
419+ class FooMapper(Mapper):
420+ __type__ = dict
421+ name = field.String()
422+ id = field.String()
423+ secret = field.String()
424+
425+ __roles__ = {
426+ 'overview': whitelist('id', 'name'),
427+ }
428+
429+ mapper._get_role('overview', deferred_role=whitelist('id'))
430+
431+ Deferred roles can be used for things like allowing end users to provide a list
432+ of fields they want back from your API but only if they appear in a role you've
433+ specified.
434+
435+ :param deferred_role: provide a role containing fields to dynamically change the
436+ permitted fields for the role specified in ``name_or_role``
414437 :param name_or_role: role name as a string or a Role instance
415438
416439 :raises: :class:`.MapperError`
417440 :returns: Role instance
418441 """
419442 if isinstance (name_or_role , six .string_types ):
420443 try :
421- return self .roles [name_or_role ]
444+ role = self .roles [name_or_role ]
422445 except KeyError :
423446 raise MapperError ("Role '%s' not found on %s" % (
424447 name_or_role , self .__class__ .__name__ ))
425448 elif isinstance (name_or_role , Role ):
426- return name_or_role
449+ role = name_or_role
427450 else :
428451 raise MapperError ('role must be string or Role instance, got %s'
429452 % type (name_or_role ))
430453
454+ # If deferred_role is not None, return the intersection of the
455+ # role and the deffered_role
456+ if deferred_role is not None :
457+ if not isinstance (deferred_role , Role ):
458+ raise MapperError ('deferred_role must be instance of Role' )
459+
460+ return role & deferred_role
461+ else :
462+ return role
463+
431464 def _field_in_data (self , field ):
432465 for key in self .data .keys ():
433466 if key == field .name :
434467 return True
435468 return False
436469
437- def _get_fields (self , name_or_role , for_marshal = False ):
470+ def _get_fields (self , name_or_role , deferred_role = None , for_marshal = False ):
438471 """Returns a list of :class:`.Field` instances providing they are
439472 registered in the specified :class:`Role`.
440473
@@ -445,7 +478,7 @@ def _get_fields(self, name_or_role, for_marshal=False):
445478 :returns: list of :class:`.Field`
446479 """
447480
448- role = self ._get_role (name_or_role )
481+ role = self ._get_role (name_or_role , deferred_role = deferred_role )
449482
450483 fields = [f for name , f in six .iteritems (self .fields ) if name in role ]
451484
@@ -542,7 +575,7 @@ def get_mapper_session(self, data, output):
542575
543576 return MapperSession (self , data , output , partial = self .partial )
544577
545- def serialize (self , role = '__default__' , raw = False ):
578+ def serialize (self , role = '__default__' , raw = False , deferred_role = None ):
546579 """Serialize ``self.obj`` into a dict according to the fields
547580 defined on this Mapper.
548581
@@ -562,7 +595,7 @@ def serialize(self, role='__default__', raw=False):
562595 else :
563596 data = self ._get_obj ()
564597
565- for field in self ._get_fields (role ):
598+ for field in self ._get_fields (role , deferred_role = deferred_role ):
566599 field .serialize (self .get_mapper_session (data , output ))
567600
568601 return output
@@ -734,7 +767,7 @@ def get_mapper(self, data=None, obj=None):
734767 })
735768 return self .mapper (** self .mapper_params )
736769
737- def serialize (self , objs , role = '__default__' ):
770+ def serialize (self , objs , role = '__default__' , deferred_role = None ):
738771 """Serializes each item in ``objs`` creating a new mapper each time.
739772
740773 :param objs: iterable of objects to serialize
@@ -745,7 +778,9 @@ def serialize(self, objs, role='__default__'):
745778
746779 output = [] # TODO should this be user defined?
747780 for obj in objs :
748- output .append (self .get_mapper (obj = obj ).serialize (role = role ))
781+ output .append (self .get_mapper (obj = obj ).serialize (
782+ role = role ,
783+ deferred_role = deferred_role ))
749784
750785 return output
751786
0 commit comments