213
107
logger.debug('Set XDG_CONFIG_DIRS to %s', XDG_CONFIG_DIRS)
214
108
logger.debug('Set XDG_CACHE_HOME to %s', XDG_CACHE_HOME)
217
def data_dirs(path=None):
218
'''Generator listing paths that contain zim data files in the order
219
that they should be searched. These will be the equivalent of
220
e.g. "~/.local/share/zim", "/usr/share/zim", etc.
221
@param path: a file path relative to to the data dir, including this
222
will list sub-folders with this relative path.
223
@returns: yields L{Dir} objects for the data dirs
227
if isinstance(path, basestring):
229
assert not path[0] == 'zim'
232
yield XDG_DATA_HOME.subdir(zimpath)
236
yield ZIM_DATA_DIR.subdir(path)
240
for dir in XDG_DATA_DIRS:
241
yield dir.subdir(zimpath)
244
'''Get an data dir sub-folder. Will look up C{path} relative
245
to all data dirs and return the first one that exists. Use this
246
function to find any folders from the "data/" folder in the source
248
@param path: a file path relative to to the data dir
249
@returns: a L{Dir} object or C{None}
251
for dir in data_dirs(path):
258
'''Get a data file. Will look up C{path} relative to all data dirs
259
and return the first one that exists. Use this function to find
260
any files from the "data/" folder in the source package.
261
@param path: a file path relative to to the data dir (e.g. "zim.png")
262
@returns: a L{File} object or C{None}
264
for dir in data_dirs():
265
file = dir.file(path)
272
'''Generator listing paths for zim config files. These will be the
273
equivalent of e.g. "~/.config/zim", "/etc/xdg/zim" etc.
275
Zim is not strictly XDG conformant by installing default config
276
files in "/usr/share/zim" instead of in "/etc/xdg/zim". Therefore
277
this function yields both.
279
@returns: yields L{Dir} objects for all config and data dirs
281
yield XDG_CONFIG_HOME.subdir(('zim'))
282
for dir in XDG_CONFIG_DIRS:
283
yield dir.subdir(('zim'))
284
for dir in data_dirs():
288
def config_file(path):
289
'''Alias for constructing a L{ConfigFile} object
290
@param path: either basename as string or tuple with relative path
291
@returns: a L{ConfigFile}
293
return ConfigFile(path)
296
def get_config(path):
297
'''Convenience method to construct a L{ConfigDictFile} based on a
299
@param path: either basename as string or tuple with relative path
300
@returns: a L{ConfigDictFile}
302
file = ConfigFile(path)
303
return ConfigDictFile(file)
307
'''Returns a list known preferences profiles.'''
309
for dir in config_dirs():
310
for f in dir.subdir('profiles').list():
311
if f.endswith('.conf'):
312
profiles.append(f[:-5])
318
'''Get the XDG user dirs.
319
@returns: a dict with directories for the XDG user dirs. These are
320
typically defined in "~/.config/user-dirs.dirs". Common user dirs
321
are: "XDG_DESKTOP_DIR", "XDG_DOWNLOAD_DIR", etc. If no definition
322
is found an empty dict will be returned.
325
file = XDG_CONFIG_HOME.file('user-dirs.dirs')
327
for line in file.readlines():
329
if line.isspace() or line.startswith('#'):
334
key, value = line.split('=', 1)
335
value = os.path.expandvars(value.strip('"'))
336
dirs[key] = Dir(value)
338
logger.exception('Exception while parsing %s', file)
339
except FileNotFoundError:
344
class ConfigFile(object):
345
'''Container object for a config file
347
Maps to a "base" file in the home folder, used to write new values,
348
and one or more default files, e.g. in C{/usr/share/zim}, which
349
are the fallback to get default values
351
@ivar file: the underlying file object for the base config file
354
@note: this class implement similar API to the L{File} class but
355
is explicitly not a sub-class of L{File} because config files should
356
typically not be moved, renamed, etc. It just implements the reading
360
def __init__(self, path, file=None):
362
@param path: either basename as string or tuple with relative path,
363
is resolved relative to the default config dir for zim.
364
@param file: optional argument for some special case to
365
override the base file in the home folder.
367
if isinstance(path, basestring):
369
self._path = tuple(path)
373
self.file = File((XDG_CONFIG_HOME, 'zim') + self._path)
376
return '<%s: %s>' % (self.__class__.__name__, self.file.path)
378
def __eq__(self, other):
379
return isinstance(other, ConfigFile) \
380
and other._path == self._path \
381
and other.file == self.file
385
return self.file.basename
387
def default_files(self):
388
'''Generator that yields default config files (read-only) to
389
use instead of the standard file when it is still empty.
390
Typically only the first one is used.
392
for dir in config_dirs():
393
default = dir.file(self._path)
398
'''Ensure the custom file in the home folder exists. Either by
399
copying a default config file, or touching an empty file.
400
Intended to be called before trying to edit the file with an
403
if not self.file.exists():
404
for file in self.default_files():
405
file.copyto(self.file)
408
self.file.touch() # create empty file
410
def read(self, fail=False):
411
'''Read the base file or first default file
412
@param fail: if C{True} a L{FileNotFoundError} error is raised
413
when neither the base file or a default file are found. If
414
C{False} it will return C{''} for a non-existing file.
415
@returns: file content as a string
418
return self.file.read()
419
except FileNotFoundError:
420
for file in self.default_files():
428
def readlines(self, fail=False):
429
'''Read the base file or first default file
430
@param fail: if C{True} a L{FileNotFoundError} error is raised
431
when neither the base file or a default file are found. If
432
C{False} it will return C{[]} for a non-existing file.
433
@returns: file content as a list of lines
436
return self.file.readlines()
437
except FileNotFoundError:
438
for file in self.default_files():
439
return file.readlines()
446
# Not implemented: read_async and readlines_async
448
def write(self, text):
449
'''Write base file, see L{File.write()}'''
450
self.file.write(text)
452
def writelines(self, lines):
453
'''Write base file, see L{File.writelines()}'''
454
self.file.writelines(lines)
456
def write_async(self, text, callback=None, data=None):
457
'''Write base file async, see L{File.write_async()}'''
458
return self.file.write_async(text, callback=callback, data=data)
460
def writelines_async(self, lines, callback=None, data=None):
461
'''Write base file async, see L{File.writelines_async()}'''
462
return self.file.writelines_async(lines, callback=callback, data=data)
465
'''Remove user file, leaves default files in place'''
466
if self.file.exists():
467
return self.file.remove()
470
def check_class_allow_empty(value, default):
471
'''Check function for L{ListDict.setdefault()} which ensures the
472
value is of the same class as the default if it is set, but also
473
allows it to be empty (empty string or C{None}). This is
474
the same as the default behavior when "C{allow_empty}" is C{True}.
475
It will convert C{list} type to C{tuple} automatically if the
478
This function can be used in cases where the check is provided
479
inderictly and C{allow_empty} can not be passed along, e.g.
480
in the definition of plugin preferences.
482
@param value: the value in the dict
483
@param default: the default that is set
484
@returns: the new value to set
485
@raises AssertionError: when the value if of the wrong class
486
(which will result in C{setdefault()} setting the default value)
488
klass = default.__class__
489
if issubclass(klass, basestring):
492
if value in ('', None) or isinstance(value, klass):
494
elif klass is tuple and isinstance(value, list):
495
# Special case because json does not know difference list or tuple
498
raise AssertionError, 'should be of type: %s' % klass
501
def value_is_coord(value, default):
502
'''Check function for L{ListDict.setdefault()} which will check
503
whether the value is a coordinate (a tuple of two integers). This
504
is e.g. used to store for window coordinates. If the value is a
505
list of two integers, it will automatically be converted to a tuple.
507
@param value: the value in the dict
508
@param default: the default that is set
509
@returns: the new value to set
510
@raises AssertionError: when the value is not a coordinate tuple
511
(which will result in C{setdefault()} setting the default value)
513
if isinstance(value, list):
517
isinstance(value, tuple)
519
and isinstance(value[0], int)
520
and isinstance(value[1], int)
524
raise AssertionError, 'should be coordinate (tuple of int)'
527
class ListDict(dict):
528
'''Class that behaves like a dict but keeps items in same order.
529
This is the base class for all dicts holding config items in zim.
530
Most importantly it is used for each section in the L{ConfigDict}.
531
Because it remembers the order of the items in the dict, the order
532
in which they will be written to a config file is predictable.
533
Another important function is to check the config values have
534
proper values, this is enforced by L{setdefault()}.
536
@ivar modified: C{True} when the values were modified, used to e.g.
537
track when a config needs to be written back to file
542
self._modified = False
545
return '<%s: %s>' % (self.__class__.__name__, dict.__repr__(self))
548
'''Shallow copy of the items
549
@returns: a new object of the same class with the same items
551
new = self.__class__()
560
return any(v.modified for v in self.values()
561
if isinstance(v, ListDict))
563
def set_modified(self, modified):
564
'''Set the modified state. Used to reset modified to C{False}
565
after the configuration has been saved to file.
566
@param modified: C{True} or C{False}
569
self._modified = True
571
self._modified = False
572
for v in self.values():
573
if isinstance(v, ListDict):
574
v.set_modified(False)
576
def update(D, E=None, **F):
577
'''Like C{dict.update()}'''
578
if E and hasattr(E, 'keys'):
579
for k in E: D[k] = E[k]
581
for (k, v) in E: D[k] = v
582
for k in F: D[k] = F[k]
584
def __setitem__(self, k, v):
585
dict.__setitem__(self, k, v)
586
self._modified = True
587
if not k in self.order:
590
def __delitem__(self, k):
591
dict.__delitem__(self, k)
595
return iter(self.order)
598
'''Like C{dict.pop()}'''
599
v = dict.pop(self, k)
603
def setdefault(self, key, default, check=None, allow_empty=False):
604
'''Set the default value for a configuration item.
606
Compatible with C{dict.setdefault()} but extended with
607
functionality to check the value that is in the dict, and use
608
the default if the value is mal-formed. This is used extensively
609
in zim to do a sanity check on values in the configuration
610
files. If you initialize the config items with this method you
611
can assume them to be safe afterward and avoid a lot of checks
612
or bugs later in the code.
614
@param key: the dict key
615
@param default: the default value for this key
617
@param check: the check to do on the values, when the check
618
fails the value is considered mal-formed and the default is
619
used while a warning is logged.
621
If C{check} is C{None} the default behavior will be to compare
622
the classes of the set value and the default and enforce them to
623
be of the same type. Automatic conversion is done for values of
624
type C{list} with defaults of type C{tuple}. And for defaults of
625
type C{str} or C{unicode} the C{basestring} type is used as
626
check. As a special case when the default is C{None} the check
627
is not allowed to be C{None} as well.
629
If C{check} is given and it is a class the existing value will be
630
checked to be of that class. Same special case for tuples
631
and strings applies here.
633
If C{check} is given and is a C{set}, C{list} or C{tuple} the
634
value will be tested to be in this set or list.
636
If the default is an integer and C{check} is a tuple of two
637
integers, the check will be that the value is in this range.
638
(For compatibility with L{InputForm} extra argument for integer
641
If C{check} is given and it is a function it will be used to
642
check the value in the dictionary if it exists. The function
645
check(value, default)
647
Where C{value} is the current value in the dict and C{default}
648
is the default value that was provided. The function can not
649
only check the value, it can also do on the fly modifications,
650
e.g. to coerce it into a specific type. If the value is OK the
651
function should return the (modified) value, if not it should
652
raise an C{AssertionError}. When this error is raised the
653
default is used and the dict is considered being modified.
655
( Note that 'assert' statements in the code can be removed
656
by code optimization, so explicitly call "C{raise AssertionError}". )
658
Examples of functions that can be used as a check are:
659
L{check_class_allow_empty} and L{value_is_coord}.
661
@param allow_empty: if C{True} the value is allowed to be empty
662
(either empty string or C{None}). In this case the default is
663
not set to overwrite an empty value, but only for a mal-formed
664
value or for a value that doesn't exist yet in the dict.
666
assert not (default is None and check is None), \
667
'Bad practice to set default to None without check'
670
self.__setitem__(key, default)
674
klass = default.__class__
675
if issubclass(klass, basestring):
679
if default in ('', None):
682
if allow_empty and self[key] in ('', None):
685
if isinstance(check, (type, types.ClassType)): # is a class
687
if not (allow_empty and default in ('', None)):
688
assert isinstance(default, klass), 'Default does not have correct class'
690
if not isinstance(self[key], klass):
691
if klass is tuple and isinstance(self[key], list):
692
# Special case because json does not know difference list or tuple
693
modified = self.modified
694
self.__setitem__(key, tuple(self[key]))
695
self.set_modified(modified) # don't change modified state
696
elif hasattr(klass, 'new_from_zim_config'):
697
# Class has special contructor
698
modified = self.modified
700
self.__setitem__(key, klass.new_from_zim_config(self[key]))
703
'Invalid config value for %s: "%s"',
705
self.set_modified(modified) # don't change modified state
708
'Invalid config value for %s: "%s" - should be of type %s',
709
key, self[key], klass)
710
self.__setitem__(key, default)
711
elif self[key] == '':
712
# Special case for empty string
714
'Invalid config value for %s: "%s" - not allowed to be empty',
716
self.__setitem__(key, default)
719
elif isinstance(check, (set, list)) \
720
or (isinstance(check, tuple) and not isinstance(default, int)):
721
if not (allow_empty and default in ('', None)):
722
# HACK to allow for preferences with "choice" item that has
723
# a list of tuples as argumnet
724
if all(isinstance(t, tuple) for t in check):
725
check = list(check) # copy
726
check += [t[0] for t in check]
727
assert default in check, 'Default is not within allowed set'
729
# HACK to allow the value to be a tuple...
730
if all(isinstance(t, tuple) for t in check) \
731
and isinstance(self[key], list):
732
modified = self.modified
733
self.__setitem__(key, tuple(self[key]))
734
self.set_modified(modified)
736
if not self[key] in check:
738
'Invalid config value for %s: "%s" - should be one of %s',
739
key, self[key], unicode(check))
740
self.__setitem__(key, default)
743
elif isinstance(check, tuple) and isinstance(default, int):
744
assert len(check) == 2 \
745
and isinstance(check[0], int) \
746
and isinstance(check[1], int)
747
if not isinstance(self[key], int):
749
'Invalid config value for %s: "%s" - should be integer',
751
self.__setitem__(key, default)
752
elif not check[0] <= self[key] <= check[1]:
754
'Invalid config value for %s: "%s" - should be between %i and %i',
755
key, self[key], check[0], check[1])
756
self.__setitem__(key, default)
759
else: # assume callable
760
modified = self.modified
762
v = check(self[key], default)
763
self.__setitem__(key, v)
764
self.set_modified(modified)
765
except AssertionError, error:
767
'Invalid config value for %s: "%s" - %s',
768
key, self[key], error.args[0])
769
self.__setitem__(key, default)
774
'''Like C{dict.keys()}'''
778
'''Like C{dict.items()}'''
779
return tuple(map(lambda k: (k, self[k]), self.order))
781
def set_order(self, order):
782
'''Change the order in which items are listed.
784
@param order: a list of keys in a specific order. Items in the
785
dict that do not appear in the list will be moved to the end.
786
Items in the list that are not in the dict are ignored.
788
order = list(order[:]) # copy and convert
789
oldorder = set(self.order)
790
neworder = set(order)
791
for k in neworder - oldorder: # keys not in the dict
793
for k in oldorder - neworder: # keys not in the list
795
neworder = set(order)
796
assert neworder == oldorder
800
class ConfigDict(ListDict):
801
'''Dict to represent a configuration file in "ini-style". Since the
802
ini-file is devided in section this is represented as a dict of
803
dicts. This class represents the top-level with a key for each
804
section. The values are in turn L{ListDict}s which contain the
805
key value pairs in that section.
807
A typical file might look like::
815
data={'foo': 1, 'bar': 2}
817
values can either be simple string, number, or one of "True",
818
"False" and "None", or a complex data structure encoded with the
821
Sections are auto-vivicated when a non-existing item is retrieved.
823
By default when parsing sections of the same name they will be
824
merged and values that appear under the same section name later in
825
the file will overwrite values that appeared earlier. As a special
826
case we can support sections that repeat under the same section name.
827
To do this assign the section name a list before parsing.
829
Sections and parameters whose name start with '_' are considered as
830
private and are not stored when the config is written to file. This
831
can be used for caching values that should not be persistent across
835
def __getitem__(self, k):
838
return dict.__getitem__(self, k)
840
def parse(self, text):
841
'''Parse an "ini-style" configuration. Fills the dictionary
842
with values from this text, wil merge with existing sections and
843
overwrite existing values.
844
@param text: a string or a list of lines
846
# Note that we explicitly do _not_ support comments on the end
847
# of a line. This is because "#" could be a valid character in
849
if isinstance(text, basestring):
850
text = text.splitlines(True)
854
if not line or line.startswith('#'):
856
elif line.startswith('[') and line.endswith(']'):
857
name = line[1:-1].strip()
859
if isinstance(section, list):
860
section.append(ListDict())
861
section = section[-1]
863
parameter, rawvalue = line.split('=', 1)
864
parameter = str(parameter.rstrip()) # no unicode
865
rawvalue = rawvalue.lstrip()
867
value = self._decode_value(parameter, rawvalue)
868
section[parameter] = value
870
logger.warn('Failed to parse value for key "%s": %s', parameter, rawvalue)
872
logger.warn('Could not parse line: %s', line)
874
# Separated out as this will be slightly different for .desktop files
875
# we ignore the key - but DesktopEntryDict uses them
876
def _decode_value(self, key, value):
879
if value == 'True': return True
880
elif value == 'False': return False
881
elif value == 'None': return None
882
elif value[0] in ('{', '['):
883
return json.loads(value)
895
return json.loads('"%s"' % value.replace('"', r'\"')) # force string
898
'''Serialize the config to a "ini-style" config file.
899
@returns: a list of lines with text in "ini-style" formatting
902
def dump_section(name, parameters):
904
lines.append('[%s]\n' % section)
905
for param, value in parameters.items():
906
if not param.startswith('_'):
907
lines.append('%s=%s\n' % (param, self._encode_value(value)))
910
logger.exception('Dumping section [%s] failed:\n%r', name, parameters)
912
for section, parameters in self.items():
913
if parameters and not section.startswith('_'):
914
if isinstance(parameters, list):
915
for param in parameters:
916
dump_section(section, param)
918
dump_section(section, parameters)
922
def _encode_value(self, value):
923
if isinstance(value, basestring):
924
return json.dumps(value)[1:-1] # get rid of quotes
925
elif value is True: return 'True'
926
elif value is False: return 'False'
927
elif value is None: return 'None'
928
elif hasattr(value, 'serialize_zim_config'):
929
return value.serialize_zim_config()
931
return json.dumps(value, separators=(',',':'))
932
# specify separators for compact encoding
935
class ConfigFileMixin(ListDict):
936
'''Mixin class for reading and writing config to file, can be used
937
with any parent class that has a C{parse()}, a C{dump()}, and a
938
C{set_modified()} method. See L{ConfigDict} for the documentation
942
def __init__(self, file):
944
@param file: a L{File} or L{ConfigFile} object for reading and
947
ListDict.__init__(self)
951
self.set_modified(False)
952
except FileNotFoundError:
956
'''Read data from file'''
957
# No flush here - this is used by change_file()
958
# but may change in the future - so do not depend on it
959
logger.debug('Loading config from: %s', self.file)
960
self.parse(self.file.readlines())
961
# Will fail with FileNotFoundError if file does not exist
964
'''Write data and set C{modified} to C{False}'''
965
self.file.writelines(self.dump())
966
self.set_modified(False)
968
def write_async(self):
969
'''Write data asynchronously and set C{modified} to C{False}
970
@returns: an L{AsyncOperation} object
972
operation = self.file.writelines_async(self.dump())
973
# TODO do we need async error handling here ?
974
self.set_modified(False)
977
def change_file(self, file, merge=True):
978
'''Change the underlaying file used to read/write data
979
Used to switch to a new config file without breaking existing
980
references to config sections.
981
@param file: a L{File} or L{ConfigFile} object for the new config
982
@param merge: if C{True} the new file will be read (if it exists)
983
and values in this dict will be updated.
988
self.set_modified(True)
989
# This is the correct state because after reading we are
990
# merged state, so does not matching file content
991
except FileNotFoundError:
995
class ConfigDictFile(ConfigFileMixin, ConfigDict):
999
class HeaderParsingError(Error):
1000
'''Error when parsing a L{HeadersDict}'''
1003
Invalid data was found in a block with headers.
1004
This probably means the header block was corrupted
1005
and can not be read correctly.'''
1007
def __init__(self, line):
1008
self.msg = 'Invalid header >>%s<<' % line.strip('\n')
1011
class HeadersDict(ListDict):
1012
'''This class maps a set of headers in the rfc822 format.
1013
Can e.g. look like::
1015
Content-Type: text/x-zim-wiki
1016
Wiki-Format: zim 0.4
1017
Creation-Date: 2010-12-14T14:15:09.134955
1019
Header names are always kept in "title()" format to ensure
1023
_is_header_re = re.compile('^([\w\-]+):\s+(.*)')
1024
_is_continue_re = re.compile('^(\s+)(?=\S)')
1026
def __init__(self, text=None):
1029
@param text: the header text, passed on to L{parse()}
1031
ListDict.__init__(self)
1032
if not text is None:
1035
def __getitem__(self, k):
1036
return ListDict.__getitem__(self, k.title())
1038
def __setitem__(self, k, v):
1039
return ListDict.__setitem__(self, k.title(), v)
1041
def read(self, lines):
1042
'''Checks for headers at the start of the list of lines and
1043
read them into the dict until the first empty line. Will remove
1044
any lines belonging to the header block from the original list,
1045
so after this method returns the input does no longer contain
1047
@param lines: a list of lines
1049
self._parse(lines, fatal=False)
1050
if lines and lines[0].isspace():
1053
def parse(self, text):
1054
'''Adds headers defined in 'text' to the dict.
1055
Trailing whitespace is ignored.
1057
@param text: a header block, either as string or as a list of lines.
1059
@raises HeaderParsingError: when C{text} is not a valid header
1062
if isinstance(text, basestring):
1063
lines = text.rstrip().splitlines(True)
1065
lines = text[:] # make copy so we do not destry the original
1068
def _parse(self, lines, fatal=True):
1071
is_header = self._is_header_re.match(lines[0])
1073
header = is_header.group(1)
1074
value = is_header.group(2)
1075
self[header] = value.strip()
1076
elif self._is_continue_re.match(lines[0]) and not header is None:
1077
self[header] += '\n' + lines[0].strip()
1080
raise HeaderParsingError, lines[0]
1085
def dump(self, strict=False):
1086
'''Serialize the dict to a header block in rfc822 header format.
1088
@param strict: if C{True} lines will be properly terminated
1089
with '\\r\\n' instead of '\\n'.
1091
@returns: the header block as a list of lines
1094
for k, v in self.items():
1095
v = v.strip().replace('\n', '\n\t')
1096
buffer.extend((k, ': ', v, '\n'))
1097
text = ''.join(buffer)
1100
text = text.replace('\n', '\r\n')
1102
return text.splitlines(True)
1105
class HierarchicDict(object):
1106
'''This class implements a data store that behaves as a hierarchig
1107
dict of dicts. Each key in this object is considered a hierarchic
1108
path (the path separator is ':' for obvious reasons). The dict for
1109
each key will "inherit" all values from parent paths. However
1110
setting a new value will set it specifically for that key, without
1111
changing the value in the "parents". This is specifically used to store
1112
namespace properties for zim notebooks. So each child namespace will
1113
inherit the properties of it's parents unless it was explicitly
1114
set for that child namespace.
1116
There is a special member dict stored under the key "__defaults__"
1117
which has the top-level fallback properties.
1119
Child dicts are auto-vivicated, so this object only implements
1120
C{__getitem__()} but no C{__setitem__()}.
1122
# Note that all the magic is actually implemented by HierarchicDictFrame
1124
__slots__ = ('dict',)
1126
def __init__(self, defaults=None):
1129
@param defaults: dict with the default properties
1132
self.dict['__defaults__'] = defaults or {}
1134
def __getitem__(self, k):
1135
if not isinstance(k, basestring):
1136
k = k.name # assume zim path
1137
return HierarchicDictFrame(self.dict, k)
1140
class HierarchicDictFrame(object):
1141
'''Object acts as a member dict for L{HierarchicDict}'''
1143
__slots__ = ('dict', 'key')
1145
def __init__(self, dict, key):
1148
@param dict: the dict used to store the properties per namespace
1149
(internal in HierarchicDict)
1150
@param key: the key for this member dict
1157
parts = self.key.split(':')
1160
yield ':'.join(parts)
1162
yield '' # top level namespace
1164
def get(self, k, default=None):
1166
v = self.__getitem__(k)
1172
def __getitem__(self, k):
1173
for key in self._keys():
1174
if key in self.dict and k in self.dict[key]:
1175
return self.dict[key][k]
1177
if k in self.dict['__defaults__']:
1178
return self.dict['__defaults__'][k]
1182
def __setitem__(self, k, v):
1183
if not self.key in self.dict:
1184
self.dict[self.key] = {}
1185
self.dict[self.key][k] = v
1187
def remove(self, k):
1188
if self.key in self.dict and k in self.dict[self.key]:
1189
return self.dict[self.key].pop(k)