Page Menu
Home
Phabricator
Search
Configure Global Search
Log In
Files
F7312475
__init__.py
No One
Temporary
Actions
Download File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
25 KB
Referenced Files
None
Subscribers
None
__init__.py
View Options
# Copyright (C) 2008-2011 AG Projects. See LICENSE for details.
#
"""Generic configuration management"""
from
__future__
import
with_statement
__all__
=
[
'ConfigurationManager'
,
'ConfigurationError'
,
'ObjectNotFoundError'
,
'DuplicateIDError'
,
'DefaultValue'
,
'Setting'
,
'CorrelatedSetting'
,
'SettingsGroup'
,
'SettingsObjectID'
,
'SettingsObject'
,
'SettingsObjectExtension'
]
from
itertools
import
chain
from
threading
import
Lock
from
weakref
import
WeakKeyDictionary
from
application
import
log
from
application.notification
import
NotificationCenter
from
application.python.util
import
Singleton
from
sipsimple.threading
import
run_in_thread
from
sipsimple.util
import
TimestampedNotificationData
## Exceptions
class
ConfigurationError
(
Exception
):
pass
class
ObjectNotFoundError
(
ConfigurationError
):
pass
class
DuplicateIDError
(
ValueError
):
pass
class
PersistentKey
(
unicode
):
persistent
=
True
def
__repr__
(
self
):
return
"
%s
(
%s
)"
%
(
self
.
__class__
.
__name__
,
unicode
.
__repr__
(
self
))
## ConfigurationManager
class
ConfigurationManager
(
object
):
"""
Singleton class used for storing and retrieving options, organized in
sections. A section contains a list of objects, each with an assigned name
which allows access to the object.
"""
__metaclass__
=
Singleton
def
__init__
(
self
):
self
.
backend
=
None
self
.
data
=
None
def
start
(
self
):
"""
Initialize the ConfigurationManager to use the specified backend. This
method can only be called once, with an object which provides IBackend.
The other methods of the object cannot be used unless this method was
called.
"""
from
sipsimple.application
import
SIPApplication
from
sipsimple.configuration.backend
import
IConfigurationBackend
if
self
.
backend
is
not
None
:
raise
RuntimeError
(
"ConfigurationManager already started"
)
if
SIPApplication
.
storage
is
None
:
raise
RuntimeError
(
"SIPApplication.storage must be defined before starting the ConfigurationManager"
)
backend
=
SIPApplication
.
storage
.
configuration_backend
if
not
IConfigurationBackend
.
providedBy
(
backend
):
raise
TypeError
(
"SIPApplication.storage.configuration_backend must implement the IConfigurationBackend interface"
)
self
.
data
=
backend
.
load
()
self
.
backend
=
backend
def
update
(
self
,
key
,
data
):
"""
Save the object's data under the tree path specified by key (a list
of strings). Cannot be called before start().
"""
if
self
.
backend
is
None
:
raise
RuntimeError
(
"ConfigurationManager cannot be used unless started"
)
if
not
key
:
raise
KeyError
(
"key cannot be empty"
)
self
.
_update
(
self
.
data
,
list
(
key
),
data
)
def
rename
(
self
,
old_key
,
new_key
):
"""
Rename the object identified by old_key to new_key (list of strings).
Cannot be called before start().
"""
if
self
.
backend
is
None
:
raise
RuntimeError
(
"ConfigurationManager cannot be used unless started"
)
if
not
old_key
or
not
new_key
:
raise
KeyError
(
"old_key and/or new_key cannot be empty"
)
try
:
data
=
self
.
_pop
(
self
.
data
,
list
(
old_key
))
except
KeyError
:
raise
ObjectNotFoundError
(
"object
%s
does not exist"
%
'/'
.
join
(
old_key
))
self
.
_insert
(
self
.
data
,
list
(
new_key
),
data
)
def
delete
(
self
,
key
):
"""
Delete the object in the tree path specified by key (list of strings).
Cannot be called before start().
"""
if
self
.
backend
is
None
:
raise
RuntimeError
(
"ConfigurationManager cannot be used unless started"
)
if
not
key
:
raise
KeyError
(
"key cannot be empty"
)
try
:
self
.
_pop
(
self
.
data
,
list
(
key
))
except
KeyError
:
pass
def
get
(
self
,
key
):
"""
Get the object in the tree path specified by key (list of strings).
Raises ObjectNotFoundError if the object does not exist. Cannot be
called before start().
"""
if
self
.
backend
is
None
:
raise
RuntimeError
(
"ConfigurationManager cannot be used unless started"
)
if
not
key
:
raise
KeyError
(
"key cannot be empty"
)
try
:
return
self
.
_get
(
self
.
data
,
list
(
key
))
except
KeyError
:
raise
ObjectNotFoundError
(
"object
%s
does not exist"
%
'/'
.
join
(
key
))
def
get_names
(
self
,
key
):
"""
Get all the names under the specified key (a list of strings).
Returns a list containing the names. Cannot be called before start().
"""
if
self
.
backend
is
None
:
raise
RuntimeError
(
"ConfigurationManager cannot be used unless started"
)
if
not
key
:
raise
KeyError
(
"key cannot be empty"
)
try
:
data
=
self
.
_get
(
self
.
data
,
list
(
key
))
return
data
.
keys
()
except
:
return
[]
def
save
(
self
):
"""
Flush the modified objects. Cannot be called before start().
"""
if
self
.
backend
is
None
:
raise
RuntimeError
(
"ConfigurationManager cannot be used unless started"
)
self
.
backend
.
save
(
self
.
data
)
def
_get
(
self
,
data_tree
,
key
):
subtree_key
=
key
.
pop
(
0
)
data_subtree
=
data_tree
[
subtree_key
]
if
key
:
return
self
.
_get
(
data_subtree
,
key
)
else
:
return
data_subtree
def
_insert
(
self
,
data_tree
,
key
,
data
):
subtree_key
=
key
.
pop
(
0
)
data_subtree
=
data_tree
.
setdefault
(
subtree_key
,
{})
if
key
:
self
.
_insert
(
data_subtree
,
key
,
data
)
else
:
data_subtree
.
update
(
data
)
def
_pop
(
self
,
data_tree
,
key
):
subtree_key
=
key
.
pop
(
0
)
data_subtree
=
data_tree
[
subtree_key
]
if
key
:
data
=
self
.
_pop
(
data_subtree
,
key
)
persistent_key
=
getattr
(
subtree_key
,
'persistent'
,
False
)
if
not
persistent_key
and
not
data_subtree
:
del
data_tree
[
subtree_key
]
return
data
else
:
return
data_tree
.
pop
(
subtree_key
)
def
_update
(
self
,
data_tree
,
key
,
data
):
subtree_key
=
key
.
pop
(
0
)
data_subtree
=
data_tree
.
setdefault
(
subtree_key
,
{})
if
key
:
self
.
_update
(
data_subtree
,
key
,
data
)
else
:
self
.
_update_dict
(
data_subtree
,
data
)
persistent_key
=
getattr
(
subtree_key
,
'persistent'
,
False
)
if
not
persistent_key
and
not
data_subtree
:
del
data_tree
[
subtree_key
]
def
_update_dict
(
self
,
old_data
,
new_data
):
for
key
,
value
in
new_data
.
iteritems
():
if
value
is
DefaultValue
:
old_data
.
pop
(
key
,
None
)
elif
type
(
value
)
is
dict
:
if
key
in
old_data
and
type
(
old_data
[
key
])
is
not
dict
:
del
old_data
[
key
]
self
.
_update_dict
(
old_data
.
setdefault
(
key
,
{}),
value
)
if
not
old_data
[
key
]:
del
old_data
[
key
]
else
:
old_data
[
key
]
=
value
## Descriptors and base classes used for represeting configuration settings
class
DefaultValue
(
object
):
"""
This object can be set as the value for a setting and it will reset the
setting to the default value.
"""
class
ModifiedValue
(
object
):
"""
Instances of this class represent the state (the old and new values) of
settings.
"""
__slots__
=
(
'old'
,
'new'
)
def
__init__
(
self
,
old
,
new
):
self
.
old
=
old
self
.
new
=
new
def
__repr__
(
self
):
return
'
%s
(old=
%r
, new=
%r
)'
%
(
self
.
__class__
.
__name__
,
self
.
old
,
self
.
new
)
class
SettingsObjectID
(
object
):
"""
Simple descriptor used for SettingsObject subclasses which have dynamic ids.
"""
lock
=
Lock
()
# need a class lock because SettingsObject IDs are correlated
def
__init__
(
self
,
type
):
self
.
type
=
type
self
.
values
=
WeakKeyDictionary
()
self
.
oldvalues
=
WeakKeyDictionary
()
self
.
dirty
=
WeakKeyDictionary
()
def
__get__
(
self
,
obj
,
objtype
):
return
self
if
obj
is
None
else
self
.
values
[
obj
]
def
__set__
(
self
,
obj
,
value
):
with
self
.
lock
:
if
not
isinstance
(
value
,
self
.
type
):
value
=
self
.
type
(
value
)
if
obj
in
self
.
values
and
self
.
values
[
obj
]
==
value
:
return
if
obj
in
self
.
oldvalues
and
self
.
oldvalues
[
obj
]
==
value
:
self
.
values
[
obj
]
=
self
.
oldvalues
[
obj
]
self
.
dirty
[
obj
]
=
False
return
try
:
other_obj
=
(
key
for
key
,
val
in
chain
(
self
.
values
.
iteritems
(),
self
.
oldvalues
.
iteritems
())
if
val
==
value
)
.
next
()
except
StopIteration
:
pass
else
:
raise
DuplicateIDError
(
'SettingsObject ID already used by another
%s
'
%
other_obj
.
__class__
.
__name__
)
if
obj
in
self
.
values
:
self
.
values
[
obj
]
=
value
self
.
dirty
[
obj
]
=
True
else
:
self
.
values
[
obj
]
=
self
.
oldvalues
[
obj
]
=
value
self
.
dirty
[
obj
]
=
False
def
get_modified
(
self
,
obj
):
"""
Returns a ModifiedValue instance with references to the old and new
values or None if not modified.
"""
with
self
.
lock
:
try
:
if
self
.
dirty
.
get
(
obj
,
False
):
return
ModifiedValue
(
old
=
self
.
oldvalues
[
obj
],
new
=
self
.
values
[
obj
])
else
:
return
None
finally
:
self
.
oldvalues
[
obj
]
=
self
.
values
[
obj
]
self
.
dirty
[
obj
]
=
False
def
get_old
(
self
,
obj
):
return
self
.
oldvalues
[
obj
]
def
undo
(
self
,
obj
):
with
self
.
lock
:
self
.
values
[
obj
]
=
self
.
oldvalues
[
obj
]
self
.
dirty
[
obj
]
=
False
class
Setting
(
object
):
"""
Descriptor represeting a setting in a configuration object.
If a setting is set to the object DefaultValue, it will be reset to the
default. Also, only Setting attributes with nillable=True can be assigned
the value None. All other values are passed to the type specified.
"""
def
__init__
(
self
,
type
,
default
=
None
,
nillable
=
False
):
if
default
is
None
and
not
nillable
:
raise
TypeError
(
"default must be specified if object is not nillable"
)
self
.
type
=
type
self
.
default
=
default
self
.
nillable
=
nillable
self
.
values
=
WeakKeyDictionary
()
self
.
oldvalues
=
WeakKeyDictionary
()
self
.
dirty
=
WeakKeyDictionary
()
self
.
lock
=
Lock
()
def
__get__
(
self
,
obj
,
objtype
):
if
obj
is
None
:
return
self
return
self
.
values
.
get
(
obj
,
self
.
default
)
def
__set__
(
self
,
obj
,
value
):
with
self
.
lock
:
if
value
is
None
and
not
self
.
nillable
:
raise
ValueError
(
"setting attribute is not nillable"
)
if
value
is
DefaultValue
:
if
obj
in
self
.
values
:
self
.
values
.
pop
(
obj
)
self
.
dirty
[
obj
]
=
obj
in
self
.
oldvalues
return
if
value
is
not
None
and
not
isinstance
(
value
,
self
.
type
):
value
=
self
.
type
(
value
)
if
obj
in
self
.
values
and
self
.
values
[
obj
]
==
value
:
return
self
.
values
[
obj
]
=
value
self
.
dirty
[
obj
]
=
value
!=
self
.
oldvalues
.
get
(
obj
,
DefaultValue
)
def
__getstate__
(
self
,
obj
):
value
=
self
.
values
.
get
(
obj
,
DefaultValue
)
if
value
in
(
None
,
DefaultValue
):
pass
elif
issubclass
(
self
.
type
,
bool
):
value
=
u'true'
if
value
else
u'false'
elif
issubclass
(
self
.
type
,
(
int
,
long
,
basestring
)):
value
=
unicode
(
value
)
else
:
try
:
value
=
value
.
__getstate__
()
except
AttributeError
:
raise
TypeError
(
"Setting type
%s
does not provide __getstate__"
%
value
.
__class__
.
__name__
)
return
value
def
__setstate__
(
self
,
obj
,
value
):
with
self
.
lock
:
if
value
is
None
and
not
self
.
nillable
:
raise
ValueError
(
"setting attribute is not nillable"
)
if
value
is
None
:
pass
elif
issubclass
(
self
.
type
,
bool
):
if
value
.
lower
()
in
(
'true'
,
'yes'
,
'on'
,
'1'
):
value
=
True
elif
value
.
lower
()
in
(
'false'
,
'no'
,
'off'
,
'0'
):
value
=
False
else
:
raise
ValueError
(
"invalid boolean value:
%s
"
%
(
value
,))
elif
issubclass
(
self
.
type
,
(
int
,
long
,
basestring
)):
value
=
self
.
type
(
value
)
else
:
object
=
self
.
type
.
__new__
(
self
.
type
)
object
.
__setstate__
(
value
)
value
=
object
self
.
oldvalues
[
obj
]
=
self
.
values
[
obj
]
=
value
self
.
dirty
[
obj
]
=
False
def
get_modified
(
self
,
obj
):
"""
Returns a ModifiedValue instance with references to the old and new
values or None if not modified.
"""
with
self
.
lock
:
try
:
if
self
.
dirty
.
get
(
obj
,
False
):
return
ModifiedValue
(
old
=
self
.
oldvalues
.
get
(
obj
,
self
.
default
),
new
=
self
.
values
.
get
(
obj
,
self
.
default
))
else
:
return
None
finally
:
try
:
self
.
oldvalues
[
obj
]
=
self
.
values
[
obj
]
except
KeyError
:
self
.
oldvalues
.
pop
(
obj
,
None
)
self
.
dirty
[
obj
]
=
False
def
undo
(
self
,
obj
):
with
self
.
lock
:
if
obj
in
self
.
oldvalues
:
self
.
values
[
obj
]
=
self
.
oldvalues
[
obj
]
else
:
self
.
values
.
pop
(
obj
,
None
)
self
.
dirty
[
obj
]
=
False
class
CorrelatedSetting
(
Setting
):
"""
Descriptor represeting a setting in a configuration object that is
correlated with another setting on the same configuration object.
Sibling is the name of the sibling setting and validator is a callable
that will receive the setting value and the sibling setting value and
should raise an exception if the setting value is not acceptable relative
to the sibling setting value.
If a setting is set to the object DefaultValue, it will be reset to the
default. Also, only Setting attributes with nillable=True can be assigned
the value None. All other values are passed to the type specified.
"""
correlation_lock
=
Lock
()
def
__init__
(
self
,
type
,
sibling
,
validator
,
default
=
None
,
nillable
=
False
):
Setting
.
__init__
(
self
,
type
,
default
,
nillable
)
self
.
sibling
=
sibling
self
.
validator
=
validator
def
__set__
(
self
,
obj
,
value
):
with
self
.
correlation_lock
:
sibling_value
=
getattr
(
obj
,
self
.
sibling
)
self
.
validator
(
value
,
sibling_value
)
Setting
.
__set__
(
self
,
obj
,
value
)
class
SettingsState
(
object
):
"""
This class represents configuration objects which can be saved and restored.
"""
def
get_modified
(
self
):
"""
Returns a dictionary containing the settings which have been changed.
The keys are the full paths to the attributes (from this object), which
are mapped to a ModifiedValue instance with references to the old and
new values.
"""
modified
=
{}
for
name
in
dir
(
self
.
__class__
):
attribute
=
getattr
(
self
.
__class__
,
name
,
None
)
if
isinstance
(
attribute
,
SettingsGroupMeta
):
modified_settings
=
getattr
(
self
,
name
)
.
get_modified
()
modified
.
update
(
dict
((
name
+
'.'
+
k
if
k
else
name
,
v
)
for
k
,
v
in
modified_settings
.
iteritems
()))
elif
isinstance
(
attribute
,
Setting
):
modified_value
=
attribute
.
get_modified
(
self
)
if
modified_value
is
not
None
:
modified
[
name
]
=
modified_value
return
modified
def
clone
(
self
):
"""
Create a copy of this object and all its sub settings.
"""
raise
NotImplementedError
def
update
(
self
,
object
):
"""
Update the settings and subsettings of this settings object using the
ones in the specified object.
"""
raise
NotImplementedError
def
__getstate__
(
self
):
state
=
{}
for
name
in
dir
(
self
.
__class__
):
attribute
=
getattr
(
self
.
__class__
,
name
,
None
)
if
isinstance
(
attribute
,
SettingsGroupMeta
):
state
[
name
]
=
getattr
(
self
,
name
)
.
__getstate__
()
elif
isinstance
(
attribute
,
Setting
):
state
[
name
]
=
attribute
.
__getstate__
(
self
)
return
state
def
__setstate__
(
self
,
state
):
configuration_manager
=
ConfigurationManager
()
notification_center
=
NotificationCenter
()
for
name
,
value
in
state
.
iteritems
():
attribute
=
getattr
(
self
.
__class__
,
name
,
None
)
if
isinstance
(
attribute
,
SettingsGroupMeta
):
group
=
getattr
(
self
,
name
)
try
:
group
.
__setstate__
(
value
)
except
ValueError
,
e
:
notification_center
.
post_notification
(
'CFGManagerLoadFailed'
,
sender
=
configuration_manager
,
data
=
TimestampedNotificationData
(
attribute
=
name
,
container
=
self
,
error
=
e
))
elif
isinstance
(
attribute
,
Setting
):
try
:
attribute
.
__setstate__
(
self
,
value
)
except
ValueError
,
e
:
notification_center
.
post_notification
(
'CFGManagerLoadFailed'
,
sender
=
configuration_manager
,
data
=
TimestampedNotificationData
(
attribute
=
name
,
container
=
self
,
error
=
e
))
class
SettingsGroupMeta
(
type
):
"""
Metaclass for SettingsGroup and its subclasses which allows them to be used
as descriptor instances.
"""
def
__init__
(
cls
,
name
,
bases
,
dct
):
cls
.
values
=
WeakKeyDictionary
()
def
__get__
(
cls
,
obj
,
objtype
):
if
obj
is
None
:
return
cls
try
:
return
cls
.
values
[
obj
]
except
KeyError
:
return
cls
.
values
.
setdefault
(
obj
,
cls
())
def
__set__
(
cls
,
obj
,
value
):
raise
AttributeError
(
"cannot overwrite group of settings"
)
class
SettingsGroup
(
SettingsState
):
"""
Base class for settings groups, i.e. non-leaf and non-root nodes in the
configuration tree. All SettingsGroup subclasses are descriptor instances
which return an instance of the subclass type when accessed. All
SettingsGroup intances are created without passing any arguments to the
constructor.
class ContainedGroup(SettingsGroup):
pass
class ContainingGroup(SettingsGroup):
subgroup = ContainedGroup
"""
__metaclass__
=
SettingsGroupMeta
class
SettingsObject
(
SettingsState
):
"""
Subclass for top-level configuration objects. These objects are identifiable
by either a global id (set in the __id__ attribute of the class) or a local
id passed as the sole argument when instantiating SettingsObjects.
For SettingsObject subclasses which are meant to be used exclusively with a
local id, the class attribute __id__ should be left to the value None; if
__init__ is defined, it would have to accept exactly one argument, id and
the subclass MUST call the parent's __init__ after it's done doing its own
initialization of the instance (SettingsObject.__init__ needs to be
executed last, after the instance is completely initialized).
The local id takes precedence over the one specified as a class attribute.
Note: __init__ and __new__ will be called not only when a new object is
created (i.e. there weren't any settings saved in the configuration), but
also when the object is retrieved from the configuration.
"""
__group__
=
None
__id__
=
None
def
__new__
(
cls
,
id
=
None
):
id
=
id
or
cls
.
__id__
if
id
is
None
:
raise
ValueError
(
"id is required for instantiating
%s
"
%
cls
.
__name__
)
if
not
isinstance
(
id
,
basestring
):
raise
TypeError
(
"id needs to be a string instance"
)
configuration
=
ConfigurationManager
()
instance
=
SettingsState
.
__new__
(
cls
)
instance
.
__id__
=
id
try
:
data
=
configuration
.
get
(
instance
.
__key__
)
except
ObjectNotFoundError
:
pass
else
:
instance
.
__setstate__
(
data
)
return
instance
def
__init__
(
self
,
id
=
None
):
notification_center
=
NotificationCenter
()
notification_center
.
post_notification
(
'CFGSettingsObjectWasCreated'
,
sender
=
self
,
data
=
TimestampedNotificationData
())
@property
def
__key__
(
self
):
if
isinstance
(
self
.
__class__
.
__id__
,
SettingsObjectID
):
id_key
=
PersistentKey
(
self
.
__id__
)
else
:
id_key
=
unicode
(
self
.
__id__
)
if
self
.
__group__
is
not
None
:
return
[
self
.
__group__
,
id_key
]
else
:
return
[
id_key
]
@property
def
__oldkey__
(
self
):
if
isinstance
(
self
.
__class__
.
__id__
,
SettingsObjectID
):
id_key
=
PersistentKey
(
self
.
__class__
.
__id__
.
get_old
(
self
))
else
:
id_key
=
unicode
(
self
.
__id__
)
if
self
.
__group__
is
not
None
:
return
[
self
.
__group__
,
id_key
]
else
:
return
[
id_key
]
@run_in_thread
(
'file-io'
)
def
save
(
self
):
"""
Use the ConfigurationManager to store the object under its id in the
specified group or top-level otherwise, depending on whether group is
None.
This method will also post a CFGSettingsObjectDidChange notification,
regardless of whether the settings have been saved to persistent storage
or not. If the save does fail, a CFGManagerSaveFailed notification is
posted as well.
"""
configuration
=
ConfigurationManager
()
notification_center
=
NotificationCenter
()
oldkey
=
self
.
__oldkey__
# save this here as get_modified will reset it
id_descriptor
=
self
.
__class__
.
__id__
if
isinstance
(
self
.
__class__
.
__id__
,
SettingsObjectID
)
else
None
modified_id
=
id_descriptor
.
get_modified
(
self
)
if
id_descriptor
else
None
modified_settings
=
self
.
get_modified
()
if
id_descriptor
:
try
:
configuration
.
get
(
oldkey
)
except
ObjectNotFoundError
:
save_required
=
True
else
:
save_required
=
False
else
:
save_required
=
False
if
not
modified_id
and
not
modified_settings
and
not
save_required
:
return
if
save_required
:
configuration
.
update
(
self
.
__key__
,
self
.
__getstate__
())
else
:
if
modified_id
:
configuration
.
rename
(
oldkey
,
self
.
__key__
)
if
modified_settings
:
configuration
.
update
(
self
.
__key__
,
self
.
__getstate__
())
modified_data
=
modified_settings
or
{}
if
modified_id
:
modified_data
[
'__id__'
]
=
modified_id
if
modified_data
:
notification_center
.
post_notification
(
'CFGSettingsObjectDidChange'
,
sender
=
self
,
data
=
TimestampedNotificationData
(
modified
=
modified_data
))
try
:
configuration
.
save
()
except
Exception
,
e
:
log
.
err
()
notification_center
.
post_notification
(
'CFGManagerSaveFailed'
,
sender
=
configuration
,
data
=
TimestampedNotificationData
(
object
=
self
,
operation
=
'save'
,
modified
=
modified_settings
,
exception
=
e
))
@run_in_thread
(
'file-io'
)
def
delete
(
self
):
"""
Remove this object from the persistent configuration.
"""
if
self
.
__id__
is
self
.
__class__
.
__id__
:
raise
TypeError
(
"cannot delete
%s
instance with default id"
%
self
.
__class__
.
__name__
)
configuration
=
ConfigurationManager
()
notification_center
=
NotificationCenter
()
configuration
.
delete
(
self
.
__oldkey__
)
# we need the key that wasn't yet saved
notification_center
.
post_notification
(
'CFGSettingsObjectWasDeleted'
,
sender
=
self
,
data
=
TimestampedNotificationData
())
try
:
configuration
.
save
()
except
Exception
,
e
:
log
.
err
()
notification_center
.
post_notification
(
'CFGManagerSaveFailed'
,
sender
=
configuration
,
data
=
TimestampedNotificationData
(
object
=
self
,
operation
=
'delete'
,
exception
=
e
))
def
clone
(
self
,
new_id
):
"""
Create a copy of this object and all its sub settings.
"""
raise
NotImplementedError
@classmethod
def
register_extension
(
cls
,
extension
):
"""
Register an extension of this SettingsObject. All Settings and
SettingsGroups defined in the extension will be added to this
SettingsObject, overwriting any attributes with the same name.
Other attributes in the extension are ignored.
"""
if
not
issubclass
(
extension
,
SettingsObjectExtension
):
raise
TypeError
(
"expected subclass of SettingsObjectExtension, got
%r
"
%
(
extension
,))
for
name
in
dir
(
extension
):
attribute
=
getattr
(
extension
,
name
,
None
)
if
isinstance
(
attribute
,
(
Setting
,
SettingsGroupMeta
)):
setattr
(
cls
,
name
,
attribute
)
class
SettingsObjectExtension
(
object
):
"""
Base class for extensions of SettingsObjects.
"""
def
__new__
(
self
,
*
args
,
**
kwargs
):
raise
TypeError
(
"SettingsObjectExtension subclasses cannot be instantiated"
)
File Metadata
Details
Attached
Mime Type
text/x-python
Expires
Sat, Feb 1, 6:27 AM (1 d, 11 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
3488854
Default Alt Text
__init__.py (25 KB)
Attached To
Mode
rPYNSIPSIMPLE python3-sipsimple
Attached
Detach File
Event Timeline
Log In to Comment