Skip to main content

A more configurable Python code formatter

Project description

Cercis

Cercis /ˈsɜːrsɪs/ is a Python code formatter that is more configurable than Black (a popular Python code formatter).

Cercis is also the name of a deciduous tree that boasts vibrant pink to purple-hued flowers, which bloom in early spring.

This code repository is forked from and directly inspired by Black. The original license of Black is included in this repository (see LICENSE_ORIGINAL).

1. Motivations

While we like the idea of auto-formatting and code readability, we take issue with some style choices and the lack of configurability of the Black formatter. Therefore, Cercis aims at providing some configurability beyond Black's limited offering.

2. Installation and usage

2.1. Installation

Cercis can be installed by running pip install cercis. It requires Python 3.7+ to run. If you want to format Jupyter Notebooks, install with pip install "cercis[jupyter]".

2.2. Usage

2.2.1. Command line usage

To get started right away with sensible defaults:

cercis {source_file_or_directory}

You can run Cercis as a package if running it as a script doesn't work:

python -m cercis {source_file_or_directory}

The commands above reformat entire file(s) in place.

2.2.2. As pre-commit hook

To format Python files (.py), put the following into your .pre-commit-config.yaml file. Remember to replace <VERSION> with your version of this tool (such as v0.1.0):

- repo: https://github.com/jsh9/cercis
  rev: <VERSION>
  hooks:
    - id: cercis
      args: [--line-length=88]

To format Jupyter notebooks (.ipynb), put the following into your .pre-commit-config.yaml file:

- repo: https://github.com/jsh9/cercis
  rev: <VERSION>
  hooks:
    - id: cercis-jupyter
      args: [--line-length=88]

See pre-commit for more instructions. In particular, here is how to specify arguments in pre-commit config.

3. Cercis's code style

Cercis's code style is largely consistent with the style of of Black.

The main difference is that Cercis provides several configurable options that Black doesn't. Configurability is our main motivation behind creating Cercis.

Cercis offers the following configurable options:

  1. Line length
  2. Single quote vs double quote
  3. Extra indentation at function definition
  4. "Simple" lines with long strings
  5. Collapse nested brackets
  6. Wrap pragma comments

The next section (How to configure Cercis) contains detailed instructions of how to configure these options.

3.1. Line length

Cercis uses 79 characters as the line length limit, instead of 88 (Black's default).

You can override this default if necessary.

Option
Name --line-length
Abbreviation -l
Default 79
Black's default 88
Command line usage cercis -l=120 myScript.py
pyproject.toml usage line-length = 120 under [tool.cercis]
pre-commit usage args: [--line-length=120]

3.2. Single quote vs double quote

Cercis uses single quotes (') as the default for strings, instead of double quotes (") which is Black's default.

You can override this default if necessary.

Option
Name --single-quote
Abbreviation -sq
Default True
Black's default False
Command line usage cercis -sq=True myScript.py
pyproject.toml usage single-quote = false under [tool.cercis]
pre-commit usage args: [--single-quote=False]

3.3. Extra indentation at function definition

# Cercis's default style
def some_function(
        arg1_with_long_name: str,
        arg2_with_longer_name: int,
        arg3_with_longer_name: float,
        arg4_with_longer_name: bool,
) -> None:
    ...
# Black's style (not configurable)
def some_function(
    arg1_with_long_name: str,
    arg2_with_longer_name: int,
    arg3_with_longer_name: float,
    arg4_with_longer_name: bool,
) -> None:
    ...

We choose to indent an extra 4 spaces (8 in total) because it adds a clear visual separation between the function name and the argument list. Not adding extra indentation is also called out as wrong in the the official PEP8 style guide.

You can override this default if necessary.

Option
Name --function-definition-extra-indent
Abbreviation -fdei
Default True
Black's default False
Command line usage cercis -fdei=False myScript.py
pyproject.toml usage function-definition-extra-indent = true under [tool.cercis]
pre-commit usage args: [--function-definition-extra-indent=False]

3.4. "Simple" lines with long strings

By default, Black wraps lines that exceed length limit. But for very simple lines (such as assigning a long string to a variable), line wrapping is not necessary.

# Cercis's default style
# (Suppose line length limit is 30 chars)

# Cercis doesn't wrap slightly long lines
var1 = 'This line has 31 chars'



# Cercis doesn't wrap longer lines
var2 = 'This line has 43 characters_______'


# Falls back to Black when comments present
var3 = (
    'shorter line'  # comment
)
# Black's style (not configurable)
# (Suppose line length limit is 30 chars)

# Black wraps slightly long lines
var1 = (
    "This line has 31 chars"
)

# But Black doesn't wrap longer lines
var2 = "This line has 43 characters_______"


# Black wraps comments like this:
var3 = (
    "shorter line"  # comment
)
Option
Name --wrap-line-with-long-string
Abbreviation -wl
Default False
Black's default True
Command line usage cercis -wl=True myScript.py
pyproject.toml usage wrap-line-with-long-string = true under [tool.cercis]
pre-commit usage args: [--wrap-line-with-long-string=False]

3.5. Collapse nested brackets

Cercis by default collapses nested brackets to make the code more compact.

# Cercis's default style

# If line length limit is 30
value = np.array([
    [1, 2, 3, 4, 5],
    [6, 7, 8, 9, 0],
])



# If line length limit is 10
value = function({
    1,
    2,
    3,
    4,
    5,
})
# Black's style (not configurable)

# If line length limit is 30
value = np.array(
    [
        [1, 2, 3, 4, 5],
        [6, 7, 8, 9, 0],
    ]
)

# If line length limit is 10
value = function(
    {
        1,
        2,
        3,
        4,
        5,
    }
)
Option
Name --collapse-nested-brackets
Abbreviation -cnb
Default True
Black's style False
Command line usage cercis -cnb=True myScript.py
pyproject.toml usage collapse-nested-brackets = true under [tool.cercis]
pre-commit usage args: [--collapse-nested-brackets=False]

The code implementation of this option comes from Pyink, another forked project from Black.

3.6. Wrapping long lines ending with pragma comments ^

"Pragma comments", in this context, mean the directives for Python linters usually to tell them to ignore certain errors. Pragma comments that Cercis currently recognizes include:

  • noqa: # noqa: E501
  • type: ignore: # type: ignore[no-untyped-def]
  • pylint: # pylint: disable=protected-access
  • pytype: # pytype: disable=attribute-error
# Cercis's default style
# (Suppose line length limit is 30)

# This line has 30 characters
var = some_func(some_long_arg)  # noqa:F501

# This line has 31 characters
var_ = some_func(
    some_long_arg
)  # type: ignore

# Cercis doesn't wraps a line if its main
# content (without the comment) does not
# exceed the line length limit.
# Black's style (not configurable)
# (Suppose line length limit is 30)

# Black doesn't wrap lines, no matter
# how long, if the line has
# a "# type: ignore..." comment.
# (This line has 31 characters.)
var_ = some_func(some_long_arg)  # type: ignore

# Black does not recognize "# type:ignore",
# even though mypy recognizes it.
var_ = some_func(
    some_long_arg
)  # type:ignore

# Black only recognizes "# type: ignore"
var_ = some_func(
    some_long_arg
)  # noqa:F501
Option
Name --wrap-pragma-comments
Abbreviation -wpc
Default False
Black's style True
Command line usage cercis -wpc=True myScript.py
pyproject.toml usage wrap-pragma-comments = true under [tool.cercis]
pre-commit usage args: [--wrap-pragma-comments=False]

4. How to configure Cercis

4.1. Dynamically in the command line

Here are some examples:

  • cercis --single-quote=True myScript.py to format files to single quotes
  • cercis --function-definition-extra-indent=False myScript.py to format files without extra indentation at function definition
  • cercis --line-length=79 myScript.py to format files with a line length of 79 characters

4.2. In your project's pyproject.toml file

You can specify the options under the [tool.cercis] section of the file:

[tool.cercis]
line-length = 88
function-definition-extra-indent = true
single-quote = false

4.3. In your project's .pre-commit-config.yaml file

You can specify the options under the args section of your .pre-commit-config.yaml file.

For example:

repos:
  - repo: https://github.com/jsh9/cercis
    rev: 0.1.0
    hooks:
      - id: cercis
        args: [--function-definition-extra-indent=False, --ling-length=79]
  - repo: https://github.com/jsh9/cercis
    rev: 0.1.0
    hooks:
      - id: cercis-jupyter
        args: [--function-definition-extra-indent=False, --line-length=79]

The value in rev can be any Cercis release, or it can be main, which means to always use the latest (including unreleased) Cercis features.

4.4. Specify options in tox.ini

Currently, Cercis does not support a config section in tox.ini. Instead, you can specify the options in pyproject.toml.

4.5. How to reproduce Black's behavior

If you'd like to reproduce Black's behavior, simply set all the configurable options in Section 3 to Black's default values.

Change Log

[0.1.3] - 2023-05-07

  • Added

    • A new configurable option: --collapse-nested-brackets
    • A new configurable option: --wrap-pragma-comments
    • Some Github workflow actions to make sure CHANGELOG.md is updated
  • Changed

    • Changed the default quote to single quote
    • Changed the default line length to 79 characters
  • Removed

    • Some unrelated documentation and config files

[0.1.2] - 2023-05-04

[0.1.1] - 2023-05-03

[0.1.0] - 2023-04-30

  • This is the initial version that branches away from Black (commit: e712e4)
  • Changed
    • The default indentation within a function definition (when line wrap happens) is now 8 spaces. (Black's default is 4, which is not PEP8-compatible)
    • Updated README, because cercis now branches away from Black
  • Added
    • A configurable option (function-definition-extra-indent) is added instead of enforcing 8 spaces for everyone

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

cercis-0.1.3.tar.gz (285.1 kB view hashes)

Uploaded Source

Built Distribution

cercis-0.1.3-py3-none-any.whl (167.0 kB view hashes)

Uploaded Python 3

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page