summaryrefslogtreecommitdiff
path: root/lib/debug.py
diff options
context:
space:
mode:
authorkarnigen <karnigen@gmail.com>2024-05-03 01:34:58 +0200
committerGitHub <noreply@github.com>2024-05-03 01:34:58 +0200
commitbf5c2dfd67fac98868f86276504715ecfe1c369a (patch)
tree3b6bd611f452156fab48201fe7c868536dcb3e2d /lib/debug.py
parentad2914284e8ef5f59c410018415dbb8d574586f8 (diff)
Kgn/logging revamp (#2720)
* update config files * rebase after electron remove * added toml to requirements * logging update * Unified use of the TOML format instead of INI [no ci] * Unified use of the TOML format instead of INI [no ci] * moving debug*.py to debug dir, moving some part for debugger [no ci] * use of alternate logging in some cases * updated debug logger [no ci] * logging update * updated notes * updated notes about logging * style check
Diffstat (limited to 'lib/debug.py')
-rw-r--r--lib/debug.py421
1 files changed, 0 insertions, 421 deletions
diff --git a/lib/debug.py b/lib/debug.py
deleted file mode 100644
index e72f6435..00000000
--- a/lib/debug.py
+++ /dev/null
@@ -1,421 +0,0 @@
-# Authors: see git history
-#
-# Copyright (c) 2010 Authors
-# Licensed under the GNU GPL version 3.0 or later. See the file LICENSE for details.
-
-import os
-import sys
-import atexit # to save svg file on exit
-import socket # to check if debugger is running
-import time # to measure time of code block, use time.monotonic() instead of time.time()
-from datetime import datetime
-
-from contextlib import contextmanager # to measure time of with block
-import configparser # to read DEBUG.ini
-from pathlib import Path # to work with paths as objects
-
-import inkex
-from lxml import etree # to create svg file
-
-from .svg import line_strings_to_path
-from .svg.tags import INKSCAPE_GROUPMODE, INKSCAPE_LABEL
-
-
-# decorator to check if debugging is enabled
-# - if debug is not enabled then decorated function is not called
-def check_enabled(func):
- def decorated(self, *args, **kwargs):
- if self.enabled:
- return func(self, *args, **kwargs)
-
- return decorated
-
-
-# unwrapping = provision for functions as arguments
-# - if argument is callable then it is called and return value is used as argument
-# otherwise argument is returned as is
-def _unwrap(arg):
- if callable(arg):
- return arg()
- else:
- return arg
-
-
-# decorator to unwrap arguments if they are callable
-# eg: if argument is lambda function then it is called and return value is used as argument
-def unwrap_arguments(func):
- def decorated(self, *args, **kwargs):
- unwrapped_args = [_unwrap(arg) for arg in args]
- unwrapped_kwargs = {name: _unwrap(value) for name, value in kwargs.items()}
-
- return func(self, *unwrapped_args, **unwrapped_kwargs)
-
- return decorated
-
-
-class Debug(object):
- """Tools to help debug Ink/Stitch
-
- This class contains methods to log strings and SVG elements. Strings are
- logged to debug.log, and SVG elements are stored in debug.svg to aid in
- debugging stitch algorithms.
-
- All functionality is gated by self.enabled. If debugging is not enabled,
- then debug calls will consume very few resources. Any method argument
- can be a callable, in which case it is called and the return value is
- logged instead. This way one can log potentially expensive expressions
- by wrapping them in a lambda:
-
- debug.log(lambda: some_expensive_function(some_argument))
-
- The lambda is only called if debugging is enabled.
- """
-
- def __init__(self):
- self.debugger = None
- self.wait_attach = True
- self.enabled = False
- self.last_log_time = None
- self.current_layer = None
- self.group_stack = []
-
- def enable(self, debug_type, debug_dir: Path, ini: configparser.ConfigParser):
- # initilize file names and other parameters from DEBUG.ini file
- self.debug_dir = debug_dir # directory where debug files are stored
- self.debug_log_file = ini.get("DEBUG", "debug_log_file", fallback="debug.log")
- self.debug_svg_file = ini.get("DEBUG", "debug_svg_file", fallback="debug.svg")
- self.wait_attach = ini.getboolean("DEBUG", "wait_attach", fallback=True) # currently only for vscode
-
- if debug_type == 'none':
- return
-
- self.debugger = debug_type
- self.enabled = True
- self.init_log()
- self.init_debugger()
- self.init_svg()
-
- def init_log(self):
- self.log_file = self.debug_dir / self.debug_log_file
- # delete old content
- with self.log_file.open("w"):
- pass
- self.log("Debug logging enabled.")
-
- # we intentionally disable flakes C901 - function is too complex, beacuse it is used only for debugging
- # currently complexity is set 10 see 'make style', this means that function can have max 10 nested blocks, here we have more
- # flake8: noqa: C901
- def init_debugger(self):
- # ### General debugging notes:
- # 1. to enable debugging or profiling copy DEBUG_template.ini to DEBUG.ini and edit it
-
- # ### How create bash script for offline debugging from console
- # 1. in DEBUG.ini set create_bash_script = True
- # 2. call inkstitch.py extension from inkscape to create bash script named by bash_file_base in DEBUG.ini
- # 3. run bash script from console
-
- # ### Enable debugging
- # 1. set debug_type to one of - vscode, pycharm, pydev, see below for details
- # debug_type = vscode - 'debugpy' for vscode editor
- # debug_type = pycharm - 'pydevd-pycharm' for pycharm editor
- # debug_type = pydev - 'pydevd' for eclipse editor
- # 2. set debug_enable = True in DEBUG.ini
- # or use command line argument -d in bash script
- # or set environment variable INKSTITCH_DEBUG_ENABLE = True or 1 or yes or y
-
- # ### Enable profiling
- # 1. set profiler_type to one of - cprofile, profile, pyinstrument
- # profiler_type = cprofile - 'cProfile' profiler
- # profiler_type = profile - 'profile' profiler
- # profiler_type = pyinstrument- 'pyinstrument' profiler
- # 2. set profile_enable = True in DEBUG.ini
- # or use command line argument -p in bash script
- # or set environment variable INKSTITCH_PROFILE_ENABLE = True or 1 or yes or y
-
- # ### Miscelaneous notes:
- # - to disable debugger when running from inkscape set disable_from_inkscape = True in DEBUG.ini
- # - to write debug output to file set debug_to_file = True in DEBUG.ini
- # - to change various output file names see DEBUG.ini
- # - to disable waiting for debugger to attach (vscode editor) set wait_attach = False in DEBUG.ini
- # - to prefer inkscape version of inkex module over pip version set prefer_pip_inkex = False in DEBUG.ini
-
- # ###
-
- # ### How to debug Ink/Stitch with LiClipse:
- #
- # 1. Install LiClipse (liclipse.com) -- no need to install Eclipse first
- # 2. Start debug server as described here: http://www.pydev.org/manual_adv_remote_debugger.html
- # * follow the "Note:" to enable the debug server menu item
- # 3. Copy and edit a file named "DEBUG.ini" from "DEBUG_template.ini" next to inkstitch.py in your git clone
- # and set debug_type = pydev
- # 4. Run any extension and PyDev will start debugging.
-
- # ###
-
- # ### To debug with PyCharm:
-
- # You must use the PyCharm Professional Edition and _not_ the Community
- # Edition. Jetbrains has chosen to make remote debugging a Pro feature.
- # To debug Inkscape python extensions, the extension code and Inkscape run
- # independently of PyCharm, and only communicate with the debugger via a
- # TCP socket. Thus, debugging is "remote," even if it's on the same machine,
- # connected via the loopback interface.
- #
- # 1. pip install pydev_pycharm
- #
- # pydev_pycharm is versioned frequently. Jetbrains suggests installing
- # a version at least compatible with the current build. For example, if your
- # PyCharm build, as found in menu PyCharm -> About Pycharm is 223.8617.48,
- # you could do:
- # pip install pydevd-pycharm~=223.8617.48
- #
- # 2. From the Pycharm "Run" menu, choose "Edit Configurations..." and create a new
- # configuration. Set "IDE host name:" to "localhost" and "Port:" to 5678.
- # You can leave the default settings for all other choices.
- #
- # 3. Touch a file named "DEBUG.ini" at the top of your git repo, as above
- # set debug_type = pycharm
- #
- # 4. Create a symbolic link in the Inkscape extensions directory to the
- # top-level directory of your git repo. On a mac, for example:
- # cd ~/Library/Application\ Support/org.inkscape.Inkscape/config/inkscape/extensions/
- # ln -s <full path to the top level of your Ink/Stitch git repo>
- # On other architectures it may be:
- # cd ~/.config/inkscape/extensions
- # ln -s <full path to the top level of your Ink/Stitch git repo>
- # Remove any other Ink/Stitch files or references to Ink/Stitch from the
- # extensions directory, or you'll see duplicate entries in the Ink/Stitch
- # extensions menu in Inkscape.
- #
- # 5. In Pycharm, either click on the green "bug" icon if visible in the upper
- # right or press Ctrl-D to start debugging.The PyCharm debugger pane will
- # display the message "Waiting for process connection..."
- #
- # 6. Do some action in Inkscape which invokes Ink/Stitch extension code, and the
- # debugger will be triggered. If you've left "Suspend after connect" checked
- # in the Run configuration, PyCharm will pause in the "self.log("Enabled
- # PyDev debugger.)" statement, below. Uncheck the box to have it continue
- # automatically to your first set breakpoint.
-
- # ###
-
- # ### To debug with VS Code
- # see: https://code.visualstudio.com/docs/python/debugging#_command-line-debugging
- # https://code.visualstudio.com/docs/python/debugging#_debugging-by-attaching-over-a-network-connection
- #
- # 1. Install the Python extension for VS Code
- # pip install debugpy
- # 2. create .vscode/launch.json containing:
- # "configurations": [ ...
- # {
- # "name": "Python: Attach",
- # "type": "python",
- # "request": "attach",
- # "connect": {
- # "host": "localhost",
- # "port": 5678
- # }
- # }
- # ]
- # 3. Touch a file named "DEBUG.ini" at the top of your git repo, as above
- # set debug_type = vscode
- # 4. Start the debug server in VS Code by clicking on the debug icon in the left pane
- # select "Python: Attach" from the dropdown menu and click on the green arrow.
- # The debug server will start and connect to already running python processes,
- # but immediately exit if no python processes are running.
- #
- # Notes:
- # to see flask server url routes:
- # - comment out the line self.disable_logging() in run() of lib/api/server.py
-
- try:
- if self.debugger == 'vscode':
- import debugpy
- elif self.debugger == 'pycharm':
- import pydevd_pycharm
- elif self.debugger == 'pydev':
- import pydevd
- elif self.debugger == 'file':
- pass
- else:
- raise ValueError(f"unknown debugger: '{self.debugger}'")
-
- except ImportError:
- self.log(f"importing debugger failed (debugger disabled) for {self.debugger}")
-
- # pydevd likes to shout about errors to stderr whether I want it to or not
- with open(os.devnull, 'w') as devnull:
- stderr = sys.stderr
- sys.stderr = devnull
-
- try:
- if self.debugger == 'vscode':
- debugpy.listen(('localhost', 5678))
- if self.wait_attach:
- print("Waiting for debugger attach")
- debugpy.wait_for_client() # wait for debugger to attach
- debugpy.breakpoint() # stop here to start normal debugging
- elif self.debugger == 'pycharm':
- pydevd_pycharm.settrace('localhost', port=5678, stdoutToServer=True,
- stderrToServer=True)
- elif self.debugger == 'pydev':
- pydevd.settrace()
- elif self.debugger == 'file':
- pass
- else:
- raise ValueError(f"unknown debugger: '{self.debugger}'")
-
- except socket.error as error:
- self.log("Debugging: connection to pydevd failed: %s", error)
- self.log(f"Be sure to run 'Start debugging server' in {self.debugger} to enable debugging.")
- else:
- self.log(f"Enabled '{self.debugger}' debugger.")
-
- sys.stderr = stderr
-
- def init_svg(self):
- self.svg = etree.Element("svg", nsmap=inkex.NSS)
- atexit.register(self.save_svg)
-
- def save_svg(self):
- tree = etree.ElementTree(self.svg)
- debug_svg = self.debug_dir / self.debug_svg_file
- tree.write(str(debug_svg)) # lxml <5.0.0 does not support Path objects
-
- @check_enabled
- @unwrap_arguments
- def add_layer(self, name="Debug"):
- layer = etree.Element("g", {
- INKSCAPE_GROUPMODE: "layer",
- INKSCAPE_LABEL: name,
- "style": "display: none"
- })
- self.svg.append(layer)
- self.current_layer = layer
-
- @check_enabled
- @unwrap_arguments
- def open_group(self, name="Group"):
- group = etree.Element("g", {
- INKSCAPE_LABEL: name
- })
-
- self.log_svg_element(group)
- self.group_stack.append(group)
-
- @check_enabled
- @unwrap_arguments
- def close_group(self):
- if self.group_stack:
- self.group_stack.pop()
-
- @check_enabled
- @unwrap_arguments
- def log(self, message, *args):
- if self.last_log_time:
- message = "(+%s) %s" % (datetime.now() - self.last_log_time, message)
-
- self.raw_log(message, *args)
-
- def raw_log(self, message, *args):
- now = datetime.now()
- timestamp = now.isoformat()
- self.last_log_time = now
-
- with self.log_file.open("a") as logfile:
- print(timestamp, message % args, file=logfile)
- logfile.flush()
-
- # decorator to measure time of function
- def time(self, func):
- def decorated(*args, **kwargs):
- if self.enabled:
- self.raw_log("entering %s()", func.__name__)
- start = time.monotonic()
-
- result = func(*args, **kwargs)
-
- if self.enabled:
- end = time.monotonic()
- self.raw_log("leaving %s(), duration = %s", func.__name__, round(end - start, 6))
-
- return result
-
- return decorated
-
- @check_enabled
- @unwrap_arguments
- def log_svg_element(self, element):
- if self.current_layer is None:
- self.add_layer()
-
- if self.group_stack:
- self.group_stack[-1].append(element)
- else:
- self.current_layer.append(element)
-
- @check_enabled
- @unwrap_arguments
- def log_line_string(self, line_string, name=None, color=None):
- """Add a Shapely LineString to the SVG log."""
- self.log_line_strings([line_string], name, color)
-
- @check_enabled
- @unwrap_arguments
- def log_line_strings(self, line_strings, name=None, color=None):
- path = line_strings_to_path(line_strings)
- path.set('style', str(inkex.Style({"stroke": color or "#000000", "stroke-width": "0.3", "fill": None})))
-
- if name is not None:
- path.set(INKSCAPE_LABEL, name)
-
- self.log_svg_element(path)
-
- @check_enabled
- @unwrap_arguments
- def log_line(self, start, end, name="line", color=None):
- self.log_svg_element(etree.Element("path", {
- "d": "M%s,%s %s,%s" % (start + end),
- "style": str(inkex.Style({"stroke": color or "#000000", "stroke-width": "0.3", "fill": None})),
- INKSCAPE_LABEL: name
- }))
-
- @check_enabled
- @unwrap_arguments
- def log_point(self, point, name="point", color=None):
- self.log_svg_element(etree.Element("circle", {
- "cx": str(point.x),
- "cy": str(point.y),
- "r": "1",
- "style": str(inkex.Style({"fill": "#000000"})),
- }))
-
- @check_enabled
- @unwrap_arguments
- def log_graph(self, graph, name="Graph", color=None):
- d = ""
-
- for edge in graph.edges:
- d += "M%s,%s %s,%s" % (edge[0] + edge[1])
-
- self.log_svg_element(etree.Element("path", {
- "d": d,
- "style": str(inkex.Style({"stroke": color or "#000000", "stroke-width": "0.3", "fill": None})),
- INKSCAPE_LABEL: name
- }))
-
- # decorator to measure time of with block
- @contextmanager
- def time_this(self, label="code block"):
- if self.enabled:
- start = time.monotonic()
- self.raw_log("begin %s", label)
-
- yield
-
- if self.enabled:
- self.raw_log("completed %s, duration = %s", label, time.monotonic() - start)
-
-
-# global debug object
-debug = Debug()