Skip to content

Commit

Permalink
Merge pull request #6987 from 3nids/pyqgis_additions
Browse files Browse the repository at this point in the history
[pyqgis] move qgis.core tricks to their own python file in additions folder
  • Loading branch information
3nids authored May 15, 2018
2 parents 26174ea + 261e5f7 commit c5ee15a
Show file tree
Hide file tree
Showing 12 changed files with 491 additions and 331 deletions.
3 changes: 2 additions & 1 deletion python/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -319,10 +319,11 @@ FOREACH(module ${PY_MODULES})
FILE(GLOB_RECURSE PY_FILES "${module}/*.py")
INSTALL(FILES ${PY_FILES} DESTINATION "${QGIS_PYTHON_DIR}/${module}")
FOREACH(pyfile ${PY_FILES})
FILE(RELATIVE_PATH subdir ${CMAKE_CURRENT_SOURCE_DIR}/${module} ${pyfile})
ADD_CUSTOM_COMMAND(TARGET py${module}
POST_BUILD
COMMAND ${CMAKE_COMMAND} -E make_directory "${QGIS_PYTHON_OUTPUT_DIRECTORY}/${module}"
COMMAND ${CMAKE_COMMAND} -E copy ${pyfile} "${QGIS_PYTHON_OUTPUT_DIRECTORY}/${module}"
COMMAND ${CMAKE_COMMAND} -E copy ${pyfile} "${QGIS_PYTHON_OUTPUT_DIRECTORY}/${module}/${subdir}"
WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}
DEPENDS ${PY_FILES}
)
Expand Down
342 changes: 12 additions & 330 deletions python/core/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,350 +16,32 @@
* *
***************************************************************************
"""
from builtins import str
from builtins import object

__author__ = 'Nathan Woodrow'
__date__ = 'May 2014'
__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.PyQt.QtCore import NULL
from qgis._core import *


# Boolean evaluation of QgsGeometry


def _geometryNonZero(self):
return not self.isEmpty()


def _isValid(self):
return self.isValid()


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

# 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("""<h3>$name function</h3><br>$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 "<QgsProcessingFeatureSourceDefinition {{'source':{}, 'selectedFeaturesOnly': {}}}>".format(
self.source.staticValue(), self.selectedFeaturesOnly)


QgsProcessingFeatureSourceDefinition.__repr__ = processing_source_repr


def processing_output_layer_repr(self):
return "<QgsProcessingOutputLayerDefinition {{'sink':{}, 'createOptions': {}}}>".format(self.sink.staticValue(),
self.createOptions)


QgsProcessingOutputLayerDefinition.__repr__ = processing_output_layer_repr
Empty file.
Loading

0 comments on commit c5ee15a

Please sign in to comment.