774
744
cls.reppers = reppers
775
745
cls.unreppers = unreppers
777
# Prevent unnecessary warning messages within the following class definitions.
778
with warnings.catch_warnings():
779
warnings.simplefilter("ignore")
781
class RepresentableMixin(object):
782
""" Mixin class for JSON-representable objects.
784
This class makes special use of methods that have the prefixes
785
"_rep_" or "_unrep_":
787
* Methods named like "_rep_x" will be used to get a
788
JSON-representable version of the attribute `x` in the
791
* Methods named like "_unrep_x" will be used to create an
792
object that will be passed to the constructor as the
793
argument `x` when creating an instance from JSON data.
794
All _unrep_ methods MUST be defined as classmethods since they
795
will be called before any instance is actually created.
797
_rep_ and _unrep_ methods must be defined with the following form,
798
where arguments in [brackets] are optional:
800
>>> def _rep_attr(self [, usenorep]):
801
>>> ''' Returns a JSON representation of attribute `attr`.
803
>>> usenorep: A flag that indicates whether or not the
804
>>> object or subobjects of the object being
805
>>> represented should be represented as None
806
>>> (JSON 'null') when they cannot otherwise
809
>>> return rep_of_attr
812
>>> def _unrep_attr(cls, value [, jdata]):
813
>>> ''' Returns an object based on a rep. of attribute `attr`
815
>>> value: The representation of the attribute's value.
817
>>> jdata: Other represented data (in case it is needed).
820
>>> return value_of_attr
822
You do not have to put `usenorep` or `jdata` parameters into a
823
method's definition if the method's code won't be using them; the
824
calling class uses Python's function introspection utilities to
825
determine which way to call them.
827
Attribute representers and unrepresenters can each return the
828
special constant object `resman.NOREP` to prevent their attribute
829
from being represented or unrepresented, respectively. NOREP is a
830
one-of-a-kind object that the resource manager recognizes as a
831
signal that means that means "ignore this representation". It can
832
be used to conditionally represent certain attributes and/or
833
conditionally pass them as arguments to the class constructor when
834
the representation data is loaded back into an object.
836
If the special-prefixed methods approach does not suit your
837
needs or if you find yourself making heavy use of the `usenorep` and
838
`jdata` arguments, you can override the `to_json` and `from_json`
839
methods for more complete control over how the representations
840
are created. Doing this will prevent any attribute rep/unrep methods
841
from being used automatically.
843
Should you choose to override the `from_json` classmethod but still
844
want to use some or all of the class's unrepresenter functions, you
845
can call the `unrep_jdata` class method instead. This method accepts
846
the same arguments that would be passed to `from_json`, but instead
847
of creating a new instance of the current class, it simply passes
848
the given jdata through the appropriate attribute unrepresenter
849
class methods and returns the results as a dict. (In fact, this is
850
how most of the work for the default `from_json` is performed
853
It is permissible to create an attribute representer method without
854
a corresponding unrepresenter. In this case, the attribute
855
representation will be passed to the constructor unaltered. It is
856
also permissible to have an unrepresenter without a representer
857
because attributes missing from the given data will simply not get
858
passed to the constructor.
860
This class also makes use of a special class attribute, `tag`.
861
The purpose of `tag` is to help complex un-representer chains
862
to determine how the rep. is to be unrepped. For example, if you
863
have three different possible classes that could be used for
864
a given representation, the value of the representation's tag can
865
be used to determine which class to construct.
867
The `to_json` method only includes the `tag` attribute in a rep
868
of an object if the tag exists on that object or its class. The
869
RepresentableMixin class itself does not actually have the `tag`
870
attribute, so it is up to subclasses to implement it. Also,
871
The default implementation of the `from_json` method will not pass
872
the tag to the class constructor as it is assumed that the tag
873
will mostly be used for the purpose of determining which class to
874
construct rather than as instance data.
876
The RepresentableMixin class has support for "shorthand format"
877
representations to decrease the amount of space and typing required
878
when creating resource files by hand. However, the use of shorthand
879
requires a supporting class to have a class attribute called
880
`keyorder`. Its value should be an ordered sequence of strings that
881
represent the recognized keys in a full format representation. The
882
order of these strings within the keyorder will determine which
883
positions in a shorthand format representation correspond to which
884
keys in full (standard) format representations.
886
Warnings are issued when either the `tag` or `keyorder` attributes
887
are not present in the definition of a class that derives from
890
~~~ Class Attributes ~~~
892
All subclasses of RepresentableMixin will have the following
895
reppers: A dict that maps the name of each representable
896
attribute to the representer method for that attribute.
897
This is created automatically for each class using
898
methods that were defined in the class.
900
unreppers: A dict that maps the name of each unrepresentable
901
attribute to the unrepresenter method for that
902
attribute. This is created automatically for each class
903
using methods that were defined in the class.
906
__metaclass__ = RepresentableMixinM
908
def __init__(self, *_vargs_unused, **_kwargs_unused):
909
# Allows RepresentableMixin to be used as a mixin.
910
super(RepresentableMixin, self).__init__()
913
def short_to_full(cls, sjdata):
914
""" Converts shorthand-format data to full format.
916
<rep>.short_to_full(sjdata) -> jdata
918
The class attribute `keyorder`, a sequence of strings, of the class
919
`<rep>` is used to determine which positions in the given
920
sequence `sjdata` correspond to which keys in the dicitonary
921
`jdata`. Note that the tag must be present in shorthand format
922
and always comes first in the key order and is therefore not
923
included in the `keyorder` class attribute.
925
This method determines whether the given jdata is in shorthand
926
or full format in a primitive way. It is not fool proof, but works
927
most of the time. It basically just tries to access an item in the
928
data using a key/index that should not be present. If a KeyError
929
occurs, it is assumed that the data is in full format. If an
930
IndexError occurs, it is assumed to be in shorthand format. If
931
neither error occurs, a FormatError is excplicitly raised because
932
the full/shortness of the data could not be determined.
934
If `sjdata` is determined to be full format data it is returned
938
# Try to check for shorthand format data.
749
class RepresentableMixin(object):
750
""" Mixin class for JSON-representable objects.
752
This class makes special use of methods that have the prefixes
753
"_rep_" or "_unrep_":
755
* Methods named like "_rep_x" will be used to get a
756
JSON-representable version of the attribute `x` in the
759
* Methods named like "_unrep_x" will be used to create an
760
object that will be passed to the constructor as the
761
argument `x` when creating an instance from JSON data.
762
All _unrep_ methods MUST be defined as classmethods since they
763
will be called before any instance is actually created.
765
_rep_ and _unrep_ methods must be defined with the following form,
766
where arguments in [brackets] are optional:
768
>>> def _rep_attr(self [, usenorep]):
769
>>> ''' Returns a JSON representation of attribute `attr`.
771
>>> usenorep: A flag that indicates whether or not the
772
>>> object or subobjects of the object being
773
>>> represented should be represented as None
774
>>> (JSON 'null') when they cannot otherwise
777
>>> return rep_of_attr
780
>>> def _unrep_attr(cls, value [, jdata]):
781
>>> ''' Returns an object based on a rep. of attribute `attr`
783
>>> value: The representation of the attribute's value.
785
>>> jdata: Other represented data (in case it is needed).
788
>>> return value_of_attr
790
You do not have to put `usenorep` or `jdata` parameters into a
791
method's definition if the method's code won't be using them; the
792
calling class uses Python's function introspection utilities to
793
determine which way to call them.
795
Attribute representers and unrepresenters can each return the
796
special constant object `resman.NOREP` to prevent their attribute
797
from being represented or unrepresented, respectively. NOREP is a
798
one-of-a-kind object that the resource manager recognizes as a
799
signal that means that means "ignore this representation". It can
800
be used to conditionally represent certain attributes and/or
801
conditionally pass them as arguments to the class constructor when
802
the representation data is loaded back into an object.
804
If the special-prefixed methods approach does not suit your
805
needs or if you find yourself making heavy use of the `usenorep` and
806
`jdata` arguments, you can override the `to_json` and `from_json`
807
methods for more complete control over how the representations
808
are created. Doing this will prevent any attribute rep/unrep methods
809
from being used automatically.
811
Should you choose to override the `from_json` classmethod but still
812
want to use some or all of the class's unrepresenter functions, you
813
can call the `unrep_jdata` class method instead. This method accepts
814
the same arguments that would be passed to `from_json`, but instead
815
of creating a new instance of the current class, it simply passes
816
the given jdata through the appropriate attribute unrepresenter
817
class methods and returns the results as a dict. (In fact, this is
818
how most of the work for the default `from_json` is performed
821
It is permissible to create an attribute representer method without
822
a corresponding unrepresenter. In this case, the attribute
823
representation will be passed to the constructor unaltered. It is
824
also permissible to have an unrepresenter without a representer
825
because attributes missing from the given data will simply not get
826
passed to the constructor.
828
This class also makes use of a special class attribute, `tag`.
829
The purpose of `tag` is to help complex un-representer chains
830
to determine how the rep. is to be unrepped. For example, if you
831
have three different possible classes that could be used for
832
a given representation, the value of the representation's tag can
833
be used to determine which class to construct.
835
The `to_json` method only includes the `tag` attribute in a rep
836
of an object if the tag exists on that object or its class. The
837
RepresentableMixin class itself does not actually have the `tag`
838
attribute, so it is up to subclasses to implement it. Also,
839
The default implementation of the `from_json` method will not pass
840
the tag to the class constructor as it is assumed that the tag
841
will mostly be used for the purpose of determining which class to
842
construct rather than as instance data.
844
The RepresentableMixin class has support for "shorthand format"
845
representations to decrease the amount of space and typing required
846
when creating resource files by hand. However, the use of shorthand
847
requires a supporting class to have a class attribute called
848
`keyorder`. Its value should be an ordered sequence of strings that
849
represent the recognized keys in a full format representation. The
850
order of these strings within the keyorder will determine which
851
positions in a shorthand format representation correspond to which
852
keys in full (standard) format representations.
854
Warnings are issued when either the `tag` or `keyorder` attributes
855
are not present in the definition of a class that derives from
858
~~~ Class Attributes ~~~
860
All subclasses of RepresentableMixin will have the following
863
reppers: A dict that maps the name of each representable
864
attribute to the representer method for that attribute.
865
This is created automatically for each class using
866
methods that were defined in the class.
868
unreppers: A dict that maps the name of each unrepresentable
869
attribute to the unrepresenter method for that
870
attribute. This is created automatically for each class
871
using methods that were defined in the class.
874
__metaclass__ = RepresentableMixinM
876
def __init__(self, *vargs, **kwargs):
877
# Allows RepresentableMixin to be used as a mixin.
878
super(RepresentableMixin, self).__init__(*vargs, **kwargs)
881
def short_to_full(cls, sjdata):
882
""" Converts shorthand-format data to full format.
884
<rep>.short_to_full(sjdata) -> jdata
886
The class attribute `keyorder`, a sequence of strings, of the class
887
`<rep>` is used to determine which positions in the given
888
sequence `sjdata` correspond to which keys in the dicitonary
889
`jdata`. Note that the tag must be present in shorthand format
890
and always comes first in the key order and is therefore not
891
included in the `keyorder` class attribute.
893
This method determines whether the given jdata is in shorthand
894
or full format in a primitive way. It is not fool proof, but works
895
most of the time. It basically just tries to access an item in the
896
data using a key/index that should not be present. If a KeyError
897
occurs, it is assumed that the data is in full format. If an
898
IndexError occurs, it is assumed to be in shorthand format. If
899
neither error occurs, a FormatError is excplicitly raised because
900
the full/shortness of the data could not be determined.
902
If `sjdata` is determined to be full format data it is returned
906
# Try to check for shorthand format data.
908
# Deliberately cause an invalid index/key error. This will determine
909
# whether the format is full (a mapping) or shorthand (a sqeuence).
913
# The data appears to be in full format, not shorthand.
917
# The data appears to be shorthand format.
919
# Add the class tag to the keyorder. (The tag is always first.)
920
keyorder = (cls.tag,) + tuple(cls.keyorder)
922
# Convert shorthand format to full format.
923
jdata = dict((key, sjdata[n]) for n, key in enumerate(keyorder))
925
# Since no exception occured, there is no way to determine the format.
929
"Unable to determine whether data format is full or shorthand! "
930
"Either the data object given is a mapping/sequence that has a "
931
"key/index of %r in it or else the data object does not raise "
932
"errors in the standard way.\nObject:\n%r" % (L, sjdata)
938
def unrep_jdata(cls, jdata):
939
""" Unrepresents given JSON data for use with the constructor.
941
This method is called to turn data in the given JSON data dict
942
into data that is compatible with the representable class's
943
constructor. It does not actually create an instance of the
944
class; it merely returns a dict containing data that was created
945
by procesing the given jdata using the class's unreppers.
948
# Compile a mapping of all unreppers from the MRO
950
for base in reversed(cls.__mro__):
951
if hasattr(base, "unreppers"):
952
unreppers.update(base.unreppers)
954
# The keys in jdata will most likely be unicode, so they must be
955
# converted to string.
956
data = dict((str(k), v) for k, v in jdata.iteritems())
958
# Sort the unreppers by the class' key order.
959
# (Keys not in the keyorder are placed at the end.)
960
keyorder = list(cls.keyorder)
961
keyorder.extend(k for k in unreppers.keys() if k not in keyorder)
962
unreppers = [(k, unreppers[k]) for k in keyorder if k in unreppers]
964
# Convert jdata representations to actual data.
965
for name, unrep in unreppers:
967
# Skip values that are not present in both the data and the
971
# Choose the calling convention appropriate for the unrepper
972
argc = len(inspect.getargspec(unrep)[0])
974
v = unrep(jdata[name], jdata)
976
v = unrep(jdata[name])
987
def from_json(cls, jdata):
988
""" Create an object from a JSON representation.
990
jdata: JSON representation of the object. A copy of this
991
mapping will be expanded into the arguments for
992
the constructor with values for which a _unrep_
993
method exists replaced by the values returned from
996
Returns a representable object based on the calling class.
999
args = cls.unrep_jdata(jdata)
1001
# Remove the tag from the arguments.
1005
# Remove any temporary data
1006
for name in args.keys():
1007
if name.startswith("<") and name.endswith(">"):
1010
# Create a new object from the arguments.
1015
def to_json(self, usenorep=False):
1016
""" Create a JSON representation from the object.
1018
usenorep: A flag indicating whether or not objects that
1019
cannot be represented should be represented as
1020
None. If True, None will be used for such
1021
objects. If False, sbak.error.RepresentError
1022
will be raised. Default is False.
1025
# Empty representation.
1028
# Add tag to the data if tag is available.
1029
if hasattr(self, "tag"):
1030
jdata["tag"] = self.tag
1032
# Compile a mapping of all unreppers from the MRO
1034
for base in reversed(self.__class__.__mro__):
1035
if hasattr(base, "reppers"):
1036
reppers.update(base.reppers)
1038
# Represent attributes that are marked as representable.
1039
for name, rep in reppers.iteritems():
1040
# Try different calling conventions until one works, or until
1041
# there are no other options.
940
# Deliberately cause an invalid index/key error. This will determine
941
# whether the format is full (a mapping) or shorthand (a sqeuence).
945
# The data appears to be in full format, not shorthand.
949
# The data appears to be shorthand format.
951
# Add the class tag to the keyorder. (The tag is always first.)
952
keyorder = (cls.tag,) + tuple(cls.keyorder)
954
# Convert shorthand format to full format.
955
jdata = dict((key, sjdata[n]) for n, key in enumerate(keyorder))
957
# Since no exception occured, there is no way to determine the format.
1043
v = rep(self, usenorep)
1052
# These are needed to make sure that the missing class attribute warnings are
1053
# triggered when necessary.
1055
class ResourceMixin(RepresentableMixin):
1056
""" Mixin class to add resource functionality to a class.
1058
In addition to being representable, resource classes also have
1059
save() and load() methods for saving and loading resource data
1065
""" Loads JSON data from a file and decodes it into a resource.
1067
src: A file name or file-like object from which to
1070
This method does not normally need to be overridden since
1071
the real work is in the JSON decoder method for subclasses
1072
and not in the raw data loading. However, should a subclass
1073
need to load data from some other format besides ordinary
1074
JSON, this may need to be overridden.
1078
# Determine the correct way to use `src`.
1080
# If `src` is a string, open a file using `src` as a filename.
1081
if isinstance(src, basestring):
1083
# If `src` is not a string, assume it is an open file.
1088
# Load the JSON data from the file.
1089
obj = json.load(fsrc)
1090
except ValueError, e:
1091
# Raise a standardized FormatError if there were any value errors
1092
# while loading the JSON data.
1093
raise _file_format_err(cls.tag, e, src)
1095
# Close the file if necessary.
1099
# Turn the JSON object into a resource object.
1100
obj = cls.from_json(obj)
1104
def save(self, dest=None, usenorep=False):
1105
""" Save a JSON representation of the resource to a file.
1107
dest: Destination file. May be an open file-like
1108
object or a file name. If None (default), the
1109
file name will be determine using the `src`
1112
usenorep: If true, referenced resources that cannot be
1113
represented as JSON or as filenames will be
1114
represented as None (JSON 'null'). If false
1115
(default), sbak.error.RepresentError will be
1116
raised when such an object is encountered.
1118
This method does not normally need to be overridden because
1119
most of the work is performed by ResourceMixin.to_json.
1120
However, should a subclass need to save its data using some
1121
format other than JSON, overriding may be necessary.
1123
Raises ValueError if no file name is provided and no file name
1124
is associated with the resource.
1130
"Resource %r has no source file name associated, and none "
1131
"was provided." % self
1135
obj = self.to_json(usenorep)
1137
# Save encoded self to file.
1139
# Open dest for writing,
1141
if isinstance(dest, basestring):
1142
dest = open(dest, 'w')
1145
# and save encoded self to it.
1146
json.dump(obj, dest, sort_keys=True, indent=4)
1148
# Make sure the file gets closed.
1153
def save_as(self, dest, usenorep=False):
1154
""" Saves the resource to the given location and changes its src.
1156
This will save the resource to the given destination and also
1157
change the source file name associated with the resource
1158
within the resource manager. (If the resource manager does not
1159
have a filename associated with the resource, the destination
1160
will be associated with the resource.)
1162
A ValueError will occur if you try to save a resource to a
1163
location that is known to belong to a loaded resource object
1164
other than the current one.
1166
A TypeError will be raised if dest is None.
1170
"A destination must be specified when using `save_as`."
1173
self.save(dest, usenorep)
1182
""" The source file path that was used to load the resource.
1184
This will be None if the resource was not loaded from a file or
1185
if the resource was cleared from the resource manager.
1187
The value of this attribute is determined dynamically by
1188
calling `resman.get_src` on the resource in question. If that
1189
raises a ValueError, the src is determined to be None.
1191
Read-only. You must use `resman.clear_src` and `resman.set_src`
1192
in order to change this value, or you must save the resource to
1193
a new location using the resource's `save_as` method.
1196
return get_src(self)
1200
class ResourceGroupMixin(ResourceMixin):
1201
""" Allows a bunch of resources to be collected together in a mapping.
1203
ResourceMixinGroup() -> resgroup
1204
ResourceMixinGroup(resources) -> resgroup
1205
ResourceMixinGroup(name=resource [, ...]) -> resgroup
1206
ResourceMixinGroup.load(filename) -> resgroup
1207
ResourceMixinGroup.from_json(jdata) -> resgroup
1211
# ResourceMixinGroup must be subclassed to be used. The subclass
1212
# must define a classmethod called `resource_from_json` as follows:
1214
class MyResourceMap(ResourceMixinGroup):
1216
def resource_from_json(cls, jdata):
1217
# ... Code to parse jdata into a resource ...
1220
resgroup = MyResourceMap()
1222
resgroup = MyResourceMap({
1223
"nameA" : resourceA,
1224
"nameB" : resourceB,
1227
resgroup = MyResourceMap(
1232
resgroup = MyResourceMap.load("my_resource.resgroup")
1234
resgroup = MyResourceMap.from_json( jdata )
1238
The ResourceMixinGroup class is used as a base for creating resource
1239
map classes. A "resource group" is a collection of resources which are
1240
related, though not necessarily connected via references, and which must
1241
all be present in memory at once.
1243
A resource group functions like a mapping, such as a Python dict
1244
object. Each key in the mapping is the name of the resource within the
1245
mapping and may be used to reference resources within the resource group.
1247
*** All keys MUST be strings! ***
1248
This includes regular strings as well as unicode strings. A TypeError
1249
will be raised if ever an attempt is made to assign a value using a key
1250
that is not a string. This limitation is put in place to prevent errors
1251
when storing the resource group as a representation.
1253
Items are automatically sorted whenever an item is added to the
1254
resource group. This means that when iterating over the resource's data
1255
using the `keys`, `values`, or `iteritems` methods, items will appear in
1256
alphanumeric order based on key.
1258
~~~ Mapping Operations ~~~
1260
Mapping operations supported include the following:
1262
resgroup["name"] = resource # Set a resource to a key
1263
resource = resgroup["name"] # Get a resource from a key
1264
del resource["name"] # Remove a resource from a key
1265
"name" in resgroup # Test for existence of a key
1266
len(resgroup) # Get the number of items
1267
iter(resgroup) # Iterate over keys (for loops, etc.)
1268
resgroup.keys() # Get a list of keys (sorted)
1269
resgroup.values() # Get a list of values (sorted by key)
1270
resgroup.iteritems() # Iterate over key,resource pairs
1273
~~~ Resource Key-Paths ~~~
1275
The ResourceMixinGroup object supports a special kind of key. It is
1276
called a "key-path" because it is a key that functions like a path, as
1279
To use this feature, the resource group must have resources in it that are
1280
other resource paths, and those resource groups must have resources in
1281
them. Once this is asserted, the resources of one of the inner resource
1282
maps may be accessed by indexing the outermost resource group with a
1283
sequence of names. These names indicate the "path" taken to get from the
1284
outermost level of the resource group hierarchy to the inner resource
1289
resgroup = ResourceMixinGroup(
1290
foo = ResourceMixinGroup(
1291
innerFoo = resourceA,
1292
innerBar = resourceB
1297
myres = resgroup["foo", "innerFoo"] # 1. myres gets resourceA
1298
myres = resgroup["foo", "innerBar"] # 2. myres gets resourceB
1299
myres = resgroup["bar"] # 3. myres gets resourceC
1300
myres = resgroup["foo"]["innerFoo"] # 4. Same as 1
1301
myres = resgroup["foo"]["innerBar"] # 5. Same as 2
1303
location = ("foo", "innerFoo")
1304
myres = resgroup[location] # 6. Same as 1 and 4
1306
This feature is implemented with the intent of allowing locations within
1307
a complex resource tree to be stored as a single unit and used in a
1308
convenient way (such as example #6 above).
1310
TODO: Add support for custom reference types. (A reference is any
1311
potential resource representation that contains a key that begins with
1312
the reference symbol.)
1315
keyorder = "resources",
1317
def __init__(self, resources = (), **kwargs):
1318
""" Initializes an ResourceMixinGroupt with optional initial items
1320
ResourceMixinGroup.__init__(self) -> None
1321
ResourceMixinGroup.__init__(self, resources) -> None
1322
ResourceMixinGroup.__init__(self, name = resource [, ...]) ->
1324
resources: Must be a mapping or sequence of (name, resource) pairs.
1325
Defaults to an empty tuple.
1327
name = resource [, ...]:
1328
You may also provide resources as a series of keyword
1329
arguments, where the argument name is the key that will
1330
be used to access the resource and the value of the
1331
argument is the resource being added.
1333
Note that if you use both the `resources` argument as well as
1334
additional keyword arguments then both sets of data will be used,
1335
with the keyword arguments taking precedence if there is a naming
1338
resources = self._items = dict(resources)
1339
resources.update(kwargs)
1340
self._keyorder = sorted(resources.keys())
1344
def _unrep_resources(cls, value, jdata):
1345
""" Unrepper for `resources`.
1349
A mapping where keys indicate names of resources and the
1350
values are the representations of those resources, except
1351
for when the value is another mapping that contains a key
1352
that is one of the strings "&ref" or "©". In such a
1353
case, the value of the "&ref" or "©" key refers to
1354
a resource with the given name within the current
1357
Ex: {"&ref": "foo"} Indicates a reference to a resource
1358
named "foo" within the current
1359
ResourceMixinGroup. If the resource named "foo" has been
1360
unrepresented by the time {"&ref" : "foo"} is processed,
1361
{"&ref": "foo"} will become another occurence of "foo"
1362
within the curent ResourceMixinGroup. If "foo" has not yet been
1363
unrepresented, the name of {"&ref": "foo"} and the string
1364
"foo" itself will both be remembered, and as soon as "foo"
1365
is unrepresented, {"&ref": "foo"} will become an alternate
1368
Example value for "resources" key:
1372
"resource_foo": foo_representation_or_filename,
1373
"resource_bar": bar_representation_or_filename,
1374
"resource_spam": {"&ref": "resource_foo"}
1378
This method expects a classmethod called `resource_from_json`
1379
to be defined within the current ResourceMixinGroup
1380
subclass. It must be defined with a signature and return value
1381
as indicated by the following example method:
1384
def resource_from_json(cls, rep, jdata):
1385
''' Return an object based on a given rep.
1387
rep: Representation for an object of the type
1388
that is handled by the current
1389
ResourceMixinGroup subclass.
1391
jdata: The full JSON representation of the
1392
resource group that is currently being
1393
unrepresented. Provided in case there is
1394
some information in the resource group that
1395
affects all resources contained within.
1397
# ...processing using rep and/or jdata...
1400
`_unrep_resources` returns a mapping of loaded resources
1401
suitable for use as the parameter to the ResourceMixinGroup
1404
TODO: Make the use of "&" more generalized so that it can be used to create custom reference types
1407
# This will be used to store resources as they are loaded.
1410
# This will be used to store references that need to be defreferenced.
1413
# This will be used to store copy references that need to be copied.
1416
# Iterate over the resoruce representations
1417
for name, rep in value.iteritems():
1418
# See if its a string (ie. filename)
1419
if isinstance(rep,basestring):
1420
# It's a filename. Attempt to get the resource.
1421
resources[name] = get(rep)
961
"Unable to determine whether data format is full or shorthand! "
962
"Either the data object given is a mapping/sequence that has a "
963
"key/index of %r in it or else the data object does not raise "
964
"errors in the standard way.\nObject:\n%r" % (L, sjdata)
970
def unrep_jdata(cls, jdata):
971
""" Unrepresents given JSON data for use with the constructor.
973
This method is called to turn data in the given JSON data dict
974
into data that is compatible with the representable class's
975
constructor. It does not actually create an instance of the
976
class; it merely returns a dict containing data that was created
977
by procesing the given jdata using the class's unreppers.
980
# Compile a mapping of all unreppers from the MRO
982
for base in reversed(cls.__mro__):
983
if hasattr(base, "unreppers"):
984
unreppers.update(base.unreppers)
986
# The keys in jdata will most likely be unicode, so they must be
987
# converted to string.
988
data = dict((str(k), v) for k, v in jdata.iteritems())
990
# Sort the unreppers by the class' key order.
991
# (Keys not in the keyorder are placed at the end.)
992
keyorder = list(cls.keyorder)
993
keyorder.extend(k for k in unreppers.keys() if k not in keyorder)
994
unreppers = [(k, unreppers[k]) for k in keyorder if k in unreppers]
996
# Convert jdata representations to actual data.
997
for name, unrep in unreppers:
999
# Skip values that are not present in both the data and the
1003
# Choose the calling convention appropriate for the unrepper
1004
argc = len(inspect.getargspec(unrep)[0])
1006
v = unrep(jdata[name], jdata)
1008
v = unrep(jdata[name])
1019
def from_json(cls, jdata):
1020
""" Create an object from a JSON representation.
1022
jdata: JSON representation of the object. A copy of this
1023
mapping will be expanded into the arguments for
1024
the constructor with values for which a _unrep_
1025
method exists replaced by the values returned from
1028
Returns a representable object based on the calling class.
1031
args = cls.unrep_jdata(jdata)
1033
# Remove the tag from the arguments.
1037
# Remove any temporary data
1038
for name in args.keys():
1039
if name.startswith("<") and name.endswith(">"):
1042
# Create a new object from the arguments.
1047
def to_json(self, usenorep=False):
1048
""" Create a JSON representation from the object.
1050
usenorep: A flag indicating whether or not objects that
1051
cannot be represented should be represented as
1052
None. If True, None will be used for such
1053
objects. If False, sbak.error.RepresentError
1054
will be raised. Default is False.
1057
# Empty representation.
1060
# Add tag to the data if tag is available.
1061
if hasattr(self, "tag"):
1062
jdata["tag"] = self.tag
1064
# Compile a mapping of all unreppers from the MRO
1066
for base in reversed(self.__class__.__mro__):
1067
if hasattr(base, "reppers"):
1068
reppers.update(base.reppers)
1070
# Represent attributes that are marked as representable.
1071
for name, rep in reppers.iteritems():
1072
# Try different calling conventions until one works, or until
1073
# there are no other options.
1423
# Not a filename. See if its a regular reference.
1075
v = rep(self, usenorep)
1084
# These are needed to make sure that the missing class attribute warnings are
1085
# triggered when necessary.
1087
class ResourceMixin(RepresentableMixin):
1088
""" Mixin class to add resource functionality to a class.
1090
In addition to being representable, resource classes also have
1091
save() and load() methods for saving and loading resource data
1097
""" Loads JSON data from a file and decodes it into a resource.
1099
src: A file name or file-like object from which to
1102
This method does not normally need to be overridden since
1103
the real work is in the JSON decoder method for subclasses
1104
and not in the raw data loading. However, should a subclass
1105
need to load data from some other format besides ordinary
1106
JSON, this may need to be overridden.
1110
# Determine the correct way to use `src`.
1112
# If `src` is a string, open a file using `src` as a filename.
1113
if isinstance(src, basestring):
1115
# If `src` is not a string, assume it is an open file.
1120
# Load the JSON data from the file.
1121
obj = json.load(fsrc)
1122
except ValueError, e:
1123
# Raise a standardized FormatError if there were any value errors
1124
# while loading the JSON data.
1125
raise _file_format_err(cls.tag, e, src)
1127
# Close the file if necessary.
1131
# Turn the JSON object into a resource object.
1132
obj = cls.from_json(obj)
1136
def save(self, dest=None, usenorep=False):
1137
""" Save a JSON representation of the resource to a file.
1139
dest: Destination file. May be an open file-like
1140
object or a file name. If None (default), the
1141
file name will be determine using the `src`
1144
usenorep: If true, referenced resources that cannot be
1145
represented as JSON or as filenames will be
1146
represented as None (JSON 'null'). If false
1147
(default), sbak.error.RepresentError will be
1148
raised when such an object is encountered.
1150
This method does not normally need to be overridden because
1151
most of the work is performed by ResourceMixin.to_json.
1152
However, should a subclass need to save its data using some
1153
format other than JSON, overriding may be necessary.
1155
Raises ValueError if no file name is provided and no file name
1156
is associated with the resource.
1162
"Resource %r has no source file name associated, and none "
1163
"was provided." % self
1167
obj = self.to_json(usenorep)
1169
# Save encoded self to file.
1171
# Open dest for writing,
1173
if isinstance(dest, basestring):
1174
dest = open(dest, 'w')
1177
# and save encoded self to it.
1178
json.dump(obj, dest, sort_keys=True, indent=4)
1180
# Make sure the file gets closed.
1185
def save_as(self, dest, usenorep=False):
1186
""" Saves the resource to the given location and changes its src.
1188
This will save the resource to the given destination and also
1189
change the source file name associated with the resource
1190
within the resource manager. (If the resource manager does not
1191
have a filename associated with the resource, the destination
1192
will be associated with the resource.)
1194
A ValueError will occur if you try to save a resource to a
1195
location that is known to belong to a loaded resource object
1196
other than the current one.
1198
A TypeError will be raised if dest is None.
1202
"A destination must be specified when using `save_as`."
1205
self.save(dest, usenorep)
1214
""" The source file path that was used to load the resource.
1216
This will be None if the resource was not loaded from a file or
1217
if the resource was cleared from the resource manager.
1219
The value of this attribute is determined dynamically by
1220
calling `resman.get_src` on the resource in question. If that
1221
raises a ValueError, the src is determined to be None.
1223
Read-only. You must use `resman.clear_src` and `resman.set_src`
1224
in order to change this value, or you must save the resource to
1225
a new location using the resource's `save_as` method.
1228
return get_src(self)
1232
class ResourceMixinGroup(ResourceMixin):
1233
""" Allows a bunch of resources to be collected together in a mapping.
1235
ResourceMixinGroup() -> resgroup
1236
ResourceMixinGroup(resources) -> resgroup
1237
ResourceMixinGroup(name=resource [, ...]) -> resgroup
1238
ResourceMixinGroup.load(filename) -> resgroup
1239
ResourceMixinGroup.from_json(jdata) -> resgroup
1243
# ResourceMixinGroup must be subclassed to be used. The subclass
1244
# must define a classmethod called `resource_from_json` as follows:
1246
class MyResourceMap(ResourceMixinGroup):
1248
def resource_from_json(cls, jdata):
1249
# ... Code to parse jdata into a resource ...
1252
resgroup = MyResourceMap()
1254
resgroup = MyResourceMap({
1255
"nameA" : resourceA,
1256
"nameB" : resourceB,
1259
resgroup = MyResourceMap(
1264
resgroup = MyResourceMap.load("my_resource.resgroup")
1266
resgroup = MyResourceMap.from_json( jdata )
1270
The ResourceMixinGroup class is used as a base for creating resource
1271
map classes. A "resource group" is a collection of resources which are
1272
related, though not necessarily connected via references, and which must
1273
all be present in memory at once.
1275
A resource group functions like a mapping, such as a Python dict
1276
object. Each key in the mapping is the name of the resource within the
1277
mapping and may be used to reference resources within the resource group.
1279
*** All keys MUST be strings! ***
1280
This includes regular strings as well as unicode strings. A TypeError
1281
will be raised if ever an attempt is made to assign a value using a key
1282
that is not a string. This limitation is put in place to prevent errors
1283
when storing the resource group as a representation.
1285
Items are automatically sorted whenever an item is added to the
1286
resource group. This means that when iterating over the resource's data
1287
using the `keys`, `values`, or `iteritems` methods, items will appear in
1288
alphanumeric order based on key.
1290
~~~ Mapping Operations ~~~
1292
Mapping operations supported include the following:
1294
resgroup["name"] = resource # Set a resource to a key
1295
resource = resgroup["name"] # Get a resource from a key
1296
del resource["name"] # Remove a resource from a key
1297
"name" in resgroup # Test for existence of a key
1298
len(resgroup) # Get the number of items
1299
iter(resgroup) # Iterate over keys (for loops, etc.)
1300
resgroup.keys() # Get a list of keys (sorted)
1301
resgroup.values() # Get a list of values (sorted by key)
1302
resgroup.iteritems() # Iterate over key,resource pairs
1305
~~~ Resource Key-Paths ~~~
1307
The ResourceMixinGroup object supports a special kind of key. It is
1308
called a "key-path" because it is a key that functions like a path, as
1311
To use this feature, the resource group must have resources in it that are
1312
other resource paths, and those resource groups must have resources in
1313
them. Once this is asserted, the resources of one of the inner resource
1314
maps may be accessed by indexing the outermost resource group with a
1315
sequence of names. These names indicate the "path" taken to get from the
1316
outermost level of the resource group hierarchy to the inner resource
1321
resgroup = ResourceMixinGroup(
1322
foo = ResourceMixinGroup(
1323
innerFoo = resourceA,
1324
innerBar = resourceB
1329
myres = resgroup["foo", "innerFoo"] # 1. myres gets resourceA
1330
myres = resgroup["foo", "innerBar"] # 2. myres gets resourceB
1331
myres = resgroup["bar"] # 3. myres gets resourceC
1332
myres = resgroup["foo"]["innerFoo"] # 4. Same as 1
1333
myres = resgroup["foo"]["innerBar"] # 5. Same as 2
1335
location = ("foo", "innerFoo")
1336
myres = resgroup[location] # 6. Same as 1 and 4
1338
This feature is implemented with the intent of allowing locations within
1339
a complex resource tree to be stored as a single unit and used in a
1340
convenient way (such as example #6 above).
1342
TODO: Add support for custom reference types. (A reference is any
1343
potential resource representation that contains a key that begins with
1344
the reference symbol.)
1347
keyorder = "resources",
1349
def __init__(self, resources = (), **kwargs):
1350
""" Initializes an ResourceMixinGroupt with optional initial items
1352
ResourceMixinGroup.__init__(self) -> None
1353
ResourceMixinGroup.__init__(self, resources) -> None
1354
ResourceMixinGroup.__init__(self, name = resource [, ...]) ->
1356
resources: Must be a mapping or sequence of (name, resource) pairs.
1357
Defaults to an empty tuple.
1359
name = resource [, ...]:
1360
You may also provide resources as a series of keyword
1361
arguments, where the argument name is the key that will
1362
be used to access the resource and the value of the
1363
argument is the resource being added.
1365
Note that if you use both the `resources` argument as well as
1366
additional keyword arguments then both sets of data will be used,
1367
with the keyword arguments taking precedence if there is a naming
1370
resources = self._items = dict(resources)
1371
resources.update(kwargs)
1372
self._keyorder = sorted(resources.keys())
1376
def _unrep_resources(cls, value, jdata):
1377
""" Unrepper for `resources`.
1381
A mapping where keys indicate names of resources and the
1382
values are the representations of those resources, except
1383
for when the value is another mapping that contains a key
1384
that is one of the strings "&ref" or "©". In such a
1385
case, the value of the "&ref" or "©" key refers to
1386
a resource with the given name within the current
1389
Ex: {"&ref": "foo"} Indicates a reference to a resource
1390
named "foo" within the current
1391
ResourceMixinGroup. If the resource named "foo" has been
1392
unrepresented by the time {"&ref" : "foo"} is processed,
1393
{"&ref": "foo"} will become another occurence of "foo"
1394
within the curent ResourceMixinGroup. If "foo" has not yet been
1395
unrepresented, the name of {"&ref": "foo"} and the string
1396
"foo" itself will both be remembered, and as soon as "foo"
1397
is unrepresented, {"&ref": "foo"} will become an alternate
1400
Example value for "resources" key:
1404
"resource_foo": foo_representation_or_filename,
1405
"resource_bar": bar_representation_or_filename,
1406
"resource_spam": {"&ref": "resource_foo"}
1410
This method expects a classmethod called `resource_from_json`
1411
to be defined within the current ResourceMixinGroup
1412
subclass. It must be defined with a signature and return value
1413
as indicated by the following example method:
1416
def resource_from_json(cls, rep, jdata):
1417
''' Return an object based on a given rep.
1419
rep: Representation for an object of the type
1420
that is handled by the current
1421
ResourceMixinGroup subclass.
1423
jdata: The full JSON representation of the
1424
resource group that is currently being
1425
unrepresented. Provided in case there is
1426
some information in the resource group that
1427
affects all resources contained within.
1429
# ...processing using rep and/or jdata...
1432
`_unrep_resources` returns a mapping of loaded resources
1433
suitable for use as the parameter to the ResourceMixinGroup
1436
TODO: Make the use of "&" more generalized so that it can be used to create custom reference types
1439
# This will be used to store resources as they are loaded.
1442
# This will be used to store references that need to be defreferenced.
1445
# This will be used to store copy references that need to be copied.
1448
# Iterate over the resoruce representations
1449
for name, rep in value.iteritems():
1450
# See if its a string (ie. filename)
1451
if isinstance(rep,basestring):
1452
# It's a filename. Attempt to get the resource.
1453
resources[name] = get(rep)
1455
# Not a filename. See if its a regular reference.
1425
nameref = rep["&ref"]
1426
except (TypeError,KeyError):
1427
# It's not a regular reference.
1428
# See if its a copy reference.
1457
nameref = rep["&ref"]
1430
namecopy = rep["©"]
1458
1431
except (TypeError,KeyError):
1459
# It's not a regular reference.
1460
# See if its a copy reference.
1462
namecopy = rep["©"]
1463
except (TypeError,KeyError):
1464
# It's not a copy reference, either.
1465
# Assume it is standard jdata. Try to unrep it.
1466
resources[name] = cls.resource_from_json(rep, jdata)
1468
# Look for references waiting for this resource and
1469
# dereference any that exist.
1470
for nameref, refname in dict(refs_waiting).iteritems():
1472
resources[nameref] = resources[name]
1473
del refs_waiting[nameref]
1475
# Look for copy references waiting for this resource and
1476
# make copies for any that exist.
1477
for copyref, refname in dict(copies_waiting).iteritems():
1479
resources[copyref] =\
1480
cls.resource_from_json(rep, jdata)
1481
del copies_waiting[copyref]
1483
# It appears to be a copy reference. Try to make another
1484
# copy of the referenced resource.
1487
cls.resource_from_json(value[namecopy], jdata)
1489
# The resource being referenced does not yet exist.
1490
# Put this copyref on the mapping of references waiting
1492
copies_waiting[copyref] = name
1432
# It's not a copy reference, either.
1433
# Assume it is standard jdata. Try to unrep it.
1434
resources[name] = cls.resource_from_json(rep, jdata)
1436
# Look for references waiting for this resource and
1437
# dereference any that exist.
1438
for nameref, refname in dict(refs_waiting).iteritems():
1440
resources[nameref] = resources[name]
1441
del refs_waiting[nameref]
1443
# Look for copy references waiting for this resource and
1444
# make copies for any that exist.
1445
for copyref, refname in dict(copies_waiting).iteritems():
1447
resources[copyref] =\
1448
cls.resource_from_json(rep, jdata)
1449
del copies_waiting[copyref]
1494
# It appears to be a reference. Try to get the resource
1495
# that is being referenced.
1451
# It appears to be a copy reference. Try to make another
1452
# copy of the referenced resource.
1497
resources[name] = resources[nameref]
1455
cls.resource_from_json(value[namecopy], jdata)
1498
1456
except KeyError:
1499
1457
# The resource being referenced does not yet exist.
1500
# Put this nameref on the mapping of references waiting to
1502
refs_waiting[nameref] = name
1506
def __getitem__(self,key):
1507
""" Allows references to values (resources) in resgroups to be acquired.
1509
resgroup.__getitem__(key) <==> resgroup[key]
1511
This method supports key-paths.
1514
if isinstance(key,basestring):
1518
# This will only run (and succeed) if the key is a string and the
1519
# string is currently used as a key in the mapping.
1520
return self._items[key]
1524
location = location[k]
1527
def __setitem__(self,key,value):
1528
""" Allows resources in an ResourceMixinGroup to be added or replaced
1530
resgroup.__setitem__(key, value) <==> resgroup[key] = value
1532
This method supports key-paths.
1535
if isinstance(key,basestring):
1537
ikey = iter(key[:-1])
1539
if not isinstance(key, basestring):
1540
# This error is only raised if the key is NEITHER a string NOR
1541
# any other kind of iterable.
1543
"Only strings are allowed as keys. (Got %r)" % key
1546
# This will only run (and succeed) if the key is a string and the
1547
# string is currently used as a key in the mapping.
1548
self._items[key]= value
1551
# This block will run if the key is NOT a string, but is still iterable.
1555
location = location[k]
1556
location[key[-1]] = value
1559
self._keyorder = sorted(self._items.keys())
1561
def __delitem__(self, key):
1562
""" Allows an item with a given to be removed from ResourceMixinGroup objects.
1564
resgroup.__delitem__[key] <==> del resgroup[key]
1566
This method supports key-paths.
1569
if isinstance(key,basestring):
1571
ikey = iter(key[:-1])
1573
# This will only run (and succeed) if the key is a string and the
1574
# string is currently used as a key in the mapping.
1575
del self._items[key]
1576
# This block will run if the key is NOT a string, but is still iterable.
1580
location = location[k]
1581
del location[key[-1]]
1584
self._keyorder = sorted(self._items.keys())
1587
""" Allows the number of items in a ResourceMixinGroup object to be determined.
1589
resgroup.__len__() <==> len(resgroup)
1591
This method does NOT support key-paths.
1593
return len(self._items)
1595
def __contains__(self, key):
1596
""" Allows for checks to see if a particular key exists in the mapping.
1598
resgroup.__contains__(key) <==> key in resgroup
1600
This method supports key-paths.
1603
if isinstance(key,basestring):
1607
return key in self._items
1612
location = location[k]
1618
""" Allows iteration over the keys of ResourceMixinGroup objects.
1620
resgroup.__iter__() <==> iter(resgroup)
1622
Keys are yielded in sorted order.
1624
This method does NOT support key-paths.
1627
for k in self._keyorder:
1630
def iteritems(self):
1631
""" Returns an iterator over the key,value pairs in the mapping.
1633
resgroup.iteritems() -> pair_iterator
1637
for name, resource in resgroup.iteritems():
1638
print name, "=", resource
1640
Pairs are yielded in sorted order based on the key of each item.
1642
Note that the value returned by `ResourceMixinGroup.iteritems()` is only
1643
an iterator and not a collection. This means that it CANNOT be
1644
indexed, though it can be used in any place that respects the Python
1645
iteration protocol, such as a for loop.
1647
This method does NOT support key-paths.
1650
for k in self._keyorder:
1654
""" Returns all keys in the mapping as a sorted list.
1656
resgroup.keys() -> sorted_list
1658
This method does NOT support key-paths.
1660
return list(self._keyorder)
1663
""" Returns all values (resources) in the mapping as a list.
1665
resgroup.values() -> sorted_list
1667
The list items are ordered based on the sort order of the key that
1668
corresponds to each value for each key,value pair in the resgroup.
1669
This means, for example, that if you have a resgroup such as this
1672
resgroup = ResourceMixinGroup({
1678
then the list returned by `resgroup.values()` will look like this:
1680
[resourceA, resourceC, resourceB]
1682
This is because the alphanumeric order for the keys of the resgroup is
1683
["a", "d", "s"], not ["a", "s", "d"] as it was entered.
1685
This method does NOT support key-paths.
1688
return [items[k] for k in self._keyorder]
b'\\ No newline at end of file'
1458
# Put this copyref on the mapping of references waiting
1460
copies_waiting[copyref] = name
1462
# It appears to be a reference. Try to get the resource
1463
# that is being referenced.
1465
resources[name] = resources[nameref]
1467
# The resource being referenced does not yet exist.
1468
# Put this nameref on the mapping of references waiting to
1470
refs_waiting[nameref] = name
1474
def __getitem__(self,key):
1475
""" Allows references to values (resources) in resgroups to be acquired.
1477
resgroup.__getitem__(key) <==> resgroup[key]
1479
This method supports key-paths.
1482
if isinstance(key,basestring):
1486
# This will only run (and succeed) if the key is a string and the
1487
# string is currently used as a key in the mapping.
1488
return self._items[key]
1492
location = location[k]
1495
def __setitem__(self,key,value):
1496
""" Allows resources in an ResourceMixinGroup to be added or replaced
1498
resgroup.__setitem__(key, value) <==> resgroup[key] = value
1500
This method supports key-paths.
1503
if isinstance(key,basestring):
1505
ikey = iter(key[:-1])
1507
if not isinstance(key, basestring):
1508
# This error is only raised if the key is NEITHER a string NOR
1509
# any other kind of iterable.
1511
"Only strings are allowed as keys. (Got %r)" % key
1514
# This will only run (and succeed) if the key is a string and the
1515
# string is currently used as a key in the mapping.
1516
self._items[key]= value
1519
# This block will run if the key is NOT a string, but is still iterable.
1523
location = location[k]
1524
location[key[-1]] = value
1527
self._keyorder = sorted(self._items.keys())
1529
def __delitem__(self, key):
1530
""" Allows an item with a given to be removed from ResourceMixinGroup objects.
1532
resgroup.__delitem__[key] <==> del resgroup[key]
1534
This method supports key-paths.
1537
if isinstance(key,basestring):
1539
ikey = iter(key[:-1])
1541
# This will only run (and succeed) if the key is a string and the
1542
# string is currently used as a key in the mapping.
1543
del self._items[key]
1544
# This block will run if the key is NOT a string, but is still iterable.
1548
location = location[k]
1549
del location[key[-1]]
1552
self._keyorder = sorted(self._items.keys())
1555
""" Allows the number of items in a ResourceMixinGroup object to be determined.
1557
resgroup.__len__() <==> len(resgroup)
1559
This method does NOT support key-paths.
1561
return len(self._items)
1563
def __contains__(self, key):
1564
""" Allows for checks to see if a particular key exists in the mapping.
1566
resgroup.__contains__(key) <==> key in resgroup
1568
This method supports key-paths.
1571
if isinstance(key,basestring):
1575
return key in self._items
1580
location = location[k]
1586
""" Allows iteration over the keys of ResourceMixinGroup objects.
1588
resgroup.__iter__() <==> iter(resgroup)
1590
Keys are yielded in sorted order.
1592
This method does NOT support key-paths.
1595
for k in self._keyorder:
1598
def iteritems(self):
1599
""" Returns an iterator over the key,value pairs in the mapping.
1601
resgroup.iteritems() -> pair_iterator
1605
for name, resource in resgroup.iteritems():
1606
print name, "=", resource
1608
Pairs are yielded in sorted order based on the key of each item.
1610
Note that the value returned by `ResourceMixinGroup.iteritems()` is only
1611
an iterator and not a collection. This means that it CANNOT be
1612
indexed, though it can be used in any place that respects the Python
1613
iteration protocol, such as a for loop.
1615
This method does NOT support key-paths.
1618
for k in self._keyorder:
1622
""" Returns all keys in the mapping as a sorted list.
1624
resgroup.keys() -> sorted_list
1626
This method does NOT support key-paths.
1628
return list(self._keyorder)
1631
""" Returns all values (resources) in the mapping as a list.
1633
resgroup.values() -> sorted_list
1635
The list items are ordered based on the sort order of the key that
1636
corresponds to each value for each key,value pair in the resgroup.
1637
This means, for example, that if you have a resgroup such as this
1640
resgroup = ResourceMixinGroup({
1646
then the list returned by `resgroup.values()` will look like this:
1648
[resourceA, resourceC, resourceB]
1650
This is because the alphanumeric order for the keys of the resgroup is
1651
["a", "d", "s"], not ["a", "s", "d"] as it was entered.
1653
This method does NOT support key-paths.
1656
return [items[k] for k in self._keyorder]
b'\\ No newline at end of file'