summaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
Diffstat (limited to 'lib')
-rw-r--r--lib/debug.py421
-rw-r--r--lib/debug/__init__.py11
-rw-r--r--lib/debug/debug.py251
-rw-r--r--lib/debug/debugger.py192
-rw-r--r--lib/debug/logging.py294
-rw-r--r--lib/debug/utils.py (renamed from lib/debug_utils.py)63
-rw-r--r--lib/extensions/break_apart.py6
-rw-r--r--lib/extensions/print_pdf.py6
8 files changed, 787 insertions, 457 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()
diff --git a/lib/debug/__init__.py b/lib/debug/__init__.py
new file mode 100644
index 00000000..8093e6a0
--- /dev/null
+++ b/lib/debug/__init__.py
@@ -0,0 +1,11 @@
+# Authors: see git history
+#
+# Copyright (c) 2010 Authors
+# Licensed under the GNU GPL version 3.0 or later. See the file LICENSE for details.
+
+
+# this enable:
+# from .debug import debug
+# from ..debug import debug
+from .debug import debug
+
diff --git a/lib/debug/debug.py b/lib/debug/debug.py
new file mode 100644
index 00000000..ab5132e4
--- /dev/null
+++ b/lib/debug/debug.py
@@ -0,0 +1,251 @@
+# 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 atexit # to save svg file on exit
+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
+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
+
+import logging
+logger = logging.getLogger("inkstitch.debug") # create module logger with name 'inkstitch.debug'
+
+# to log messages if previous debug logger is not enabled
+logger_inkstich = logging.getLogger("inkstitch") # create module logger with name 'inkstitch'
+
+
+# --------------------------------------------------------------------------------------------
+# 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.enabled = False
+ self.last_log_time = None
+ self.current_layer = None
+ self.group_stack = []
+ self.svg_filename = None
+
+ def enable(self):
+ # determine svg filename from logger
+ if len(logger.handlers) > 0 and isinstance(logger.handlers[0], logging.FileHandler):
+ # determine filename of svg file from logger
+ filename = Path(logger.handlers[0].baseFilename)
+ self.svg_filename = filename.with_suffix(".svg")
+ self.svg_filename.unlink(missing_ok=True) # remove existing svg file
+
+ # self.log is activated by active logger
+ # - enabled only if logger first handler is FileHandler
+ # to disable "inkstitch.debug" simply set logging level to CRITICAL
+ if logger.isEnabledFor(logging.INFO) and self.svg_filename is not None:
+ self.enabled = True
+ self.log(f"Logging enabled with svg file: {self.svg_filename}")
+ self.init_svg()
+
+ else:
+ # use alternative logger to log message if logger has no handlers
+ logger_inkstich.info("No handlers in logger, cannot enable logging and svg file creation")
+
+ def init_svg(self):
+ self.svg = etree.Element("svg", nsmap=inkex.NSS)
+ atexit.register(self.save_svg)
+
+ def save_svg(self):
+ if self.enabled and self.svg_filename is not None:
+ self.log(f"Writing svg file: {self.svg_filename}")
+ tree = etree.ElementTree(self.svg)
+ tree.write(str(self.svg_filename)) # lxml <5.0.0 does not support Path objects, requires string
+ else:
+ # use alternative logger to log message if logger has no handlers
+ logger_inkstich.info(f"Saving to svg file is not activated {self.svg_filename=}")
+
+ @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()
+ self.last_log_time = now
+
+ msg = message % args
+ logger.info(msg)
+
+ # 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()
diff --git a/lib/debug/debugger.py b/lib/debug/debugger.py
new file mode 100644
index 00000000..11293c8a
--- /dev/null
+++ b/lib/debug/debugger.py
@@ -0,0 +1,192 @@
+# Authors: see git history
+#
+# Copyright (c) 2024 Authors
+# Licensed under the GNU GPL version 3.0 or later. See the file LICENSE for details.
+
+
+# ### General debugging notes:
+# 1. to enable debugging or profiling copy DEBUG_template.toml to DEBUG.toml and edit it
+
+# ### How create bash script for offline debugging from console
+# 1. in DEBUG.toml set create_bash_script = true
+# 2. call inkstitch.py extension from inkscape to create bash script named by bash_file_base in DEBUG.toml
+# 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.toml
+# 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.toml
+# 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.toml
+# - to change various output file names see DEBUG.toml
+# - to disable waiting for debugger to attach (vscode editor) set wait_attach = false in DEBUG.toml
+# - to prefer inkscape version of inkex module over pip version set prefer_pip_inkex = false in DEBUG.toml
+
+# ###
+
+# ### 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.toml" from "DEBUG_template.toml" 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.toml" 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.toml" 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
+
+import os
+import sys
+
+import socket # to check if debugger is running
+
+from .utils import safe_get # mimic get method of dict with default value
+
+import logging
+
+logger = logging.getLogger("inkstitch")
+
+# 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(debug_type:str, ini: dict):
+ if debug_type == 'none':
+ return
+
+ wait_attach = safe_get(ini, "DEBUG", "wait_attach", default=True) # currently only for vscode
+ debugger = debug_type
+
+ try:
+ if debugger == 'vscode':
+ import debugpy
+ elif debugger == 'pycharm':
+ import pydevd_pycharm
+ elif debugger == 'pydev':
+ import pydevd
+ elif debugger == 'file':
+ pass
+ else:
+ raise ValueError(f"unknown debugger: '{debugger}'")
+
+ except ImportError:
+ logger.info(f"importing debugger failed (debugger disabled) for {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 debugger == 'vscode':
+ debugpy.listen(('localhost', 5678))
+ if wait_attach:
+ print("Waiting for debugger attach", file=sys.stderr)
+ debugpy.wait_for_client() # wait for debugger to attach
+ debugpy.breakpoint() # stop here to start normal debugging
+ elif debugger == 'pycharm':
+ pydevd_pycharm.settrace('localhost', port=5678, stdoutToServer=True,
+ stderrToServer=True)
+ elif debugger == 'pydev':
+ pydevd.settrace()
+ elif debugger == 'file':
+ pass
+ else:
+ raise ValueError(f"unknown debugger: '{debugger}'")
+
+ except socket.error as error:
+ logger.info("Debugging: connection to pydevd failed: %s", error)
+ logger.info(f"Be sure to run 'Start debugging server' in {debugger} to enable debugging.")
+ else:
+ logger.info(f"Enabled '{debugger}' debugger.")
+
+ sys.stderr = stderr
diff --git a/lib/debug/logging.py b/lib/debug/logging.py
new file mode 100644
index 00000000..4d3683b6
--- /dev/null
+++ b/lib/debug/logging.py
@@ -0,0 +1,294 @@
+# Authors: see git history
+#
+# Copyright (c) 2024 Authors
+# Licensed under the GNU GPL version 3.0 or later. See the file LICENSE for details.
+
+# basic info for inkstitch logging:
+# ---------------------------------
+# some idea can be found in: Modern Python logging - https://www.youtube.com/watch?v=9L77QExPmI0
+#
+# logging vs warnings
+# -------------------
+# warnings - see https://docs.python.org/3/library/warnings.html
+# logging - see https://docs.python.org/3/library/logging.html
+#
+# In simplified terms, use "warning" to alert that a specific function is deprecated, and in all other cases, use "logging".
+# Warnings are primarily intended for libraries where there's a newer solution available, but for backward compatibility
+# reasons, the old functionality is retained.
+#
+# In complex applications like Inkstitch, it might be sensible to exclusively use one method, namely "logging",
+# to unify and simplify the messaging system of such a system.
+#
+#
+# root logger:
+# ------------
+# - The primary logger orchestrates all other loggers through logging.xxx() calls.
+# - It should only be utilized at the application's highest level to manage the logging of all loggers.
+# - It can easily disable all loggers by invoking logging.disable() and channel all warnings to logging
+# by setting logging.captureWarnings(True) with the level WARNING.
+# - The configuration of all loggers can be achieved via a file, and logging.config.dictConfig(logging_dict).
+#
+
+# module logger:
+# --------------
+# - Instantiate the logger by invoking logger=getLogger(name).
+# Avoid using __name__ as the name, as it generates numerous loggers per application.
+# The logger name persists globally throughout the application.
+# - Avoid configuring the module logger within the module itself;
+# instead, utilize the top-level application configuration with logging.config.
+# This allows users of the application to customize it according to their requirements.
+
+# example of module logger:
+# -------------------------
+# import logging
+# logger = logging.getLogger('inkstitch') # create module logger with name 'inkstitch', but configure it at top level of app
+# ...
+# logger.debug('debug message') # example of using module logger
+# ...
+
+# top level of the application:
+# ----------------------------
+# - configure root and other loggers
+# - best practice is to configure from a file: eg logging.config.fileConfig('logging.conf')
+# - consider toml format for logging configuration (json, yaml, xml, dict are also possible)
+#
+
+# list of loggers in inkstitch (not complete):
+# -------------------------------------------
+# - root - main logger that controls all other loggers
+# - inkstitch - suggested name for inkstitch
+# - inkstitch.debug - uses in debug module with svg file saving
+#
+# third-party loggers:
+# --------------------
+# - werkzeug - is used by flask
+# - shapely.geos - was used by shapely but currently replaced by exceptions and warnings
+#
+
+# --------------------------------------------------------------------------------------------
+import os
+import sys
+from pathlib import Path
+
+if sys.version_info >= (3, 11):
+ import tomllib # built-in in Python 3.11+
+else:
+ import tomli as tomllib
+
+import warnings # to control python warnings
+import logging # to configure logging
+import logging.config # to configure logging from dict
+
+from .utils import safe_get # mimic get method of dict with default value
+
+logger = logging.getLogger('inkstitch')
+
+
+# --------------------------------------------------------------------------------------------
+# activate_logging - configure logging for inkstitch application
+def activate_logging(running_as_frozen: bool, ini: dict, SCRIPTDIR: Path):
+ if running_as_frozen: # in release mode
+ activate_for_frozen()
+ else: # in development
+ activate_for_development(ini, SCRIPTDIR)
+
+
+# Configure logging in frozen (release) mode of application:
+# in release mode normally we want to ignore all warnings and logging, but we can enable it by setting environment variables
+# - INKSTITCH_LOGLEVEL - logging level:
+# 'DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'
+# - PYTHONWARNINGS, -W - warnings action controlled by python
+# actions: 'error', 'ignore', 'always', 'default', 'module', 'once'
+def activate_for_frozen():
+ loglevel = os.environ.get('INKSTITCH_LOGLEVEL') # read log level from environment variable or None
+ docpath = os.environ.get('DOCUMENT_PATH') # read document path from environment variable (set by inkscape) or None
+
+ if docpath is not None and loglevel is not None and loglevel.upper() in ['DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL']:
+
+ # The end user enabled logging and warnings are redirected to the input_svg.inkstitch.log file.
+
+ vars = {
+ 'loglevel': loglevel.upper(),
+ 'logfilename': Path(docpath).with_suffix('.inkstitch.log') # log file is created in document path
+ }
+ config = expand_variables(frozen_config, vars)
+
+ # dictConfig has access to top level variables, dict contains: ext://__main__.var
+ # - restriction: variable must be last token in string - very limited functionality, avoid using it
+
+ # After this operation, logging will be activated, so we can use the logger.
+ logging.config.dictConfig(config) # configure root logger from dict
+
+ logging.captureWarnings(True) # capture all warnings to log file with level WARNING
+ else:
+ logging.disable() # globally disable all logging of all loggers
+ warnings.simplefilter('ignore') # ignore all warnings
+
+
+# in development mode we want to use configuration from some LOGGING.toml file
+def activate_for_development(ini: dict, SCRIPTDIR: Path):
+ logging_config_file = safe_get(ini, "LOGGING", "log_config_file", default=None)
+ vars = {'SCRIPTDIR': SCRIPTDIR} # dynamic data for logging configuration
+
+ if logging_config_file is not None:
+ logging_config_file = Path(logging_config_file)
+ if logging_config_file.exists():
+ with open(logging_config_file, "rb") as f:
+ devel_config = tomllib.load(f) # -> dict
+ else:
+ raise FileNotFoundError(f"{logging_config_file} file not found")
+ else: # if LOGGING.toml file does not exist, use default logging configuration
+ vars['loglevel'] = 'DEBUG' # set log level to DEBUG
+ vars['logfilename'] = SCRIPTDIR / "inkstitch.log" # log file is created in current directory
+ devel_config = development_config # get TOML configuration from module
+
+ configure_logging(devel_config, ini, vars) # initialize and activate logging configuration
+
+ logger.info("Running in development mode")
+ logger.info(f"Using logging configuration from file: {logging_config_file}")
+ logger.debug(f"Logging configuration: {devel_config = }")
+
+
+# --------------------------------------------------------------------------------------------
+# configure logging from dictionary:
+# - capture all warnings to log file with level WARNING - depends on warnings_capture
+# - set action for warnings: 'error', 'ignore', 'always', 'default', 'module', 'once' - depends on warnings_action
+def configure_logging(config: dict, ini: dict, vars: dict):
+ config = expand_variables(config, vars)
+
+ # After this operation, logging will be activated, so we can use the logger.
+ logging.config.dictConfig(config) # configure loggers from dict - using loglevel, logfilename
+
+ warnings_capture = config.get('warnings_capture', True)
+ logging.captureWarnings(warnings_capture) # capture warnings to log file with level WARNING
+ warnings_action = config.get('warnings_action', 'default').lower()
+ warnings.simplefilter(warnings_action) # set action for warnings: 'error', 'ignore', 'always', ...
+
+ disable_logging = safe_get(ini, "LOGGING", "disable_logging", default=False)
+ if disable_logging:
+ logger.warning(f"Logging is disabled by configuration in ini file. {disable_logging = }")
+ logging.disable() # globally disable all logging of all loggers
+
+
+# Evaluate evaluation of variables in logging configuration:
+# "handlers": {
+# "file": {
+# "filename": "%(SCRIPTDIR)s/xxx.log", # <--- replace %(SCRIPTDIR)s -> script path
+# "%(logfilename)s", # <--- replace %(logfilename)s -> log file name
+# ...
+# "loggers": {
+# "inkstitch": {
+# "level": "%(loglevel)s", # <--- replace %(loglevel)s -> log level
+# ...
+# - for external configuration file (eg. LOGGING.toml) we cannot pass parameters such as the current directory.
+# - we do: filename = "%(SCRIPTDIR)s/inkstitch.log" -> filename = "path/inkstitch.log"
+# - safety: we can use only predefined variables in myvars, otherwise we get KeyError
+# - return modified configuration
+# - create logging directory if not exists, directory cannot end with ":" to avoid error with ext:// keys
+def expand_variables(cfg: dict, vars: dict):
+ for k, v in cfg.get('loggers', {}).items():
+ if 'level' in v: # replace level in logger
+ cfg['loggers'][k]['level'] = v['level'] % vars
+
+ for k, v in cfg.get('handlers', {}).items():
+ if 'filename' in v: # replace filename in handler
+ orig_filename = v['filename'] # original filename for later comparison
+ cfg['handlers'][k]['filename'] = v['filename'] % vars
+ # create logging directory only if substitution was done, we need to avoid ext:// cfg:// keys
+ if orig_filename != cfg['handlers'][k]['filename']:
+ dirname = Path(cfg['handlers'][k]['filename']).parent
+ if not dirname.exists():
+ # inform user about creating logging directory, otherwise it is silent, logging is not yet active
+ print(f"DEBUG: Creating logging directory: {dirname} ", file=sys.stderr)
+ dirname.mkdir(parents=True, exist_ok=True)
+ return cfg
+
+
+def startup_info(logger: logging.Logger, SCRIPTDIR: Path, running_as_frozen: bool, running_from_inkscape: bool,
+ debug_active: bool, debug_type: str, profiler_type: str):
+ logger.info(f"Running as frozen: {running_as_frozen}")
+ logger.info(f"Running from inkscape: {running_from_inkscape}")
+ logger.info(f"Debugger active: {debug_active}")
+ logger.info(f"Debugger type: {debug_type!r}")
+ logger.info(f"Profiler type: {profiler_type!r}")
+
+ # log Python version, platform, command line arguments, sys.path
+ import sys
+ import platform
+
+ logger.info(f"Python version: {sys.version}")
+ logger.info(f"Platform: {platform.platform()}")
+ logger.info(f"Command line arguments: {sys.argv}")
+ logger.debug(f"sys.path: {sys.path}")
+
+
+# example of logger configuration for release mode:
+# ------------------------------------------------
+# - logger suitable for release mode, where we assume that the directory of the input SVG file allows writing the log file.
+# - in inkstitch.py we check release mode and environment variable INKSTITCH_LOGLEVEL
+# - this config redirect all loggers to file svg_file.inkstitch.log to directory of svg file
+# - set loglevel and logfilename in inkstitch.py before calling logging.config.dictConfig(frozen_config)
+frozen_config = {
+ "version": 1, # mandatory key and value (int) is 1
+ "disable_existing_loggers": False, # false enable all loggers not defined here, true disable
+ "filters": {}, # no filters
+ "formatters": {
+ "simple": { # formatter name (https://docs.python.org/3/library/logging.html#logging.LogRecord)
+ "format": '%(asctime)s [%(levelname)s]: %(filename)s.%(funcName)s: %(message)s' # format string
+ }
+ },
+ "handlers": {
+ "file": {
+ "class": "logging.FileHandler", # type - file output
+ "formatter": "simple", # use formatter 'simple' for handler 'file'
+ "filename": "%(logfilename)s", # access variable logfilename
+ "mode": "w" # create new file
+ }
+ },
+ "loggers": {
+ "root": { # top level logger
+ "level": "%(loglevel)s", # access variable loglevel
+ "handlers": ["file"], # use handler 'file' for logger
+ }
+ },
+}
+
+# ---------------------------------------------------
+# example of implicit developer logger configuration:
+# ---------------------------------------------------
+# - configured two loggers: root and inkstitch loggers
+# - output is redirected to file 'logfilename' in the directory of inkstitch.py
+# - this configuration uses only one log level 'loglevel for both the root and inkstitch loggers.
+# - set loglevel and logfilename in inkstitch.py before calling logging.config.dictConfig(development_config)
+development_config = {
+ "warnings_action": "default", # dafault action for warnings
+ "warnings_capture": True, # capture warnings to log file with level WARNING
+
+ "version": 1, # mandatory key and value (int) is 1
+ "disable_existing_loggers": False, # false enable all loggers not defined here, true disable
+ "filters": {}, # no filters
+ "formatters": {
+ "simple": { # formatter name (https://docs.python.org/3/library/logging.html#logging.LogRecord)
+ "format": '%(asctime)s [%(levelname)s]: %(filename)s.%(funcName)s: %(message)s' # format string
+ }
+ },
+ "handlers": {
+ "file": {
+ "class": "logging.FileHandler", # type - file output
+ "formatter": "simple", # use formatter 'simple' for handler 'file'
+ "filename": "%(logfilename)s", # ext: --> access variable logfilename
+ "mode": "w" # create new file
+ },
+ },
+ "loggers": { # configure loggers
+ "inkstitch": { # specific logger to inkstitch application
+ "level": "%(loglevel)s", # ext: --> access variable loglevel
+ "handlers": ["file"], # use handler 'file' for logger
+ "propagate": False, # don't propagate to root logger - otherwise all will be logged twice
+ },
+ "root": { # top level logger
+ "level": "%(loglevel)s", # ext: --> access variable loglevel
+ "handlers": ["file"], # use handler 'file' for logger
+ }
+ },
+}
diff --git a/lib/debug_utils.py b/lib/debug/utils.py
index ef8b364d..9618d173 100644
--- a/lib/debug_utils.py
+++ b/lib/debug/utils.py
@@ -1,28 +1,42 @@
# Authors: see git history
#
-# Copyright (c) 2010 Authors
+# Copyright (c) 2024 Authors
# Licensed under the GNU GPL version 3.0 or later. See the file LICENSE for details.
+# this file is without: import inkex
+# - we need dump argv and sys.path as is on startup from inkscape
+# - later sys.path may be modified that influences importing inkex (see prefere_pip_inkex)
+
import os
import sys
from pathlib import Path # to work with paths as objects
-import configparser # to read DEBUG.ini
+import logging
-# this file is without: import inkex
-# - we need dump argv and sys.path as is on startup from inkscape
-# - later sys.path may be modified that influences importing inkex (see prefere_pip_inkex)
+logger = logging.getLogger("inkstitch")
+
+
+# safe_get - get value from nested dictionary, return default if key does not exist
+# - to read nested values from dict - mimic get method of dict with default value
+# example: safe_get({'a': {'b': 1}}, 'a', 'b') -> 1
+# safe_get({'a': {'b': 1}}, 'a', 'c', default=2) -> 2
+def safe_get(dictionary: dict, *keys, default=None):
+ for key in keys:
+ if key not in dictionary:
+ return default
+ dictionary = dictionary[key]
+ return dictionary
-def write_offline_debug_script(debug_script_dir: Path, ini: configparser.ConfigParser):
+def write_offline_debug_script(debug_script_dir: Path, ini: dict):
'''
prepare Bash script for offline debugging from console
arguments:
- debug_script_dir - Path object, absolute path to directory of inkstitch.py
- - ini - see DEBUG.ini
+ - ini - see DEBUG.toml
'''
# define names of files used by offline Bash script
- bash_file_base = ini.get("DEBUG", "bash_file_base", fallback="debug_inkstitch")
+ bash_file_base = safe_get(ini, "DEBUG", "bash_file_base", default="debug_inkstitch")
bash_name = Path(bash_file_base).with_suffix(".sh") # Path object
bash_svg = Path(bash_file_base).with_suffix(".svg") # Path object
@@ -125,7 +139,6 @@ def reorder_sys_path():
'''
change sys.path to prefer pip installed inkex over inkscape bundled inkex
'''
-
# see static void set_extensions_env() in inkscape/src/inkscape-main.cpp
# what we do:
# - move inkscape extensions path to the end of sys.path
@@ -147,34 +160,30 @@ def reorder_sys_path():
# -----------------------------------------------------------------------------
# try to resolve debugger type from ini file or cmd line of bash
-def resole_debug_type(ini: configparser.ConfigParser):
+def resolve_debug_type(ini: dict):
# enable/disable debugger from bash: -d
if os.environ.get('INKSTITCH_DEBUG_ENABLE', '').lower() in ['true', '1', 'yes', 'y']:
debug_enable = True
else:
- debug_enable = ini.getboolean("DEBUG", "debug_enable", fallback=False) # enable debugger on startup from ini
+ debug_enable = safe_get(ini, "DEBUG", "debug_enable", default=False) # enable debugger on startup from ini
- debug_type = ini.get("DEBUG", "debug_type", fallback="none") # debugger type vscode, pycharm, pydevd
+ debug_type = safe_get(ini, "DEBUG", "debug_type", default="none") # debugger type vscode, pycharm, pydevd
if not debug_enable:
debug_type = 'none'
- debug_to_file = ini.getboolean("DEBUG", "debug_to_file", fallback=False) # write debug output to file
- if debug_to_file and debug_type == 'none':
- debug_type = 'file'
-
return debug_type
# try to resolve profiler type from ini file or cmd line of bash
-def resole_profile_type(ini: configparser.ConfigParser):
+def resolve_profiler_type(ini: dict):
# enable/disable profiling from bash: -p
if os.environ.get('INKSTITCH_PROFILE_ENABLE', '').lower() in ['true', '1', 'yes', 'y']:
profile_enable = True
else:
- profile_enable = ini.getboolean("PROFILE", "profile_enable", fallback=False) # read from ini
+ profile_enable = safe_get(ini, "PROFILE", "profile_enable", default=False) # read from ini
# specify profiler type
- profiler_type = ini.get("PROFILE", "profiler_type", fallback="none") # profiler type cprofile, profile, pyinstrument
+ profiler_type = safe_get(ini, "PROFILE", "profiler_type", default="none") # profiler type cprofile, profile, pyinstrument
if not profile_enable:
profiler_type = 'none'
@@ -189,13 +198,19 @@ def resole_profile_type(ini: configparser.ConfigParser):
# - pyinstrument - profiler with nice html output
-def profile(profiler_type, profile_dir: Path, ini: configparser.ConfigParser, extension, remaining_args):
+def profile(profiler_type, profile_dir: Path, ini: dict, extension, remaining_args):
'''
profile with cProfile, profile or pyinstrument
'''
- profile_file_base = ini.get("PROFILE", "profile_file_base", fallback="debug_profile")
+ profile_file_base = safe_get(ini, "PROFILE", "profile_file_base", default="debug_profile")
profile_file_path = profile_dir / profile_file_base # Path object
+ # create directory if not exists
+ dirname = profile_file_path.parent
+ if not dirname.exists():
+ logger.debug(f"Creating directory for profile output: {dirname}")
+ dirname.mkdir(parents=True, exist_ok=True)
+
if profiler_type == 'cprofile':
with_cprofile(extension, remaining_args, profile_file_path)
elif profiler_type == 'profile':
@@ -206,7 +221,7 @@ def profile(profiler_type, profile_dir: Path, ini: configparser.ConfigParser, ex
raise ValueError(f"unknown profiler type: '{profiler_type}'")
-def with_cprofile(extension, remaining_args, profile_file_path):
+def with_cprofile(extension, remaining_args, profile_file_path: Path):
'''
profile with cProfile
'''
@@ -227,7 +242,7 @@ def with_cprofile(extension, remaining_args, profile_file_path):
file=sys.stderr)
-def with_profile(extension, remaining_args, profile_file_path):
+def with_profile(extension, remaining_args, profile_file_path: Path):
'''
profile with profile
'''
@@ -246,7 +261,7 @@ def with_profile(extension, remaining_args, profile_file_path):
file=sys.stderr)
-def with_pyinstrument(extension, remaining_args, profile_file_path):
+def with_pyinstrument(extension, remaining_args, profile_file_path: Path):
'''
profile with pyinstrument
'''
diff --git a/lib/extensions/break_apart.py b/lib/extensions/break_apart.py
index 581e49bc..b5104dc7 100644
--- a/lib/extensions/break_apart.py
+++ b/lib/extensions/break_apart.py
@@ -3,7 +3,6 @@
# Copyright (c) 2010 Authors
# Licensed under the GNU GPL version 3.0 or later. See the file LICENSE for details.
-import logging
from copy import copy
import inkex
@@ -92,12 +91,7 @@ class BreakApart(InkstitchExtension):
return polygons
def geom_is_valid(self, geom):
- # Don't complain about invalid shapes, we just want to know
- logger = logging.getLogger('shapely.geos')
- level = logger.level
- logger.setLevel(logging.CRITICAL)
valid = geom.is_valid
- logger.setLevel(level)
return valid
def ensure_minimum_size(self, polygons, size):
diff --git a/lib/extensions/print_pdf.py b/lib/extensions/print_pdf.py
index 6d850ccb..640a168b 100644
--- a/lib/extensions/print_pdf.py
+++ b/lib/extensions/print_pdf.py
@@ -4,7 +4,6 @@
# Licensed under the GNU GPL version 3.0 or later. See the file LICENSE for details.
import json
-import logging
import os
import socket
import sys
@@ -249,9 +248,6 @@ class PrintPreviewServer(Thread):
debug.log(f"exception in watcher {sys.exc_info()}")
pass
- def disable_logging(self):
- logging.getLogger('werkzeug').setLevel(logging.ERROR)
-
# https://github.com/aluo-x/Learning_Neural_Acoustic_Fields/blob/master/train.py
# https://github.com/pytorch/pytorch/issues/71029
def find_free_port(self):
@@ -260,8 +256,6 @@ class PrintPreviewServer(Thread):
return s.getsockname()[1]
def run(self):
- self.disable_logging()
-
self.host = "127.0.0.1"
self.port = self.find_free_port()
# exporting the port number for languages to work