Source code for PartSeg.common_backend.base_argparser
import argparse
import getpass
import locale
import os
import platform
import sys
import zlib
from contextlib import suppress
from importlib.metadata import version as package_version
from typing import Optional, Sequence
import sentry_sdk
import sentry_sdk.serializer
import sentry_sdk.utils
from packaging.version import parse as parse_version
from PartSeg import __version__, state_store
from PartSeg.common_backend.except_hook import my_excepthook
from PartSegCore.utils import safe_repr
SENTRY_GE_1_29 = parse_version(package_version("sentry_sdk")) >= parse_version("1.29.0")
[docs]
def proper_suffix(val: str):
"""
check if val contains only alphanumeric characters
:raise argparse.ArgumentTypeError: on validation error
"""
if not val or val.isalnum():
return val
raise argparse.ArgumentTypeError(f"suffix '{val}' need to contains only alpha numeric characters")
[docs]
def proper_path(val: str):
"""
Check if `val` is proper path in current system
:raise argparse.ArgumentTypeError: on validation error
"""
if os.path.exists(val) and os.path.isdir(val):
return val
try:
os.makedirs(val)
except OSError as e:
raise argparse.ArgumentTypeError(f" Path {val} is not a valid path in this system") from e
return val
[docs]
class CustomParser(argparse.ArgumentParser):
"""
Argument parser with set of predefined flags:
#. ``--no_report`` - disable error reporting, still showing dialog with information.
Set :py:data:`.state_store.report_errors`.
#. ``--no_dialog`` - disable error reporting and showing dialog. Exceptions will be printed on stderr.
Set :py:data:`.state_store.show_error_dialog`.
#. ``--no_update`` - disable check for updates on application startup
Set :py:data:`.state_store.check_for_updates`.
#. ``--save_suffix`` - add special suffix to directory contains saved state. designed to allow separate projects.
Set :py:data:`.state_store.save_suffix`.
#. ``--save_directory`` - set custom save directory. default can be previewed in `Help > State directory`
from PartSeg main menu, Set :py:data:`.state_store.save_folder`.
#. ``--inner_plugins`` - designed to hide part plugin from default start.
Set :py:data:`.state_store.custom_plugin_load`.
#. ``--develop`` -- for developer purpose. Allow to reload part of Program without restarting. May be unstable.
Set :py:data:`.state_store.develop`. Base on this :py:class:`PartSeg.common_gui.advanced_tabs.AdvancedWindow`
constructor add developer tab.
"""
def __init__(self, *args, **kwargs):
if "epilog" not in kwargs and len(args) < 4:
kwargs["epilog"] = "To control server for sentry reporting use PARTSEG_SENTRY_URL environment variable"
super().__init__(*args, **kwargs)
self.add_argument("--no_report", action="store_false", help="disable error reporting")
self.add_argument("--always_report", action="store_true", help="always report errors without asking user")
self.add_argument("--no_dialog", action="store_false", help="disable error reporting and showing error dialog")
self.add_argument("--no_update", action="store_false", help="disable check for updates")
self.add_argument(
"--save_suffix",
"--ssuf",
type=proper_suffix,
default=[""],
help="suffix for configuration_directory",
nargs=1,
metavar="suffix",
)
self.add_argument(
"--save_directory",
"--sdir",
type=proper_path,
default=[state_store.save_folder],
help=f"path to custom configuration folder, if not set then {state_store.save_folder} will be used. "
"Could be customized using 'PARTSEG_SETTINGS_DIR' environment variable.",
nargs=1,
metavar="path",
)
self.add_argument("--inner_plugins", action="store_true", help=argparse.SUPPRESS)
self.add_argument("--develop", action="store_true", help=argparse.SUPPRESS)
self.add_argument("--version", action="version", version=f"%(prog)s {__version__}")
[docs]
def parse_args(self, args: Optional[Sequence[str]] = None, namespace: Optional[argparse.Namespace] = None):
"""
overload of :py:meth:`argparse.ArgumentParser.parse_args`. Set flags like described in class documentation.
"""
args = super().parse_args(args, namespace)
if args.always_report and not args.no_report:
self.error("Cannot set both --always_report and --no_report")
state_store.always_report = args.always_report
state_store.report_errors = args.no_report
state_store.show_error_dialog = args.no_dialog
state_store.custom_plugin_load = args.inner_plugins
state_store.check_for_updates = args.no_update
state_store.develop = args.develop
state_store.save_suffix = args.save_suffix[0]
state_store.save_folder = os.path.abspath(
args.save_directory[0] + (f"_{state_store.save_suffix}" if state_store.save_suffix else "")
)
if args.no_report and args.no_dialog and state_store.sentry_url:
_setup_sentry()
sys.excepthook = my_excepthook
with suppress(locale.Error):
# some bug in reset locale
# https://stackoverflow.com/questions/68962248/python-setlocale-with-empty-string-default-locale-gives-unsupported-locale-se
locale.setlocale(locale.LC_NUMERIC, "")
return args
def _setup_sentry(): # pragma: no cover
if not state_store.sentry_url:
state_store.report_errors = False
return
sentry_sdk.utils.MAX_STRING_LENGTH = 10**4
sentry_sdk.utils.DEFAULT_MAX_VALUE_LENGTH = 10**4
sentry_sdk.serializer.safe_repr = safe_repr
sentry_sdk.serializer.MAX_DATABAG_BREADTH = 100
init_kwargs = {"release": f"PartSeg@{__version__}"}
if SENTRY_GE_1_29:
init_kwargs["max_value_length"] = 10**4
sentry_sdk.init(
state_store.sentry_url,
**init_kwargs,
)
with sentry_sdk.configure_scope() as scope:
scope.set_user(
{
"name": getpass.getuser(),
"id": zlib.adler32(f"{getpass.getuser()}#{platform.node()}".encode()),
}
)