This document describes the Python functions that form the CLI and non-CLI functions. Non-CLI functions are those used by the CLI functions. We include function signatures, documentation, some pseudocode, and diagrams that represent the interface along with explanations for these design decisions. It primarily serves as a reference for us to develop from and track what has been finished and what remains to be done.
We use symbols to indicate the status of implementation for the different parts of the interface (see table below). For work that is planned or is in progress, we include in-depth descriptions of the planned implementation. This may include signatures, docstrings, and pseudocode to clarify the design. Once the interface is implemented (done), we will remove the signatures from the documentation and point to the reference documentation instead. The symbols we use are described in the table below.
A table showing the symbols used to indicate the status of interface components, along with their descriptions.
Status
Description
Interface that has been implemented.
Interface that is currently being worked on.
Interface that is planned, but isn’t being worked on currently.
Overview
The Python interface has two parts: the functions that represent the interface via the Terminal (CLI) and the functions that implement the behaviour of the CLI functions. We will call them “CLI” and “non-CLI” functions. We have this split for a few reasons:
To create a clear separation between code that is exposed through the CLI and code that could be used on its own as a Python package library.
To apply functional programming practices to the non-CLI functions, and restrict the use of side effects and other non-functional practices to CLI functions.
To write unit tests for the non-CLI functions and integration tests for the CLI functions.
To keep the CLI functions small by building them from functional units of non-CLI code.
Python CLI functions
These functions are the direct Python equivalents of the CLI commands described on the CLI design page: build() and view(). This page focuses on the Python design and decisions made to avoid too much overlap with the content of the CLI design page.
build()
As already described in the CLI design page, this function has four parameters:
uri for the URI argument. The default is the datapackage.json file found in the working directory. See the CLI design for reasons why we use a URI.
style for the style of output to use, which specifies the output file type and the number of files created. Either None or one of the built-in styles.
output_dir to specify where the root directory is for the output files. Defaults to the current working directory.
verbose to output more messages, rather than have no output.
The internal flow of this function is shown in the diagram below.
Figure 1: Diagram of the internal flow of functions and objects in the build() CLI function.
TipPseudocode: build()
src/seedcase_flower/cli.py
from typing import Optionalfrom seedcase_flower.config import Stylefrom pathlib import Pathfrom dataclasses import dataclass# To keep track of path and built content for each section.@dataclass(frozen=True)class BuiltSection: output_path: Path built_content: str# TODO: Will need to convert them to Typer CLI args.# See https://typer.tiangolo.com/tutorial/arguments/default/# and https://typer.tiangolo.com/tutorial/commands/help/#rich-markdown-and-markup# CLI functions don't need a return type.def build( uri: str="datapackage.json", style: Optional[Style] =None, output_dir: Path = Path("."), verbose: bool=False ):"""Build human-friendly documentation from a `datapackage.json` file. Args: uri: The URI to a datapackage.json file. Defaults to `datapackage.json` in the current working directory. style: The style of output to use. Either one of the built-in styles in `Style` or None. If None, it will look for a config file in the same directory as the `datapackage.json` file. If a config file is not found, it will use the default style (`Style.quarto_one_page`). The `Style.custom` option is only available to use in the config file (or directly via `Config`). output_dir: The directory to output the generated files to. Defaults to the current working directory. verbose: If True, outputs messages to the console. Returns: Outputs a message of the files created if verbose is True, otherwise outputs nothing. """# Potential implementation steps:# Output maybe str? Path?# Use `match` inside for strictness on URI types? path: str= resolve_uri(uri)# TODO: Use match inside of load_config to have strict checks?# If style is provided, use that, not the config file.# If style is None, look for config file in same dir as datapackage.json.# If style is None and no config path found, use default style. config: Config = load_config(style, path)# Able to read from URI, e.g. `https` or `file` or `gh` properties: dict= read_datapackage(path)# One item per section, rendered from template.# Internally uses Jinja2 to render templates with metadata output: list[BuiltSection] = build_sections( properties, config ) output_files: list[Path] = write_sections(output, output_dir)if verbose: cli_message(output_files) #?
view()
As mentioned in the CLI design page, this function has two parameters that are both the same as those in the build() function: uri and style. The only difference with the style parameter is that it can only accept built-in terminal styles and does not allow a custom style.
The internal flow of this function is shown in the diagram below.
Figure 2: Diagram of the internal flow of functions and objects in the view() CLI function.
TipPseudocode: view()
src/seedcase_flower/cli.py
from typing import Optionalfrom seedcase_flower.config import Stylefrom seedcase_sprout import read_properties, PackagePropertiesfrom pathlib import Pathfrom enum import Enum# Allows for strict checking of built-in styles, as this is a sum type.# The specific terminal style needs to also exist in the Style enum.class TerminalStyle(Enum):"""Built-in styles for outputting to the terminal."""# Can add more styles later here if needed. terminal_default ="terminal-default"def view(uri: str="datapackage.json", style: TerminalStyle = TerminalStyle.terminal_default):"""Display the `datapackage.json` properties in a human-friendly way in the terminal. Args: uri: The URI to a datapackage.json file. Defaults to `datapackage.json` in the current working directory. style: The style of output to use. Must be one of built-in terminal styles in `TerminalStyle` or None. """# Potential implementation steps:# Output maybe str? Path?# Use `match` inside for strictness on URI types? path: str= resolve_uri(uri)# Match works well when paired with enums for strictness and checking.match style:# Might be a more concise way of doing this when there are more styles.case _ if style in TerminalStyle: config: Config = Config(style=TerminalStyle(style))# This matches all other cases not explicitly handled above.case _:# Or however we implement error handling here. error("Style not supported for terminal output. Should be one of ...")# Able to read from URI, e.g. `https` or `file` or `gh` properties: PackageProperties = read_properties(path)# One item per section, rendered from template.# Internally uses Jinja2 to render templates with metadata. output: list[BuiltSection] = build_sections( properties, config )# Pretty printing here.returnprint(output)