From 2768a715444f78e9ad16481731adaf6cc4bbd604 Mon Sep 17 00:00:00 2001 From: Oscar Levin Date: Sun, 7 Jun 2026 15:03:54 -0600 Subject: [PATCH 1/2] implement tmp directory cleanup and better debug flag support --- pretext/__init__.py | 2 +- pretext/cli.py | 51 ++++++++++++++++++++++++++++++++++--- pretext/project/__init__.py | 22 ++++++++-------- 3 files changed, 60 insertions(+), 15 deletions(-) diff --git a/pretext/__init__.py b/pretext/__init__.py index 373779ad..f1b43c6a 100644 --- a/pretext/__init__.py +++ b/pretext/__init__.py @@ -19,7 +19,7 @@ VERSION = get_version("pretext", Path(__file__).parent.parent) -CORE_COMMIT = "fc8221d5b8e7027f686729102e169c482fbd1fe5" +CORE_COMMIT = "7bef656c9f303489fc07d719df52108c1970ca26" def activate() -> None: diff --git a/pretext/cli.py b/pretext/cli.py index d182c00b..f793b3f9 100644 --- a/pretext/cli.py +++ b/pretext/cli.py @@ -80,9 +80,43 @@ def try_except(ctx: click.Context, *args: Any, **kwargs: Any) -> Any: CONTEXT_SETTINGS = dict(help_option_names=["-h", "--help"]) +_VERBOSITY_HELP = "Sets the severity of log messaging: DEBUG for all, INFO (default) for most, then WARNING, ERROR, and CRITICAL for decreasing verbosity." + + +def set_log_level(_ctx: click.Context, _param: click.Parameter, value: Optional[str]) -> None: + _ = (_ctx, _param) # Click's callback protocol requires these; not used here. + if value: + log.setLevel(value.upper()) + + +class PreTeXtGroup(click.Group): + """Click Group that injects a -v/--verbosity option into every subcommand so that + `pretext build -v debug` works in addition to the canonical `pretext -v debug build`.""" + + def command(self, *args: Any, **kwargs: Any) -> Any: + original_decorator = super().command(*args, **kwargs) + + def decorator(f: Callable[..., Any]) -> Any: + f = click.option( + "--verbosity", + "-v", + default=None, + expose_value=False, + is_eager=True, + callback=set_log_level, + type=click.Choice( + ["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"], + case_sensitive=False, + ), + help=_VERBOSITY_HELP, + )(f) + return original_decorator(f) + + return decorator + # Click command-line interface -@click.group(invoke_without_command=True, context_settings=CONTEXT_SETTINGS) +@click.group(cls=PreTeXtGroup, invoke_without_command=True, context_settings=CONTEXT_SETTINGS) @click.pass_context # Allow a verbosity command: @click_log.simple_verbosity_option( @@ -96,8 +130,13 @@ def try_except(ctx: click.Context, *args: Any, **kwargs: Any) -> Any: is_flag=True, help='Display list of build/view "targets" available in the project manifest.', ) +@click.option( + "--save-tmp-dirs", + is_flag=True, + help="Do not clean temporary directories after a build or generate. Might be useful for debugging (previously calling -v debug would also save these directories).", +) @nice_errors -def main(ctx: click.Context, targets: bool) -> None: +def main(ctx: click.Context, targets: bool, save_tmp_dirs: bool) -> None: """ Command line tools for quickly creating, authoring, and building PreTeXt projects. @@ -177,7 +216,7 @@ def main(ctx: click.Context, targets: bool) -> None: f"No project.ptx manifest found in current workspace. Using global configuration specified in '~/.ptx/{VERSION}/project.ptx'." ) # Add project to context so it can be used in subcommands - ctx.obj = {"project": project} + ctx.obj = {"project": project, "save_tmp_dirs": save_tmp_dirs} @main.result_callback() @@ -552,6 +591,7 @@ def build( # Create a new project, apply overlay, and get target. Note, the CLI always finds changes to the root folder of the project, so we don't need to specify a path to the project.ptx file. # Use the project discovered in the main command. project = ctx.obj["project"] + save_tmp_dirs = ctx.obj.get("save_tmp_dirs", False) # Check to see whether target_name is a path to a file: if target_name and Path(target_name).is_file(): @@ -620,7 +660,7 @@ def build( for t in targets: log.info(f"Generating assets for {t.name}") t.generate_assets( - only_changed=False, xmlid=xmlid, clean=clean, skip_cache=True + only_changed=False, xmlid=xmlid, clean=clean, skip_cache=True, clean_tmp_dirs=not save_tmp_dirs ) no_generate = True except Exception as e: @@ -645,6 +685,7 @@ def build( xmlid=xmlid, no_knowls=no_knowls, latex=latex, + clean_tmp_dirs=not save_tmp_dirs, ) if t.format == "html" and t.compression is None: log.info( @@ -761,6 +802,7 @@ def generate( return project = ctx.obj["project"] + save_tmp_dirs = ctx.obj.get("save_tmp_dirs", False) # Now create the target if the target_name is not missing. try: target = project.get_target(name=target_name) @@ -780,6 +822,7 @@ def generate( clean=clean, skip_cache=force, slow=slow, + clean_tmp_dirs=not save_tmp_dirs, ) # Check if there are errors reported by the build by looking at the error_flush_handler. if utils.has_errors(error_flush_handler): diff --git a/pretext/project/__init__.py b/pretext/project/__init__.py index d3d96182..19554dba 100644 --- a/pretext/project/__init__.py +++ b/pretext/project/__init__.py @@ -582,7 +582,7 @@ def save_asset_table(self, asset_table: pt.AssetTable) -> None: ) as f: json.dump(asset_table, f) - def ensure_myopenmath_xml(self) -> None: + def ensure_myopenmath_xml(self, clean_tmp_dirs: bool = True) -> None: """ Ensures that the myopenmath xml files are present if the source contains myopenmath exercises. Needed to generate other "static" assets and targets. """ @@ -607,14 +607,14 @@ def ensure_myopenmath_xml(self) -> None: f"MyOpenMath problem {prob_num} does not exist, generating" ) self.generate_assets( - requested_asset_types=["myopenmath"], only_changed=False + requested_asset_types=["myopenmath"], only_changed=False, clean_tmp_dirs=clean_tmp_dirs ) # Only need to generate once a single missing file is discovered. break else: log.debug("Source does not contain myopenmath problems") - def ensure_webwork_reps(self) -> None: + def ensure_webwork_reps(self, clean_tmp_dirs: bool = True) -> None: """ Ensures that the webwork representation file is present if the source contains webwork problems. This is needed to build or generate other assets. """ @@ -634,7 +634,7 @@ def ensure_webwork_reps(self) -> None: f'At least one WeBWorK representation file (for webwork problem with id "{id}") does not exist, generating' ) self.generate_assets( - requested_asset_types=["webwork"], only_changed=False + requested_asset_types=["webwork"], only_changed=False, clean_tmp_dirs=clean_tmp_dirs ) break else: @@ -720,6 +720,7 @@ def build( xmlid: t.Optional[str] = None, no_knowls: bool = False, latex: bool = False, + clean_tmp_dirs: bool = True, ) -> None: # Add cli.version to stringparams. Use only the major and minor version numbers. self.stringparams["cli.version"] = VERSION[: VERSION.rfind(".")] @@ -744,11 +745,11 @@ def build( self.ensure_asset_directories() # verify that a webwork_representations.xml file exists if it is needed; generated if needed. - self.ensure_webwork_reps() + self.ensure_webwork_reps(clean_tmp_dirs=clean_tmp_dirs) # Generate needed assets unless requested not to. if generate: - self.generate_assets(xmlid=xmlid) + self.generate_assets(xmlid=xmlid, clean_tmp_dirs=clean_tmp_dirs) # Ensure the output directories exist. self.ensure_output_directory() @@ -936,7 +937,7 @@ def build( log.critical(f"Unknown format {self.format}") # Delete temporary directories left behind by core: try: - core.release_temporary_directories() + core.release_temporary_directories(any_log_level=clean_tmp_dirs) except Exception as e: log.error( "Unable to release temporary directories. Please report this error to pretext-support" @@ -952,6 +953,7 @@ def generate_assets( clean: bool = False, skip_cache: bool = False, slow: bool = False, + clean_tmp_dirs: bool = True, ) -> None: """ Generates assets for the current target. Options: @@ -1320,10 +1322,10 @@ def generate_assets( # log.debug(e, exc_info=True) # Delete temporary directories left behind by core: try: - core.release_temporary_directories() + core.release_temporary_directories(any_log_level=clean_tmp_dirs) except Exception as e: - log.error( - "Unable to release temporary directories. Please report this error to pretext-support" + log.debug( + "Unable to release temporary directories." ) log.debug(e, exc_info=True) # After all assets are generated, update the asset cache (but we shouldn't do this if we didn't generate any assets successfully) From e8b25cb432c1ba558831646b447ee7cbec71b5f3 Mon Sep 17 00:00:00 2001 From: Oscar Levin Date: Sun, 7 Jun 2026 18:04:51 -0600 Subject: [PATCH 2/2] format and changelog --- CHANGELOG.md | 5 +++++ pretext/cli.py | 17 +++++++++++++---- pretext/project/__init__.py | 12 +++++++----- 3 files changed, 25 insertions(+), 9 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 87af0eab..9aedca1f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,11 @@ Instructions: Add a subsection under `[Unreleased]` for additions, fixes, change ## [Unreleased] +# Added + +- You can now specify the debug level *after* the command (e.g., `pretext -v debug build` can now also be entered as `pretext build -v debug`). +- Temporary directories created by core pretext will now be cleaned up at all debug levels. To save them for debugging, use the `--save-tmp-dirs` flag, as in `pretext --save-tmp-dirs build`. + ## [2.41.2] - 2026-06-07 Includes updates to core through commit: [fc8221d](https://github.com/PreTeXtBook/pretext/commit/fc8221d5b8e7027f686729102e169c482fbd1fe5) diff --git a/pretext/cli.py b/pretext/cli.py index f793b3f9..b35e10a1 100644 --- a/pretext/cli.py +++ b/pretext/cli.py @@ -83,7 +83,9 @@ def try_except(ctx: click.Context, *args: Any, **kwargs: Any) -> Any: _VERBOSITY_HELP = "Sets the severity of log messaging: DEBUG for all, INFO (default) for most, then WARNING, ERROR, and CRITICAL for decreasing verbosity." -def set_log_level(_ctx: click.Context, _param: click.Parameter, value: Optional[str]) -> None: +def set_log_level( + _ctx: click.Context, _param: click.Parameter, value: Optional[str] +) -> None: _ = (_ctx, _param) # Click's callback protocol requires these; not used here. if value: log.setLevel(value.upper()) @@ -91,7 +93,8 @@ def set_log_level(_ctx: click.Context, _param: click.Parameter, value: Optional[ class PreTeXtGroup(click.Group): """Click Group that injects a -v/--verbosity option into every subcommand so that - `pretext build -v debug` works in addition to the canonical `pretext -v debug build`.""" + `pretext build -v debug` works in addition to the canonical `pretext -v debug build`. + """ def command(self, *args: Any, **kwargs: Any) -> Any: original_decorator = super().command(*args, **kwargs) @@ -116,7 +119,9 @@ def decorator(f: Callable[..., Any]) -> Any: # Click command-line interface -@click.group(cls=PreTeXtGroup, invoke_without_command=True, context_settings=CONTEXT_SETTINGS) +@click.group( + cls=PreTeXtGroup, invoke_without_command=True, context_settings=CONTEXT_SETTINGS +) @click.pass_context # Allow a verbosity command: @click_log.simple_verbosity_option( @@ -660,7 +665,11 @@ def build( for t in targets: log.info(f"Generating assets for {t.name}") t.generate_assets( - only_changed=False, xmlid=xmlid, clean=clean, skip_cache=True, clean_tmp_dirs=not save_tmp_dirs + only_changed=False, + xmlid=xmlid, + clean=clean, + skip_cache=True, + clean_tmp_dirs=not save_tmp_dirs, ) no_generate = True except Exception as e: diff --git a/pretext/project/__init__.py b/pretext/project/__init__.py index 19554dba..776788e5 100644 --- a/pretext/project/__init__.py +++ b/pretext/project/__init__.py @@ -607,7 +607,9 @@ def ensure_myopenmath_xml(self, clean_tmp_dirs: bool = True) -> None: f"MyOpenMath problem {prob_num} does not exist, generating" ) self.generate_assets( - requested_asset_types=["myopenmath"], only_changed=False, clean_tmp_dirs=clean_tmp_dirs + requested_asset_types=["myopenmath"], + only_changed=False, + clean_tmp_dirs=clean_tmp_dirs, ) # Only need to generate once a single missing file is discovered. break @@ -634,7 +636,9 @@ def ensure_webwork_reps(self, clean_tmp_dirs: bool = True) -> None: f'At least one WeBWorK representation file (for webwork problem with id "{id}") does not exist, generating' ) self.generate_assets( - requested_asset_types=["webwork"], only_changed=False, clean_tmp_dirs=clean_tmp_dirs + requested_asset_types=["webwork"], + only_changed=False, + clean_tmp_dirs=clean_tmp_dirs, ) break else: @@ -1324,9 +1328,7 @@ def generate_assets( try: core.release_temporary_directories(any_log_level=clean_tmp_dirs) except Exception as e: - log.debug( - "Unable to release temporary directories." - ) + log.debug("Unable to release temporary directories.") log.debug(e, exc_info=True) # After all assets are generated, update the asset cache (but we shouldn't do this if we didn't generate any assets successfully) log.debug(f"Updated these assets successfully: {successful_assets}")