Skip to content

Commit

Permalink
Merge branch 'release/0.5.1'
Browse files Browse the repository at this point in the history
  • Loading branch information
cnheider committed Apr 16, 2024
2 parents b148094 + 51ed722 commit bee89f6
Show file tree
Hide file tree
Showing 5 changed files with 279 additions and 21 deletions.
2 changes: 1 addition & 1 deletion jord/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

__project__ = "Jord"
__author__ = "Christian Heider Lindbjerg"
__version__ = "0.5.0"
__version__ = "0.5.1"
__doc__ = r"""
.. module:: jord
:platform: Unix, Windows
Expand Down
6 changes: 5 additions & 1 deletion jord/qgis_utilities/categorisation.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,11 @@ def categorise_layer(

color_iter = iter(iterable)

assert field_name in layer.fields().names()
available_field_names = layer.fields().names()

assert (
field_name in available_field_names
), f"Did not find {field_name=} in {available_field_names=}"

render_categories = []
for cat in layer.uniqueValues(layer.fields().indexFromName(field_name)):
Expand Down
1 change: 1 addition & 0 deletions jord/qgis_utilities/helpers/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,4 @@
from .sessions import *
from .actions import *
from .groups import *
from .logging import *
241 changes: 241 additions & 0 deletions jord/qgis_utilities/helpers/logging.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,241 @@
import logging
import os
import sys
from pathlib import Path
from typing import Optional, Any

__all__ = ["setup_logger"]

level_map = {
logging.NOTSET: 0, # Qgis.MessageLevel.NoLevel
# 0 When set on a logger, indicates that ancestor loggers are to be consulted to determine the effective level. If that still resolves to NOTSET, then all events are logged. When set on a handler, all events are handled.
logging.DEBUG: 0, # Qgis.MessageLevel.Info
# 10 Detailed information, typically only of interest to a developer trying to diagnose a problem.
logging.INFO: 0, # Qgis.MessageLevel.Info #Qgis.MessageLevel.Success
# 20 Confirmation that things are working as expected.
logging.WARNING: 1, # Qgis.MessageLevel.Warning
# 30 An indication that something unexpected happened, or that a problem might occur in the near future (e.g. ‘disk space low’). The software is still working as expected.
logging.ERROR: 2, # Qgis.MessageLevel.Critical
# 40 Due to a more serious problem, the software has not been able to perform some function.
logging.CRITICAL: 2, # Qgis.MessageLevel.Critical
# 50 A serious error, indicating that the program itself may be unable to continue running.}
}


class QgsLogHandler(logging.Handler):
"""A logging handler that will log messages to the QGIS logging console."""

def __init__(
self, tag_name, iface: Optional[Any] = None, level: int = logging.NOTSET
):
self.tag_name = tag_name
self.iface = iface
super().__init__(level=level)

def emit(self, record: logging.LogRecord) -> None:
"""Try to log the message to QGIS if available, otherwise do nothing.
:param record: logging message containing whatever info needs to be
logged.
:type record: str
"""

from qgis.core import QgsMessageLog

push = False

level = level_map[record.levelno]
if self.iface:
push = True

message = record.getMessage()
QgsMessageLog.logMessage(
message=message, tag=self.tag_name, notifyUser=push, level=level
)

# optionally, display message on QGIS Message bar (above the map canvas)
if push and level >= logging.WARNING:
self.iface.messageBar().pushMessage(
title=self.tag_name,
text=message,
level=level,
duration=(level + 1) * 3,
)


def add_logging_handler_once(logger, handler):
"""A helper to add a handler to a logger, ensuring there are no duplicates.
:param logger: Logger that should have a handler added.
:type logger: logging.logger
:param handler: Handler instance to be added. It will not be added if an
instance of that Handler subclass already exists.
:type handler: logging.Handler
:returns: True if the logging handler was added, otherwise False.
:rtype: bool
"""
class_name = handler.__class__.__name__

for handler in logger.handlers:
if handler.__class__.__name__ == class_name:
return False

logger.addHandler(handler)
return True


def setup_logger(
logger_name: str,
*,
iface: Optional[Any] = None,
sentry_url: Optional[str] = None,
log_file: Optional[Path] = None,
) -> logging.Logger:
"""Run once when the module is loaded and enable logging.
:param sentry_url: Mandatory url to sentry api for remote logging.
Consult your sentry instance for the client instance url.
:type sentry_url: str
:param log_file: Optional full path to a file to write logs to.
:type log_file: str
Borrowed heavily from this:
http://docs.python.org/howto/logging-cookbook.html
Use this to first initialise the logger in your __init__.py::
import custom_logging
custom_logging.setup_logger('http://path to sentry')
You would typically only need to do the above once ever as the
safe model is initialised early and will set up the logger
globally so it is available to all packages / subpackages as
shown below.
In a module that wants to do logging then use this example as
a guide to get the initialised logger instance::
# The LOGGER is initialised in utilities.py by init
import logging
LOGGER = logging.getLogger('QGIS')
Now to log a message do::
LOGGER.debug('Some debug message')
.. note:: The file logs are written to the user tmp dir e.g.:
/tmp/23-08-2012/timlinux/logs/qgis.log
"""
logger = logging.getLogger(logger_name)
logger.setLevel(logging.DEBUG)

default_handler_level = logging.DEBUG
# create formatter that will be added to the handlers
formatter = logging.Formatter(
"%(asctime)s - %(name)s - %(levelname)s - %(message)s"
)

qgis_handler = QgsLogHandler(logger_name, iface=iface)
qgis_handler.setFormatter(formatter)
add_logging_handler_once(logger, qgis_handler)

if False:
# create console handler with a higher log level
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.INFO)
console_handler.setFormatter(formatter)
add_logging_handler_once(logger, console_handler)

if log_file:
# create syslog handler which logs even debug messages
if not isinstance(log_file, Path):
log_file = Path(log_file)

assert log_file.is_file()

file_handler = logging.FileHandler(str(log_file))
file_handler.setLevel(default_handler_level)
file_handler.setFormatter(formatter)
add_logging_handler_once(logger, file_handler)

if False:
# Sentry handler - this is optional hence the localised import
# It will only log if pip install raven. If raven is available
# logging messages will be sent to the sentry host.
# We will only log exceptions.
# Enable the 'plugins/use_sentry' QgsSettings option
# before this will be enabled.
from qgis.core import QgsSettings

settings = QgsSettings()
app = "some_app"
flag = settings.value(
key=f"{app}/sentry-logging", defaultValue=False, type=bool
)

if flag:
third_party_path = os.path.abspath(
os.path.join(os.path.dirname(__file__), "third_party")
)
if third_party_path not in sys.path:
sys.path.append(third_party_path)
# noinspection PyUnresolvedReferences
from raven.handlers.logging import (
SentryHandler,
) # Deprecated for https://github.com/getsentry/sentry-python

# noinspection PyUnresolvedReferences
from raven import Client

client = Client(sentry_url)
sentry_handler = SentryHandler(client)
sentry_handler.setFormatter(formatter)
sentry_handler.setLevel(logging.ERROR)
if add_logging_handler_once(logger, sentry_handler):
logger.debug("Sentry logging enabled")
else:
logger.debug("Sentry logging disabled")

return logger


IGNORE = """
from qgis.core import QgsNetworkAccessManager, QgsMessageLog, Qgis, QgsApplication
import os, logging
LOGDIR = os.path.dirname(os.path.realpath(__file__))
LOGFILE = os.path.join(LOGDIR, 'log.txt')
def setupLogger(logfile):
logger = logging.getLogger('logger')
if (logger.hasHandlers()):
logger.handlers.clear()
formatter = logging.Formatter('%(asctime)s %(name)s %(levelname)s: %(message)s')
logger.setLevel(logging.DEBUG)
fileHandler = logging.FileHandler(logfile)
fileHandler.setLevel(logging.DEBUG)
fileHandler.setFormatter(formatter)
logger.addHandler(fileHandler)
return logger
logger = setupLogger(LOGFILE)
# Map received messages to logger
def write_log_message(message, tag, level):
if level == Qgis.Warning:
logger.warning(message)
elif level == Qgis.Critical:
logger.error(message)
else:
logger.info(message)
# Connect the message received signal to the utility function
QgsApplication.messageLog().messageReceived.connect(write_log_message)
"""
50 changes: 31 additions & 19 deletions jord/qgis_utilities/layer_creation.py
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,13 @@ def add_qgis_single_feature_layer(
categorise_by_attribute in fields
), f"{categorise_by_attribute} was not found in {fields}"

if not isinstance(qgis_instance_handle, QgsProject):
qgis_project = qgis_instance_handle.qgis_project
elif qgis_instance_handle is None:
qgis_project = QgsProject.instance()
else:
qgis_project = qgis_instance_handle

if geom_type == GeoJsonGeometryTypesEnum.geometry_collection.value.__name__:
for g in geom.asGeometryCollection(): # TODO: Look into recursion?
uri = json.loads(g.asJson())["type"]
Expand Down Expand Up @@ -150,16 +157,13 @@ def add_qgis_single_feature_layer(
return_collection.append(sub_layer)

if group:
qgis_instance_handle.qgis_project.addMapLayer(sub_layer, False)

qgis_project.addMapLayer(sub_layer, False)
group.insertLayer(0, sub_layer)
else:
qgis_instance_handle.qgis_project.addMapLayer(sub_layer)
qgis_project.addMapLayer(sub_layer)

layer_tree_handle = (
qgis_instance_handle.qgis_project.layerTreeRoot().findLayer(
sub_layer.id()
)
)
layer_tree_handle = qgis_project.layerTreeRoot().findLayer(sub_layer.id())
if layer_tree_handle:
layer_tree_handle.setItemVisibilityChecked(visible)
else:
Expand Down Expand Up @@ -211,14 +215,12 @@ def add_qgis_single_feature_layer(
return_collection.append(layer)

if group:
qgis_instance_handle.qgis_project.addMapLayer(layer, False)
qgis_project.addMapLayer(layer, False)
group.insertLayer(0, layer)
else:
qgis_instance_handle.qgis_project.addMapLayer(layer)
qgis_project.addMapLayer(layer)

layer_tree_handle = qgis_instance_handle.qgis_project.layerTreeRoot().findLayer(
layer.id()
)
layer_tree_handle = qgis_project.layerTreeRoot().findLayer(layer.id())
if layer_tree_handle:
layer_tree_handle.setItemVisibilityChecked(visible)

Expand Down Expand Up @@ -286,10 +288,15 @@ def add_qgis_multi_feature_layer(
:return:
"""

# noinspection PyUnresolvedReferences
from qgis.core import QgsVectorLayer, QgsFeature
from .categorisation import categorise_layer

# noinspection PyUnresolvedReferences
from qgis.core import (
QgsFeature,
QgsVectorLayer,
QgsProject,
)

# noinspection PyUnresolvedReferences
import qgis

Expand Down Expand Up @@ -422,15 +429,20 @@ def add_qgis_multi_feature_layer(
layer.updateFields()
layer.updateExtents()

if not isinstance(qgis_instance_handle, QgsProject):
qgis_project = qgis_instance_handle.qgis_project
elif qgis_instance_handle is None:
qgis_project = QgsProject.instance()
else:
qgis_project = qgis_instance_handle

if group:
qgis_instance_handle.qgis_project.addMapLayer(layer, False)
qgis_project.addMapLayer(layer, False)
group.insertLayer(0, layer)
else:
qgis_instance_handle.qgis_project.addMapLayer(layer)
qgis_project.addMapLayer(layer)

layer_tree_handle = qgis_instance_handle.qgis_project.layerTreeRoot().findLayer(
layer.id()
)
layer_tree_handle = qgis_project.layerTreeRoot().findLayer(layer.id())
if layer_tree_handle:
layer_tree_handle.setItemVisibilityChecked(visible)

Expand Down

0 comments on commit bee89f6

Please sign in to comment.