diff --git a/python/core/__init__.py b/python/core/__init__.py
index 50d37024b06..95345ba3c7f 100644
--- a/python/core/__init__.py
+++ b/python/core/__init__.py
@@ -16,8 +16,6 @@
* *
***************************************************************************
"""
-from builtins import str
-from builtins import object
__author__ = 'Nathan Woodrow'
__date__ = 'May 2014'
@@ -25,341 +23,25 @@ __copyright__ = '(C) 2014, Nathan Woodrow'
# This will get replaced with a git SHA1 when you do a git archive
__revision__ = '$Format:%H$'
-from qgis.PyQt.QtCore import QCoreApplication, NULL
-import inspect
-import string
-import types
-import functools
from qgis._core import *
+from .additions.readwritecontextentercategory import ReadWriteContextEnterCategory
+from .additions.projectdirtyblocker import ProjectDirtyBlocker
+from .additions.qgstaskwrapper import QgsTaskWrapper
+from .additions.qgsfunction import register_function, qgsfunction
+from .additions.edit import edit, QgsEditError
+from .additions.fromfunction import fromFunction
+from .additions.processing import processing_output_layer_repr, processing_source_repr
+from .additions.qgsgeometry import _geometryNonZero
+from .additions.qgsdefaultvalue import _isValid
-# Boolean evaluation of QgsGeometry
-
-
-def _geometryNonZero(self):
- return not self.isEmpty()
-
-
-def _isValid(self):
- return self.isValid()
-
-
+# Injections into classes
QgsGeometry.__nonzero__ = _geometryNonZero
QgsGeometry.__bool__ = _geometryNonZero
-
QgsDefaultValue.__bool__ = _isValid
-
-
-def register_function(function, arg_count, group, usesgeometry=False,
- referenced_columns=[QgsFeatureRequest.ALL_ATTRIBUTES], **kwargs):
- """
- Register a Python function to be used as a expression function.
-
- Functions should take (values, feature, parent) as args:
-
- Example:
- def myfunc(values, feature, parent):
- pass
-
- They can also shortcut naming feature and parent args by using *args
- if they are not needed in the function.
-
- Example:
- def myfunc(values, *args):
- pass
-
- Functions should return a value compatible with QVariant
-
- Eval errors can be raised using parent.setEvalErrorString("Error message")
-
- :param function:
- :param arg_count:
- :param group:
- :param usesgeometry:
- :return:
- """
-
- class QgsPyExpressionFunction(QgsExpressionFunction):
-
- def __init__(self, func, name, args, group, helptext='', usesGeometry=True,
- referencedColumns=QgsFeatureRequest.ALL_ATTRIBUTES, expandargs=False):
- QgsExpressionFunction.__init__(self, name, args, group, helptext)
- self.function = func
- self.expandargs = expandargs
- self.uses_geometry = usesGeometry
- self.referenced_columns = referencedColumns
-
- def func(self, values, context, parent, node):
- feature = None
- if context:
- feature = context.feature()
-
- try:
- if self.expandargs:
- values.append(feature)
- values.append(parent)
- if inspect.getargspec(self.function).args[-1] == 'context':
- values.append(context)
- return self.function(*values)
- else:
- if inspect.getargspec(self.function).args[-1] == 'context':
- self.function(values, feature, parent, context)
- return self.function(values, feature, parent)
- except Exception as ex:
- parent.setEvalErrorString(str(ex))
- return None
-
- def usesGeometry(self, node):
- return self.uses_geometry
-
- def referencedColumns(self, node):
- return self.referenced_columns
-
- helptemplate = string.Template("""
$name function
$doc""")
- name = kwargs.get('name', function.__name__)
- helptext = kwargs.get('helpText') or function.__doc__ or ''
- helptext = helptext.strip()
- expandargs = False
-
- if arg_count == "auto":
- # Work out the number of args we need.
- # Number of function args - 2. The last two args are always feature, parent.
- args = inspect.getargspec(function).args
- number = len(args)
- arg_count = number - 2
- if args[-1] == 'context':
- arg_count -= 1
- expandargs = True
-
- register = kwargs.get('register', True)
- if register and QgsExpression.isFunctionName(name):
- if not QgsExpression.unregisterFunction(name):
- msgtitle = QCoreApplication.translate("UserExpressions", "User expressions")
- msg = QCoreApplication.translate("UserExpressions",
- "The user expression {0} already exists and could not be unregistered.").format(
- name)
- QgsMessageLog.logMessage(msg + "\n", msgtitle, Qgis.Warning)
- return None
-
- function.__name__ = name
- helptext = helptemplate.safe_substitute(name=name, doc=helptext)
- f = QgsPyExpressionFunction(function, name, arg_count, group, helptext, usesgeometry, referenced_columns,
- expandargs)
-
- # This doesn't really make any sense here but does when used from a decorator context
- # so it can stay.
- if register:
- QgsExpression.registerFunction(f)
- return f
-
-
-def qgsfunction(args='auto', group='custom', **kwargs):
- """
- Decorator function used to define a user expression function.
-
- Example:
- @qgsfunction(2, 'test'):
- def add(values, feature, parent):
- pass
-
- Will create and register a function in QgsExpression called 'add' in the
- 'test' group that takes two arguments.
-
- or not using feature and parent:
-
- Example:
- @qgsfunction(2, 'test'):
- def add(values, *args):
- pass
- """
-
- def wrapper(func):
- return register_function(func, args, group, **kwargs)
-
- return wrapper
-
-
-class QgsEditError(Exception):
-
- def __init__(self, value):
- self.value = value
-
- def __str__(self):
- return repr(self.value)
-
-
-# Define a `with edit(layer)` statement
-
-
-class edit(object):
-
- def __init__(self, layer):
- self.layer = layer
-
- def __enter__(self):
- assert self.layer.startEditing()
- return self.layer
-
- def __exit__(self, ex_type, ex_value, traceback):
- if ex_type is None:
- if not self.layer.commitChanges():
- raise QgsEditError(self.layer.commitErrors())
- return True
- else:
- self.layer.rollBack()
- return False
-
-# Python class to mimic QgsReadWriteContextCategoryPopper C++ class
-
-
-class ReadWriteContextEnterCategory():
- """
- Push a category to the stack
-
- .. code-block:: python
-
- context = QgsReadWriteContext()
- with QgsReadWriteContext.enterCategory(context, category, details):
- # do something
-
- .. versionadded:: 3.2
- """
-
- def __init__(self, context, category_name, details=None):
- self.context = context
- self.category_name = category_name
- self.details = details
- self.popper = None
-
- def __enter__(self):
- self.popper = self.context._enterCategory(self.category_name, self.details)
- return self.context
-
- def __exit__(self, ex_type, ex_value, traceback):
- del self.popper
- return True
-
-
-# Inject the context manager into QgsReadWriteContext class as a member
QgsReadWriteContext.enterCategory = ReadWriteContextEnterCategory
-
-
-# Python class to extend QgsProjectDirtyBlocker C++ class
-
-
-class ProjectDirtyBlocker():
- """
- Context manager used to block project setDirty calls.
-
- .. code-block:: python
-
- project = QgsProject.instance()
- with QgsProject.blockDirtying(project):
- # do something
-
- .. versionadded:: 3.2
- """
-
- def __init__(self, project):
- self.project = project
- self.blocker = None
-
- def __enter__(self):
- self.blocker = QgsProjectDirtyBlocker(self.project)
- return self.project
-
- def __exit__(self, ex_type, ex_value, traceback):
- del self.blocker
- return True
-
-
-# Inject the context manager into QgsProject class as a member
QgsProject.blockDirtying = ProjectDirtyBlocker
-
-
-class QgsTaskWrapper(QgsTask):
-
- def __init__(self, description, flags, function, on_finished, *args, **kwargs):
- QgsTask.__init__(self, description, flags)
- self.args = args
- self.kwargs = kwargs
- self.function = function
- self.on_finished = on_finished
- self.returned_values = None
- self.exception = None
-
- def run(self):
- try:
- self.returned_values = self.function(self, *self.args, **self.kwargs)
- except Exception as ex:
- # report error
- self.exception = ex
- return False
-
- return True
-
- def finished(self, result):
- if not self.on_finished:
- return
-
- if not result and self.exception is None:
- self.exception = Exception('Task canceled')
-
- try:
- if self.returned_values:
- self.on_finished(self.exception, self.returned_values)
- else:
- self.on_finished(self.exception)
- except Exception as ex:
- self.exception = ex
-
-
-@staticmethod
-def fromFunction(description, function, *args, on_finished=None, flags=QgsTask.AllFlags, **kwargs):
- """
- Creates a new QgsTask task from a python function.
-
- Example:
-
- def calculate(task):
- # pretend this is some complex maths and stuff we want
- # to run in the background
- return 5*6
-
- def calculation_finished(exception, value=None):
- if not exception:
- iface.messageBar().pushMessage(
- 'the magic number is {}'.format(value))
- else:
- iface.messageBar().pushMessage(
- str(exception))
-
- task = QgsTask.fromFunction('my task', calculate,
- on_finished=calculation_finished)
- QgsApplication.taskManager().addTask(task)
-
- """
-
- assert function
- return QgsTaskWrapper(description, flags, function, on_finished, *args, **kwargs)
-
-
QgsTask.fromFunction = fromFunction
-
-
-# add some __repr__ methods to processing classes
-def processing_source_repr(self):
- return "".format(
- self.source.staticValue(), self.selectedFeaturesOnly)
-
-
QgsProcessingFeatureSourceDefinition.__repr__ = processing_source_repr
-
-
-def processing_output_layer_repr(self):
- return "".format(self.sink.staticValue(),
- self.createOptions)
-
-
QgsProcessingOutputLayerDefinition.__repr__ = processing_output_layer_repr
diff --git a/python/core/additions/__init__.py b/python/core/additions/__init__.py
new file mode 100644
index 00000000000..e69de29bb2d
diff --git a/python/core/additions/edit.py b/python/core/additions/edit.py
new file mode 100644
index 00000000000..92756d1e895
--- /dev/null
+++ b/python/core/additions/edit.py
@@ -0,0 +1,48 @@
+# -*- coding: utf-8 -*-
+
+"""
+***************************************************************************
+ edit.py
+ ---------------------
+ Date : May 2018
+ Copyright : (C) 2018 by Denis Rouzaud
+ Email : denis@opengis.ch
+***************************************************************************
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+* *
+***************************************************************************
+"""
+
+from builtins import object
+
+
+class QgsEditError(Exception):
+
+ def __init__(self, value):
+ self.value = value
+
+ def __str__(self):
+ return repr(self.value)
+
+
+class edit(object):
+
+ def __init__(self, layer):
+ self.layer = layer
+
+ def __enter__(self):
+ assert self.layer.startEditing()
+ return self.layer
+
+ def __exit__(self, ex_type, ex_value, traceback):
+ if ex_type is None:
+ if not self.layer.commitChanges():
+ raise QgsEditError(self.layer.commitErrors())
+ return True
+ else:
+ self.layer.rollBack()
+ return False
diff --git a/python/core/additions/fromfunction.py b/python/core/additions/fromfunction.py
new file mode 100644
index 00000000000..79f19d3810f
--- /dev/null
+++ b/python/core/additions/fromfunction.py
@@ -0,0 +1,51 @@
+# -*- coding: utf-8 -*-
+
+"""
+***************************************************************************
+ fromfunction.py
+ ---------------------
+ Date : May 2018
+ Copyright : (C) 2018 by Denis Rouzaud
+ Email : denis@opengis.ch
+***************************************************************************
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+* *
+***************************************************************************
+"""
+
+from .qgstaskwrapper import QgsTaskWrapper
+from qgis._core import QgsTask
+
+
+@staticmethod
+def fromFunction(description, function, *args, on_finished=None, flags=QgsTask.AllFlags, **kwargs):
+ """
+ Creates a new QgsTask task from a python function.
+
+ Example:
+
+ def calculate(task):
+ # pretend this is some complex maths and stuff we want
+ # to run in the background
+ return 5*6
+
+ def calculation_finished(exception, value=None):
+ if not exception:
+ iface.messageBar().pushMessage(
+ 'the magic number is {}'.format(value))
+ else:
+ iface.messageBar().pushMessage(
+ str(exception))
+
+ task = QgsTask.fromFunction('my task', calculate,
+ on_finished=calculation_finished)
+ QgsApplication.taskManager().addTask(task)
+
+ """
+
+ assert function
+ return QgsTaskWrapper(description, flags, function, on_finished, *args, **kwargs)
diff --git a/python/core/additions/processing.py b/python/core/additions/processing.py
new file mode 100644
index 00000000000..95545e674ba
--- /dev/null
+++ b/python/core/additions/processing.py
@@ -0,0 +1,29 @@
+# -*- coding: utf-8 -*-
+
+"""
+***************************************************************************
+ processing.py
+ ---------------------
+ Date : May 2018
+ Copyright : (C) 2018 by Denis Rouzaud
+ Email : denis@opengis.ch
+***************************************************************************
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+* *
+***************************************************************************
+"""
+
+
+# add some __repr__ methods to processing classes
+def processing_source_repr(self):
+ return "".format(
+ self.source.staticValue(), self.selectedFeaturesOnly)
+
+
+def processing_output_layer_repr(self):
+ return "".format(self.sink.staticValue(),
+ self.createOptions)
diff --git a/python/core/additions/projectdirtyblocker.py b/python/core/additions/projectdirtyblocker.py
new file mode 100644
index 00000000000..bdaf7c99386
--- /dev/null
+++ b/python/core/additions/projectdirtyblocker.py
@@ -0,0 +1,47 @@
+# -*- coding: utf-8 -*-
+
+"""
+***************************************************************************
+ projectdirtyblocker.py
+ ---------------------
+ Date : May 2018
+ Copyright : (C) 2018 by Denis Rouzaud
+ Email : denis@opengis.ch
+***************************************************************************
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+* *
+***************************************************************************
+"""
+
+
+from qgis._core import QgsProjectDirtyBlocker
+
+
+class ProjectDirtyBlocker():
+ """
+ Context manager used to block project setDirty calls.
+
+ .. code-block:: python
+
+ project = QgsProject.instance()
+ with QgsProject.blockDirtying(project):
+ # do something
+
+ .. versionadded:: 3.2
+ """
+
+ def __init__(self, project):
+ self.project = project
+ self.blocker = None
+
+ def __enter__(self):
+ self.blocker = QgsProjectDirtyBlocker(self.project)
+ return self.project
+
+ def __exit__(self, ex_type, ex_value, traceback):
+ del self.blocker
+ return True
diff --git a/python/core/additions/qgsdefaultvalue.py b/python/core/additions/qgsdefaultvalue.py
new file mode 100644
index 00000000000..6daf2a40540
--- /dev/null
+++ b/python/core/additions/qgsdefaultvalue.py
@@ -0,0 +1,22 @@
+# -*- coding: utf-8 -*-
+
+"""
+***************************************************************************
+ qgsdefaultvalue.py
+ ---------------------
+ Date : May 2018
+ Copyright : (C) 2018 by Denis Rouzaud
+ Email : denis@opengis.ch
+***************************************************************************
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+* *
+***************************************************************************
+"""
+
+
+def _isValid(self):
+ return self.isValid()
diff --git a/python/core/additions/qgsfunction.py b/python/core/additions/qgsfunction.py
new file mode 100644
index 00000000000..1e231340c71
--- /dev/null
+++ b/python/core/additions/qgsfunction.py
@@ -0,0 +1,154 @@
+# -*- coding: utf-8 -*-
+
+"""
+***************************************************************************
+ qgsfunction.py
+ ---------------------
+ Date : May 2018
+ Copyright : (C) 2018 by Denis Rouzaud
+ Email : denis@opengis.ch
+***************************************************************************
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+* *
+***************************************************************************
+"""
+
+
+import inspect
+import string
+from builtins import str
+from qgis.PyQt.QtCore import QCoreApplication
+from qgis._core import QgsExpressionFunction, QgsExpression, QgsMessageLog, QgsFeatureRequest
+
+
+def register_function(function, arg_count, group, usesgeometry=False,
+ referenced_columns=[QgsFeatureRequest.ALL_ATTRIBUTES], **kwargs):
+ """
+ Register a Python function to be used as a expression function.
+
+ Functions should take (values, feature, parent) as args:
+
+ Example:
+ def myfunc(values, feature, parent):
+ pass
+
+ They can also shortcut naming feature and parent args by using *args
+ if they are not needed in the function.
+
+ Example:
+ def myfunc(values, *args):
+ pass
+
+ Functions should return a value compatible with QVariant
+
+ Eval errors can be raised using parent.setEvalErrorString("Error message")
+
+ :param function:
+ :param arg_count:
+ :param group:
+ :param usesgeometry:
+ :return:
+ """
+
+ class QgsPyExpressionFunction(QgsExpressionFunction):
+
+ def __init__(self, func, name, args, group, helptext='', usesGeometry=True,
+ referencedColumns=QgsFeatureRequest.ALL_ATTRIBUTES, expandargs=False):
+ QgsExpressionFunction.__init__(self, name, args, group, helptext)
+ self.function = func
+ self.expandargs = expandargs
+ self.uses_geometry = usesGeometry
+ self.referenced_columns = referencedColumns
+
+ def func(self, values, context, parent, node):
+ feature = None
+ if context:
+ feature = context.feature()
+
+ try:
+ if self.expandargs:
+ values.append(feature)
+ values.append(parent)
+ if inspect.getargspec(self.function).args[-1] == 'context':
+ values.append(context)
+ return self.function(*values)
+ else:
+ if inspect.getargspec(self.function).args[-1] == 'context':
+ self.function(values, feature, parent, context)
+ return self.function(values, feature, parent)
+ except Exception as ex:
+ parent.setEvalErrorString(str(ex))
+ return None
+
+ def usesGeometry(self, node):
+ return self.uses_geometry
+
+ def referencedColumns(self, node):
+ return self.referenced_columns
+
+ helptemplate = string.Template("""$name function
$doc""")
+ name = kwargs.get('name', function.__name__)
+ helptext = kwargs.get('helpText') or function.__doc__ or ''
+ helptext = helptext.strip()
+ expandargs = False
+
+ if arg_count == "auto":
+ # Work out the number of args we need.
+ # Number of function args - 2. The last two args are always feature, parent.
+ args = inspect.getargspec(function).args
+ number = len(args)
+ arg_count = number - 2
+ if args[-1] == 'context':
+ arg_count -= 1
+ expandargs = True
+
+ register = kwargs.get('register', True)
+ if register and QgsExpression.isFunctionName(name):
+ if not QgsExpression.unregisterFunction(name):
+ msgtitle = QCoreApplication.translate("UserExpressions", "User expressions")
+ msg = QCoreApplication.translate("UserExpressions",
+ "The user expression {0} already exists and could not be unregistered.").format(
+ name)
+ QgsMessageLog.logMessage(msg + "\n", msgtitle, Qgis.Warning)
+ return None
+
+ function.__name__ = name
+ helptext = helptemplate.safe_substitute(name=name, doc=helptext)
+ f = QgsPyExpressionFunction(function, name, arg_count, group, helptext, usesgeometry, referenced_columns,
+ expandargs)
+
+ # This doesn't really make any sense here but does when used from a decorator context
+ # so it can stay.
+ if register:
+ QgsExpression.registerFunction(f)
+ return f
+
+
+def qgsfunction(args='auto', group='custom', **kwargs):
+ """
+ Decorator function used to define a user expression function.
+
+ Example:
+ @qgsfunction(2, 'test'):
+ def add(values, feature, parent):
+ pass
+
+ Will create and register a function in QgsExpression called 'add' in the
+ 'test' group that takes two arguments.
+
+ or not using feature and parent:
+
+ Example:
+ @qgsfunction(2, 'test'):
+ def add(values, *args):
+ pass
+ """
+
+ def wrapper(func):
+ return register_function(func, args, group, **kwargs)
+
+ return wrapper
diff --git a/python/core/additions/qgsgeometry.py b/python/core/additions/qgsgeometry.py
new file mode 100644
index 00000000000..cc25a0fffd5
--- /dev/null
+++ b/python/core/additions/qgsgeometry.py
@@ -0,0 +1,22 @@
+# -*- coding: utf-8 -*-
+
+"""
+***************************************************************************
+ qgsgeometry.py
+ ---------------------
+ Date : May 2018
+ Copyright : (C) 2018 by Denis Rouzaud
+ Email : denis@opengis.ch
+***************************************************************************
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+* *
+***************************************************************************
+"""
+
+
+def _geometryNonZero(self):
+ return not self.isEmpty()
diff --git a/python/core/additions/qgstaskwrapper.py b/python/core/additions/qgstaskwrapper.py
new file mode 100644
index 00000000000..6c2b7560d81
--- /dev/null
+++ b/python/core/additions/qgstaskwrapper.py
@@ -0,0 +1,58 @@
+# -*- coding: utf-8 -*-
+
+"""
+***************************************************************************
+ qgstaskwrapper.py
+ ---------------------
+ Date : May 2018
+ Copyright : (C) 2018 by Denis Rouzaud
+ Email : denis@opengis.ch
+***************************************************************************
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+* *
+***************************************************************************
+"""
+
+
+from qgis._core import QgsTask
+
+
+class QgsTaskWrapper(QgsTask):
+
+ def __init__(self, description, flags, function, on_finished, *args, **kwargs):
+ QgsTask.__init__(self, description, flags)
+ self.args = args
+ self.kwargs = kwargs
+ self.function = function
+ self.on_finished = on_finished
+ self.returned_values = None
+ self.exception = None
+
+ def run(self):
+ try:
+ self.returned_values = self.function(self, *self.args, **self.kwargs)
+ except Exception as ex:
+ # report error
+ self.exception = ex
+ return False
+
+ return True
+
+ def finished(self, result):
+ if not self.on_finished:
+ return
+
+ if not result and self.exception is None:
+ self.exception = Exception('Task canceled')
+
+ try:
+ if self.returned_values:
+ self.on_finished(self.exception, self.returned_values)
+ else:
+ self.on_finished(self.exception)
+ except Exception as ex:
+ self.exception = ex
diff --git a/python/core/additions/readwritecontextentercategory.py b/python/core/additions/readwritecontextentercategory.py
new file mode 100644
index 00000000000..999e0a94e88
--- /dev/null
+++ b/python/core/additions/readwritecontextentercategory.py
@@ -0,0 +1,46 @@
+# -*- coding: utf-8 -*-
+
+"""
+***************************************************************************
+ readwritecontextentercategory.py
+ ---------------------
+ Date : May 2018
+ Copyright : (C) 2018 by Denis Rouzaud
+ Email : denis@opengis.ch
+***************************************************************************
+* *
+* This program is free software; you can redistribute it and/or modify *
+* it under the terms of the GNU General Public License as published by *
+* the Free Software Foundation; either version 2 of the License, or *
+* (at your option) any later version. *
+* *
+***************************************************************************
+"""
+
+
+class ReadWriteContextEnterCategory():
+ """
+ Push a category to the stack
+
+ .. code-block:: python
+
+ context = QgsReadWriteContext()
+ with QgsReadWriteContext.enterCategory(context, category, details):
+ # do something
+
+ .. versionadded:: 3.2
+ """
+
+ def __init__(self, context, category_name, details=None):
+ self.context = context
+ self.category_name = category_name
+ self.details = details
+ self.popper = None
+
+ def __enter__(self):
+ self.popper = self.context._enterCategory(self.category_name, self.details)
+ return self.context
+
+ def __exit__(self, ex_type, ex_value, traceback):
+ del self.popper
+ return True