Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,37 @@ with open(output_file_path, "w", encoding="UTF-8") as output_file:
The contents of `reqif_xml_output` should be the same as the contents of the
`input_file`.

### Logging

The `reqif` library logs its diagnostic messages (for example, warnings about
unknown tags) through Python's standard
[`logging`](https://docs.python.org/3/howto/logging.html) module, using the
`"reqif"` logger hierarchy. Following the
[standard convention for libraries](https://docs.python.org/3/howto/logging.html#configuring-logging-for-a-library),
the library does not configure any handlers itself, so by default an
application that imports `reqif` sees no log output.

To see the library's warnings on the console, attach a handler to the
`"reqif"` logger:

```py
import logging

logging.getLogger("reqif").addHandler(logging.StreamHandler())
```

Any other `logging` configuration works as well, e.g. `logging.basicConfig()`
or a handler that writes the records to a file. To also see the messages that
accompany recoverable schema errors (the errors themselves are collected in
`reqif_bundle.exceptions`), set the logger's level to `DEBUG`:

```py
logging.getLogger("reqif").setLevel(logging.DEBUG)
```

The `reqif` command-line tool attaches its own handler, so its output does
not depend on this configuration.

## Using ReqIF as a command-line tool

After installing the `reqif` Pip package, the `reqif` command becomes available
Expand Down
8 changes: 8 additions & 0 deletions reqif/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
import logging
import os.path

__version__ = "0.0.50"

# Follow the standard library convention for logging in libraries: attach a
# NullHandler to the package root logger so that reqif emits no output unless
# the embedding application configures handlers for the "reqif" logger
# hierarchy.
Comment thread
stanislaw marked this conversation as resolved.
# https://docs.python.org/3/howto/logging.html#configuring-logging-for-a-library
logging.getLogger("reqif").addHandler(logging.NullHandler())

PATH_TO_REQIF_ROOT = os.path.abspath(os.path.join(os.path.dirname(__file__), os.pardir))
24 changes: 24 additions & 0 deletions reqif/cli/main.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import logging
import os
import sys

Expand All @@ -20,6 +21,17 @@
sys.exit(1)


class _LowercaseLevelFormatter(logging.Formatter):
"""
Renders log records the way the CLI used to print() them, e.g.
"warning: Unknown child tag: FOO." with a lowercase level prefix.
"""

def format(self, record: logging.LogRecord) -> str:
record.levelname = record.levelname.lower()
return super().format(record)


def main() -> None:
# How to make python 3 print() utf8
# https://stackoverflow.com/a/3597849/598057
Expand All @@ -28,6 +40,18 @@ def main() -> None:
1, "w", encoding="utf-8", closefd=False
)

# The reqif library logs through the "reqif" logger hierarchy and emits
# nothing by default. The CLI shows the library's warnings on stdout in
# the same format the CLI printed them before the library switched to
# logging. The handler is constructed after the sys.stdout reassignment
# above so that it writes to the UTF-8-configured stream.
reqif_log_handler = logging.StreamHandler(stream=sys.stdout)
reqif_log_handler.setFormatter(
_LowercaseLevelFormatter("%(levelname)s: %(message)s")
)
logging.getLogger("reqif").addHandler(reqif_log_handler)
logging.getLogger("reqif").setLevel(logging.WARNING)

parser = create_reqif_args_parser()

if parser.is_passthrough_command:
Expand Down
10 changes: 10 additions & 0 deletions reqif/parser.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
# ruff: noqa: A005
import copy
import io
import logging
import os
import zipfile
from collections import OrderedDict, defaultdict
Expand Down Expand Up @@ -63,6 +64,8 @@
)
from reqif.reqif_bundle import ReqIFBundle, ReqIFZBundle

logger = logging.getLogger(__name__)


class ReqIFParser:
@staticmethod
Expand Down Expand Up @@ -315,6 +318,13 @@ def _parse_reqif_content(
spec_relation.target
)
except ReqIFMissingTagException as exception:
# The canonical reporting channel for recoverable schema
# errors is the returned bundle's "exceptions" list (the
# validate command prints them from there). Log at DEBUG
# only, so that embedding applications can observe the
# errors as they occur without the CLI reporting each of
# them twice.
logger.debug("%s", exception.get_description())
exceptions.append(exception)

# <SPEC-OBJECTS>
Expand Down
5 changes: 4 additions & 1 deletion reqif/parsers/spec_object_parser.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import logging
from typing import List, Optional

from reqif.models.reqif_spec_object import (
Expand All @@ -6,6 +7,8 @@
)
from reqif.parsers.attribute_value_parser import AttributeValueParser

logger = logging.getLogger(__name__)


class SpecObjectParser:
@staticmethod
Expand Down Expand Up @@ -77,7 +80,7 @@ def unparse(spec_object: ReqIFSpecObject) -> str:
elif child_tag == "TYPE":
output += SpecObjectParser._unparse_spec_object_type(spec_object)
else:
print(f"warning: Unknown child tag: {child_tag}.") # noqa: T201
logger.warning("Unknown child tag: %s.", child_tag)

output += " </SPEC-OBJECT>\n"

Expand Down
Loading