Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement support for configuration via pyproject.toml #830

Merged
merged 1 commit into from
Aug 31, 2021
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
7 changes: 6 additions & 1 deletion docs/config.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,10 @@ list(global_jupytext_configuration_directories())
```
which include `XDG_CONFIG_HOME` (defaults to `$HOME/.config`) and `XDG_CONFIG_DIR`.

The name for the configuration file can be any of `jupytext.config.JUPYTEXT_CONFIG_FILES`, i.e. `.jupytext` (in TOML), `jupytext.toml`, `jupytext.yml`, `jupytext.yaml`, `jupytext.json` or `jupytext.py`, and their dot-file versions.
The name for the configuration file can be any of `jupytext.config.JUPYTEXT_CONFIG_FILES`, i.e. `.jupytext` (in TOML),
`jupytext.toml`, `jupytext.yml`, `jupytext.yaml`, `jupytext.json` or `jupytext.py`, and their dot-file versions.
Alternatively, if you are using it, you can also use your Python project's `pyproject.toml` file by adding
configuration to a `[tool.jupytext]` table within it.

If you want to know, for a given directory, which configuration file Jupytext is using, please execute:
```python
Expand Down Expand Up @@ -81,6 +84,8 @@ or alternatively, using a dict to map the prefix path to the format name:
"notebooks/" = "ipynb"
"scripts/" = "py:percent"
```
Note that if you are using a `pyproject.toml` file with this dict format, you should make sure the table header is instead `[tool.jupytext.formats]`
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for documenting this. By the way I was thinking that it would be helpful to give a small example pyproject.toml file, say e.g.

[tool.jupytext]
formats = "ipynb,py:percent"

Where do you think this example would be best located in the documentation? Maybe right after the sample jupytext.tom file with the same configuration?


The `root_prefix` is matched with the top-most parent folder of the matching name, not above the Jupytext configuration file.

For instance, with the pairing above, a notebook with path `/home/user/jupyter/notebooks/project1/example.ipynb` will be paired with the Python file `/home/user/jupyter/scripts/project1/example.py`.
Expand Down
19 changes: 18 additions & 1 deletion jupytext/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ class JupytextConfigurationError(ValueError):
JUPYTEXT_CONFIG_FILES.extend(
["." + filename for filename in JUPYTEXT_CONFIG_FILES] + [".jupytext.py"]
)

PYPROJECT_FILE = "pyproject.toml"

JUPYTEXT_CEILING_DIRECTORIES = [
path
for path in os.environ.get("JUPYTEXT_CEILING_DIRECTORIES", "").split(":")
Expand Down Expand Up @@ -316,6 +319,15 @@ def find_jupytext_configuration_file(path, search_parent_dirs=True):
if os.path.isfile(full_path):
return full_path

pyproject_path = os.path.join(path, PYPROJECT_FILE)
if os.path.isfile(pyproject_path):
import toml

with open(pyproject_path, "r") as stream:
doc = toml.loads(stream.read())
if doc.get("tool", {}).get("jupytext") is not None:
return pyproject_path
Comment on lines +324 to +329
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Don't you think we should have the same lines in get_config_file in the contents manager?


if not search_parent_dirs:
return None

Expand Down Expand Up @@ -343,7 +355,12 @@ def parse_jupytext_configuration_file(jupytext_config_file, stream=None):
if jupytext_config_file.endswith((".toml", "jupytext")):
import toml

return toml.loads(stream)
doc = toml.loads(stream)
print(jupytext_config_file)
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we remove the print instruction?

if jupytext_config_file.endswith(PYPROJECT_FILE):
return doc["tool"]["jupytext"]
else:
return doc

if jupytext_config_file.endswith((".yml", ".yaml")):
return yaml.safe_load(stream)
Expand Down
5 changes: 5 additions & 0 deletions jupytext/contentsmanager.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@

from .config import (
JUPYTEXT_CONFIG_FILES,
PYPROJECT_FILE,
JupytextConfiguration,
JupytextConfigurationError,
find_global_jupytext_configuration_file,
Expand Down Expand Up @@ -477,6 +478,10 @@ def get_config_file(self, directory):
if self.file_exists(path):
return path

pyproject_path = directory + "/" + PYPROJECT_FILE
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the location where I think we should test whether or not there is a jupytext entry in the pyproject file.
(BTW the + "/" is correct, we could add a comment above: paths in Jupyter's contents manager always use the linux path separator, even on Windows)

if self.file_exists(pyproject_path):
return pyproject_path

if not directory:
return None

Expand Down
1 change: 1 addition & 0 deletions tests/test_cm_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,7 @@ def test_pairing_through_config_leaves_ipynb_unmodified(tmpdir):
("jupytext.toml", "hide_notebook_metadata = False"),
("jupytext.toml", 'hide_notebook_metadata = "False"'),
("jupytext.toml", "not_a_jupytext_option = true"),
("pyproject.toml", "[tool.jupytext]\nnot_a_jupytext_option = true"),
("jupytext.json", '{"notebook_metadata_filter":"-all",}'),
(".jupytext.py", "c.not_a_jupytext_option = True"),
(".jupytext.py", "c.hide_notebook_metadata = true"),
Expand Down
29 changes: 27 additions & 2 deletions tests/test_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,16 @@ def test_find_jupytext_configuration_file(tmpdir):
find_jupytext_configuration_file(str(nested)), str(root_config)
)

# Local pyproject file
pyproject_config = nested.join("pyproject.toml")
pyproject_config.write("[tool.jupytext]\n")
assert os.path.samefile(
find_jupytext_configuration_file(str(tmpdir)), str(root_config)
)
assert os.path.samefile(
find_jupytext_configuration_file(str(nested)), str(pyproject_config)
)

# Local configuration file
local_config = nested.join(".jupytext")
local_config.write("\n")
Expand All @@ -49,12 +59,27 @@ def test_jupytext_py_is_not_a_configuration_file(tmpdir):

@pytest.mark.parametrize(
"config_file",
["jupytext", "jupytext.toml", "jupytext.yml", "jupytext.json", "jupytext.py"],
[
"pyproject.toml",
"jupytext",
"jupytext.toml",
"jupytext.yml",
"jupytext.json",
"jupytext.py",
],
)
def test_load_jupytext_configuration_file(tmpdir, config_file):
full_config_path = tmpdir.join(config_file)

if config_file.endswith(("jupytext", ".toml")):
if config_file == "pyproject.toml":
full_config_path.write(
"""[tool.jupytext]
formats = "ipynb,py:percent"
notebook_metadata_filter = "all"
cell_metadata_filter = "all"
"""
)
elif config_file.endswith(("jupytext", ".toml")):
full_config_path.write(
"""formats = "ipynb,py:percent"
notebook_metadata_filter = "all"
Expand Down