@@ -1,5 +1,6 @@
Loading
1 1
#!/usr/bin/env python
2 2
# import base64
3 +
from contextlib import contextmanager
3 4
from typing import List, Tuple, Union, Dict  # noqa: F401
4 5
import logging
5 6
import os
@@ -280,40 +281,52 @@
Loading
280 281
    def logger(self):
281 282
        return logging.getLogger("ipypublish")
282 283
283 -
    def _setup_logger(self, ipynb_name, outdir):
284 +
    @contextmanager
285 +
    def _log_handlers(self, ipynb_name, outdir):
284 286
285 287
        root = logging.getLogger()
288 +
        root_level = root.level
289 +
        log_handlers = []
286 290
287 -
        if self.log_to_stdout or self.log_to_file:
288 -
            # remove any existing handlers
289 -
            root.handlers = []
291 +
        try:
290 292
            root.setLevel(logging.DEBUG)
291 293
292 -
        if self.log_to_stdout:
293 -
            # setup logging to terminal
294 -
            slogger = logging.StreamHandler(sys.stdout)
295 -
            slogger.setLevel(getattr(logging, self.log_level_stdout.upper()))
296 -
            formatter = logging.Formatter(self.log_stdout_formatstr)
297 -
            slogger.setFormatter(formatter)
298 -
            slogger.propogate = False
299 -
            root.addHandler(slogger)
300 -
301 -
        if self.log_to_file:
302 -
            # setup logging to file
303 -
            if self.log_file_path:
304 -
                path = self.log_file_path
305 -
            else:
306 -
                path = os.path.join(outdir, ipynb_name + ".nbpub.log")
307 -
308 -
            if not os.path.exists(os.path.dirname(path)):
309 -
                os.makedirs(os.path.dirname(path))
310 -
311 -
            flogger = logging.FileHandler(path, "w")
312 -
            flogger.setLevel(getattr(logging, self.log_level_file.upper()))
313 -
            formatter = logging.Formatter(self.log_file_formatstr)
314 -
            flogger.setFormatter(formatter)
315 -
            flogger.propogate = False
316 -
            root.addHandler(flogger)
294 +
            if self.log_to_stdout:
295 +
                # setup logging to terminal
296 +
                slogger = logging.StreamHandler(sys.stdout)
297 +
                slogger.setLevel(getattr(logging, self.log_level_stdout.upper()))
298 +
                formatter = logging.Formatter(self.log_stdout_formatstr)
299 +
                slogger.setFormatter(formatter)
300 +
                slogger.propogate = False
301 +
                root.addHandler(slogger)
302 +
                log_handlers.append(slogger)
303 +
304 +
            if self.log_to_file:
305 +
                # setup logging to file
306 +
                if self.log_file_path:
307 +
                    path = self.log_file_path
308 +
                else:
309 +
                    path = os.path.join(outdir, ipynb_name + ".nbpub.log")
310 +
311 +
                if not os.path.exists(os.path.dirname(path)):
312 +
                    os.makedirs(os.path.dirname(path))
313 +
314 +
                flogger = logging.FileHandler(path, "w")
315 +
                flogger.setLevel(getattr(logging, self.log_level_file.upper()))
316 +
                formatter = logging.Formatter(self.log_file_formatstr)
317 +
                flogger.setFormatter(formatter)
318 +
                flogger.propogate = False
319 +
                root.addHandler(flogger)
320 +
                log_handlers.append(flogger)
321 +
322 +
            yield
323 +
324 +
        finally:
325 +
326 +
            root.setLevel(root_level)
327 +
            for handler in log_handlers:
328 +
                handler.close()
329 +
                root.removeHandler(handler)
317 330
318 331
    def __init__(self, config=None):
319 332
        """
@@ -372,119 +385,124 @@
Loading
372 385
            else str(self.outpath)
373 386
        )
374 387
375 -
        self._setup_logger(ipynb_name, outdir)
376 -
377 -
        if not ipynb_path.exists() and not nb_node:
378 -
            handle_error(
379 -
                "the notebook path does not exist: {}".format(ipynb_path),
380 -
                IOError,
381 -
                self.logger,
382 -
            )
383 -
384 -
        # log start of conversion
385 -
        self.logger.info(
386 -
            "started ipypublish v{0} at {1}".format(
387 -
                ipypublish.__version__, time.strftime("%c")
388 -
            )
389 -
        )
390 -
        self.logger.info(
391 -
            "logging to: {}".format(os.path.join(outdir, ipynb_name + ".nbpub.log"))
392 -
        )
393 -
        self.logger.info("running for ipynb(s) at: {0}".format(ipynb_path))
394 -
        self.logger.info("with conversion configuration: {0}".format(self.conversion))
388 +
        with self._log_handlers(ipynb_name, outdir):
395 389
396 -
        if nb_node is None and ipynb_ext in self.pre_conversion_funcs:
397 -
            func = self.pre_conversion_funcs[ipynb_ext]
398 -
            self.logger.info(
399 -
                "running pre-conversion with: {}".format(inspect.getmodule(func))
400 -
            )
401 -
            try:
402 -
                nb_node = func(ipynb_path)
403 -
            except Exception as err:
390 +
            if not ipynb_path.exists() and not nb_node:
404 391
                handle_error(
405 -
                    "pre-conversion failed for {}: {}".format(ipynb_path, err),
406 -
                    err,
392 +
                    "the notebook path does not exist: {}".format(ipynb_path),
393 +
                    IOError,
407 394
                    self.logger,
408 395
                )
409 396
410 -
        # doesn't work with folders
411 -
        # if (ipynb_ext != ".ipynb" and nb_node is None):
412 -
        #     handle_error(
413 -
        #         'the file extension is not associated with any '
414 -
        #         'pre-converter: {}'.format(ipynb_ext),
415 -
        # TypeError, self.logger)
416 -
417 -
        if nb_node is None:
418 -
            # merge all notebooks
419 -
            # TODO allow notebooks to remain separate
420 -
            # (would require creating a main.tex with the preamble in etc )
421 -
            # Could make everything a 'PyProcess',
422 -
            # with support for multiple streams
423 -
            final_nb, meta_path = merge_notebooks(
424 -
                ipynb_path, ignore_prefix=self.ignore_prefix
397 +
            # log start of conversion
398 +
            self.logger.info(
399 +
                "started ipypublish v{0} at {1}".format(
400 +
                    ipypublish.__version__, time.strftime("%c")
401 +
                )
425 402
            )
426 -
        else:
427 -
            final_nb, meta_path = (nb_node, ipynb_path)
428 -
429 -
        # validate the notebook metadata against the schema
430 -
        if self.validate_nb_metadata:
431 -
            nb_metadata_schema = read_file_from_directory(
432 -
                get_module_path(schema),
433 -
                "doc_metadata.schema.json",
434 -
                "doc_metadata.schema",
435 -
                self.logger,
436 -
                interp_ext=True,
403 +
            self.logger.info(
404 +
                "logging to: {}".format(os.path.join(outdir, ipynb_name + ".nbpub.log"))
405 +
            )
406 +
            self.logger.info("running for ipynb(s) at: {0}".format(ipynb_path))
407 +
            self.logger.info(
408 +
                "with conversion configuration: {0}".format(self.conversion)
437 409
            )
438 -
            try:
439 -
                jsonschema.validate(final_nb.metadata, nb_metadata_schema)
440 -
            except jsonschema.ValidationError as err:
441 -
                handle_error(
442 -
                    "validation of notebook level metadata failed: {}\n"
443 -
                    "see the doc_metadata.schema.json for full spec".format(
444 -
                        err.message
445 -
                    ),
446 -
                    jsonschema.ValidationError,
447 -
                    logger=self.logger,
448 -
                )
449 410
450 -
        # set text replacements for export configuration
451 -
        replacements = {
452 -
            self.meta_path_placeholder: str(meta_path),
453 -
            self.files_folder_placeholder: "{}{}".format(
454 -
                get_valid_filename(ipynb_name), self.folder_suffix
455 -
            ),
456 -
        }
411 +
            if nb_node is None and ipynb_ext in self.pre_conversion_funcs:
412 +
                func = self.pre_conversion_funcs[ipynb_ext]
413 +
                self.logger.info(
414 +
                    "running pre-conversion with: {}".format(inspect.getmodule(func))
415 +
                )
416 +
                try:
417 +
                    nb_node = func(ipynb_path)
418 +
                except Exception as err:
419 +
                    handle_error(
420 +
                        "pre-conversion failed for {}: {}".format(ipynb_path, err),
421 +
                        err,
422 +
                        self.logger,
423 +
                    )
424 +
425 +
            # doesn't work with folders
426 +
            # if (ipynb_ext != ".ipynb" and nb_node is None):
427 +
            #     handle_error(
428 +
            #         'the file extension is not associated with any '
429 +
            #         'pre-converter: {}'.format(ipynb_ext),
430 +
            # TypeError, self.logger)
431 +
432 +
            if nb_node is None:
433 +
                # merge all notebooks
434 +
                # TODO allow notebooks to remain separate
435 +
                # (would require creating a main.tex with the preamble in etc )
436 +
                # Could make everything a 'PyProcess',
437 +
                # with support for multiple streams
438 +
                final_nb, meta_path = merge_notebooks(
439 +
                    ipynb_path, ignore_prefix=self.ignore_prefix
440 +
                )
441 +
            else:
442 +
                final_nb, meta_path = (nb_node, ipynb_path)
443 +
444 +
            # validate the notebook metadata against the schema
445 +
            if self.validate_nb_metadata:
446 +
                nb_metadata_schema = read_file_from_directory(
447 +
                    get_module_path(schema),
448 +
                    "doc_metadata.schema.json",
449 +
                    "doc_metadata.schema",
450 +
                    self.logger,
451 +
                    interp_ext=True,
452 +
                )
453 +
                try:
454 +
                    jsonschema.validate(final_nb.metadata, nb_metadata_schema)
455 +
                except jsonschema.ValidationError as err:
456 +
                    handle_error(
457 +
                        "validation of notebook level metadata failed: {}\n"
458 +
                        "see the doc_metadata.schema.json for full spec".format(
459 +
                            err.message
460 +
                        ),
461 +
                        jsonschema.ValidationError,
462 +
                        logger=self.logger,
463 +
                    )
464 +
465 +
            # set text replacements for export configuration
466 +
            replacements = {
467 +
                self.meta_path_placeholder: str(meta_path),
468 +
                self.files_folder_placeholder: "{}{}".format(
469 +
                    get_valid_filename(ipynb_name), self.folder_suffix
470 +
                ),
471 +
            }
457 472
458 -
        self.logger.debug("notebooks meta path: {}".format(meta_path))
459 -
460 -
        # load configuration file
461 -
        (
462 -
            exporter_cls,
463 -
            jinja_template,
464 -
            econfig,
465 -
            pprocs,
466 -
            pconfig,
467 -
        ) = self._load_config_file(replacements)
468 -
469 -
        # run nbconvert
470 -
        self.logger.info("running nbconvert")
471 -
        exporter, stream, resources = self.export_notebook(
472 -
            final_nb, exporter_cls, econfig, jinja_template
473 -
        )
473 +
            self.logger.debug("notebooks meta path: {}".format(meta_path))
474 +
475 +
            # load configuration file
476 +
            (
477 +
                exporter_cls,
478 +
                jinja_template,
479 +
                econfig,
480 +
                pprocs,
481 +
                pconfig,
482 +
            ) = self._load_config_file(replacements)
483 +
484 +
            # run nbconvert
485 +
            self.logger.info("running nbconvert")
486 +
            exporter, stream, resources = self.export_notebook(
487 +
                final_nb, exporter_cls, econfig, jinja_template
488 +
            )
474 489
475 -
        # postprocess results
476 -
        main_filepath = os.path.join(outdir, ipynb_name + exporter.file_extension)
490 +
            # postprocess results
491 +
            main_filepath = os.path.join(outdir, ipynb_name + exporter.file_extension)
477 492
478 -
        for post_proc_name in pprocs:
479 -
            proc_class = find_entry_point(
480 -
                post_proc_name, "ipypublish.postprocessors", self.logger, "ipypublish"
481 -
            )
482 -
            proc = proc_class(pconfig)
483 -
            stream, main_filepath, resources = proc.postprocess(
484 -
                stream, exporter.output_mimetype, main_filepath, resources
485 -
            )
493 +
            for post_proc_name in pprocs:
494 +
                proc_class = find_entry_point(
495 +
                    post_proc_name,
496 +
                    "ipypublish.postprocessors",
497 +
                    self.logger,
498 +
                    "ipypublish",
499 +
                )
500 +
                proc = proc_class(pconfig)
501 +
                stream, main_filepath, resources = proc.postprocess(
502 +
                    stream, exporter.output_mimetype, main_filepath, resources
503 +
                )
486 504
487 -
        self.logger.info("process finished successfully")
505 +
            self.logger.info("process finished successfully")
488 506
489 507
        return {
490 508
            "outpath": outdir,

@@ -56,6 +56,10 @@
Loading
56 56
57 57
    Examples
58 58
    --------
59 +
    >>> import os, pytest
60 +
    >>> if os.name == 'nt':
61 +
    ...     pytest.skip()
62 +
59 63
    >>> source = '''## Cell with Linked Image
60 64
    ... ![test_image](subdir/logo_example.png)
61 65
    ... a [test_link](other_doc#a-link)'''

@@ -1,3 +1,4 @@
Loading
1 +
import os
1 2
import pytest
2 3
3 4
@@ -18,6 +19,7 @@
Loading
18 19
    ipynb_app.assert_converted_equals_expected("latex_ipypublish_main.pandoc.2-2")
19 20
20 21
22 +
@pytest.mark.skipif(os.name == "nt", reason="skipping")
21 23
@pytest.mark.ipynb("nb_markdown_cells")
22 24
def test_sphinx_rst(ipynb_app):
23 25
    ipynb_app.run({"conversion": "sphinx_ipypublish_main"})
@@ -28,6 +30,7 @@
Loading
28 30
    ipynb_app.assert_converted_equals_expected("sphinx_ipypublish_main.pandoc.2-6")
29 31
30 32
33 +
@pytest.mark.skipif(os.name == "nt", reason="skipping")
31 34
@pytest.mark.ipynb("nb_with_mkdown_images")  # out_to_temp=False
32 35
def test_sphinx_rst_with_mkdown_images(ipynb_app):
33 36
    """ test a notebook with multiple images """

@@ -163,7 +163,7 @@
Loading
163 163
            logger,
164 164
            interp_ext=False,
165 165
        )
166 -
        outline_name = os.path.join(
166 +
        outline_name = "{0}/{1}".format(
167 167
            template_dict["outline"]["directory"], template_dict["outline"]["file"]
168 168
        )
169 169
    else:
@@ -174,7 +174,7 @@
Loading
174 174
            logger,
175 175
            interp_ext=False,
176 176
        )
177 -
        outline_name = os.path.join(
177 +
        outline_name = "{0}/{1}".format(
178 178
            template_dict["outline"]["module"], template_dict["outline"]["file"]
179 179
        )
180 180

@@ -1,3 +1,3 @@
Loading
1 1
from ipypublish.scripts import nb_setup  # noqa: F401
2 2
3 -
__version__ = "0.10.10"
3 +
__version__ = "0.10.11"

@@ -1,8 +1,11 @@
Loading
1 1
import os
2 +
import pytest
3 +
2 4
from ipypublish.postprocessors.pdfexport import PDFExport
3 5
from ipypublish.postprocessors.reveal_serve import RevealServer
4 6
5 7
8 +
@pytest.mark.requires_latexmk
6 9
def test_pdf_export(temp_folder):
7 10
8 11
    tex_content = """

@@ -174,8 +174,20 @@
Loading
174 174
        latexdoc_tags = ["code", "error", "table", "equation", "figure", "text"]
175 175
        # break up titles
176 176
        cells_in_slide = 0
177 -
        header_levels = []
178 177
        final_cells = FinalCells(self.header_slide)
178 +
179 +
        header_levels = []
180 +
        try:
181 +
            base_numbering = nb.metadata.toc.base_numbering
182 +
            header_levels = list(map(lambda x: int(x), base_numbering.split(".")))
183 +
            header_levels[0] -= 1
184 +
            logging.debug("base_numbering = " + base_numbering)
185 +
            logging.debug("header_levels = " + str(header_levels))
186 +
        except ValueError:
187 +
            logging.warning("Invalid toc.base_numbering in notebook metadata")
188 +
        except AttributeError:
189 +
            logging.debug("No toc.base_numbering in notebook metadata; starting at 1")
190 +
179 191
        for i, cell in enumerate(nb.cells):
180 192
181 193
            # Make sure every cell has an ipub meta tag
Files Coverage
ipypublish 79.15%
Project Totals (104 files) 79.15%
ipypublish-pytests-py3.8
Build #176468849 -
pytests
ipypublish-pytests-py3.8
Build #176467823 -
pytests
ipypublish-pytests-py3.8
Build #176509655 -
pytests

No yaml found.

Create your codecov.yml to customize your Codecov experience

Sunburst
The inner-most circle is the entire project, moving away from the center are folders then, finally, a single file. The size and color of each slice is representing the number of statements and the coverage, respectively.
Icicle
The top section represents the entire project. Proceeding with folders and finally individual files. The size and color of each slice is representing the number of statements and the coverage, respectively.
Grid
Each block represents a single file in the project. The size and color of each block is represented by the number of statements and the coverage, respectively.
Loading