nbsync 0.3.6__tar.gz → 0.3.8__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,45 +1,46 @@
1
- Metadata-Version: 2.4
1
+ Metadata-Version: 2.3
2
2
  Name: nbsync
3
- Version: 0.3.6
3
+ Version: 0.3.8
4
4
  Summary: A core library to synchronize Jupyter notebooks and Markdown documents, enabling seamless integration and dynamic content execution
5
- Project-URL: Documentation, https://daizutabi.github.io/nbsync/
6
- Project-URL: Source, https://github.com/daizutabi/nbsync
7
- Project-URL: Issues, https://github.com/daizutabi/nbsync/issues
5
+ Keywords: jupyter,notebook,documentation,markdown,python,visualization,dynamic-execution,real-time-sync
6
+ Author: daizutabi
8
7
  Author-email: daizutabi <daizutabi@gmail.com>
9
8
  License: MIT License
10
-
11
- Copyright (c) 2025 Daizu
12
-
13
- Permission is hereby granted, free of charge, to any person obtaining a copy
14
- of this software and associated documentation files (the "Software"), to deal
15
- in the Software without restriction, including without limitation the rights
16
- to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17
- copies of the Software, and to permit persons to whom the Software is
18
- furnished to do so, subject to the following conditions:
19
-
20
- The above copyright notice and this permission notice shall be included in all
21
- copies or substantial portions of the Software.
22
-
23
- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24
- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25
- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26
- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27
- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28
- OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29
- SOFTWARE.
30
- License-File: LICENSE
31
- Keywords: documentation,dynamic-execution,jupyter,markdown,notebook,python,real-time-sync,visualization
9
+
10
+ Copyright (c) 2025 Daizu
11
+
12
+ Permission is hereby granted, free of charge, to any person obtaining a copy
13
+ of this software and associated documentation files (the "Software"), to deal
14
+ in the Software without restriction, including without limitation the rights
15
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
16
+ copies of the Software, and to permit persons to whom the Software is
17
+ furnished to do so, subject to the following conditions:
18
+
19
+ The above copyright notice and this permission notice shall be included in all
20
+ copies or substantial portions of the Software.
21
+
22
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
25
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
26
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
27
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
28
+ SOFTWARE.
32
29
  Classifier: Development Status :: 4 - Beta
33
30
  Classifier: Programming Language :: Python
34
31
  Classifier: Programming Language :: Python :: 3.10
35
32
  Classifier: Programming Language :: Python :: 3.11
36
33
  Classifier: Programming Language :: Python :: 3.12
37
34
  Classifier: Programming Language :: Python :: 3.13
35
+ Classifier: Programming Language :: Python :: 3.14
38
36
  Classifier: Topic :: Documentation
39
37
  Classifier: Topic :: Software Development :: Documentation
40
38
  Classifier: Topic :: Text Processing :: Markup :: Markdown
41
- Requires-Python: >=3.10
42
39
  Requires-Dist: nbstore>=0.5.2
40
+ Requires-Python: >=3.10
41
+ Project-URL: Documentation, https://daizutabi.github.io/nbsync/
42
+ Project-URL: Issues, https://github.com/daizutabi/nbsync/issues
43
+ Project-URL: Source, https://github.com/daizutabi/nbsync
43
44
  Description-Content-Type: text/markdown
44
45
 
45
46
  # nbsync
@@ -144,6 +145,22 @@ Use standard Markdown image syntax with the figure identifier:
144
145
  ![Chart description](my-notebook.ipynb){#my-figure}
145
146
  ```
146
147
 
148
+ ### Control how results render
149
+
150
+ You can control how executed outputs render using attributes:
151
+
152
+ - `source`: where to place the source relative to the result.
153
+ Supported values include `on`/`1` (above), `below`, `only`,
154
+ `material-block`, `tabbed-left`, `tabbed-right`. See tests for details.
155
+ - `result`: wrap the execution result in a fenced code block using the
156
+ given language (similar to mkdocs-exec). For example:
157
+
158
+ ````markdown
159
+ ```python exec="1" result="text"
160
+ print(2)
161
+ ```
162
+ ````
163
+
147
164
  ## The Power of Separation
148
165
 
149
166
  Creating documentation and developing visualizations involve different
@@ -100,6 +100,22 @@ Use standard Markdown image syntax with the figure identifier:
100
100
  ![Chart description](my-notebook.ipynb){#my-figure}
101
101
  ```
102
102
 
103
+ ### Control how results render
104
+
105
+ You can control how executed outputs render using attributes:
106
+
107
+ - `source`: where to place the source relative to the result.
108
+ Supported values include `on`/`1` (above), `below`, `only`,
109
+ `material-block`, `tabbed-left`, `tabbed-right`. See tests for details.
110
+ - `result`: wrap the execution result in a fenced code block using the
111
+ given language (similar to mkdocs-exec). For example:
112
+
113
+ ````markdown
114
+ ```python exec="1" result="text"
115
+ print(2)
116
+ ```
117
+ ````
118
+
103
119
  ## The Power of Separation
104
120
 
105
121
  Creating documentation and developing visualizations involve different
@@ -1,10 +1,10 @@
1
1
  [build-system]
2
- requires = ["hatchling"]
3
- build-backend = "hatchling.build"
2
+ requires = ["uv_build"]
3
+ build-backend = "uv_build"
4
4
 
5
5
  [project]
6
6
  name = "nbsync"
7
- version = "0.3.6"
7
+ version = "0.3.8"
8
8
  description = "A core library to synchronize Jupyter notebooks and Markdown documents, enabling seamless integration and dynamic content execution"
9
9
  readme = "README.md"
10
10
  license = { file = "LICENSE" }
@@ -26,6 +26,7 @@ classifiers = [
26
26
  "Programming Language :: Python :: 3.11",
27
27
  "Programming Language :: Python :: 3.12",
28
28
  "Programming Language :: Python :: 3.13",
29
+ "Programming Language :: Python :: 3.14",
29
30
  "Topic :: Documentation",
30
31
  "Topic :: Software Development :: Documentation",
31
32
  "Topic :: Text Processing :: Markup :: Markdown",
@@ -40,12 +41,11 @@ Issues = "https://github.com/daizutabi/nbsync/issues"
40
41
 
41
42
  [dependency-groups]
42
43
  dev = [
43
- "ipykernel>=6.29.5",
44
- "matplotlib>=3.10.1",
45
- "nbconvert>=7.16.6",
46
- "pytest-cov>=6.1.0",
47
- "pytest-randomly>=3.16.0",
48
- "ruff>=0.11.4",
44
+ "ipykernel>=6",
45
+ "matplotlib>=3",
46
+ "nbconvert>=7",
47
+ "pytest-cov>=6",
48
+ "pytest-randomly>=3",
49
49
  ]
50
50
  docs = ["mkapi", "mkdocs-material"]
51
51
 
@@ -69,7 +69,16 @@ target-version = "py310"
69
69
  [tool.ruff.lint]
70
70
  select = ["ALL"]
71
71
  unfixable = ["F401"]
72
- ignore = ["ANN401", "ARG001", "ARG002", "C901", "D", "PLR0911", "S105"]
72
+ ignore = [
73
+ "ANN401",
74
+ "ARG001",
75
+ "ARG002",
76
+ "C901",
77
+ "D",
78
+ "PLC0415",
79
+ "PLR0911",
80
+ "S105",
81
+ ]
73
82
 
74
83
  [tool.ruff.lint.per-file-ignores]
75
84
  "**/tests/*" = ["ANN", "ARG", "D", "FBT", "PGH003", "PLR", "RUF", "S", "SLF"]
@@ -89,8 +89,13 @@ def get_source(
89
89
  def get_text_markdown(cell: Cell, *, escape: bool = False) -> str:
90
90
  text = str(cell.content.rstrip())
91
91
 
92
- if lang := cell.image.attributes.get("result", ""):
93
- return f"```{lang}\n{text}\n```"
92
+ if result := cell.image.attributes.get("result", ""):
93
+ if not is_truelike(result):
94
+ lang = result
95
+ return f"```{lang}\n{text}\n```"
96
+
97
+ if cell.mime == "text/plain":
98
+ return f"```text\n{text}\n```"
94
99
 
95
100
  if escape and cell.mime == "text/plain":
96
101
  return html.escape(text)
@@ -1,19 +0,0 @@
1
- {
2
- "image": "mcr.microsoft.com/vscode/devcontainers/base:ubuntu24.04",
3
- "features": {
4
- "ghcr.io/devcontainers-contrib/features/starship:1": {}
5
- },
6
- "customizations": {
7
- "vscode": {
8
- "extensions": [
9
- "charliermarsh.ruff",
10
- "fill-labs.dependi",
11
- "markis.code-coverage",
12
- "ms-python.python",
13
- "ms-python.vscode-pylance",
14
- "tamasfe.even-better-toml"
15
- ]
16
- }
17
- },
18
- "postCreateCommand": ".devcontainer/postCreate.sh"
19
- }
@@ -1,8 +0,0 @@
1
- #!/bin/bash
2
-
3
- echo 'eval "$(starship init bash)"' >> ~/.bashrc
4
- mkdir -p ~/.config
5
- cp .devcontainer/starship.toml ~/.config
6
-
7
- curl -LsSf https://astral.sh/uv/install.sh | sh
8
- echo 'eval "$(uv generate-shell-completion bash)"' >> ~/.bashrc
@@ -1,29 +0,0 @@
1
- "$schema" = 'https://starship.rs/config-schema.json'
2
-
3
- add_newline = true
4
-
5
- [username]
6
- disabled = true
7
-
8
- [hostname]
9
- disabled = true
10
-
11
- [package]
12
- format = '[$symbol$version]($style) '
13
- symbol = "󰏗 "
14
- style = 'bold blue'
15
-
16
- [git_branch]
17
- format = '[$symbol$branch(:$remote_branch)]($style)'
18
- symbol = ' '
19
- style = 'bold green'
20
-
21
- [git_status]
22
- style = 'red'
23
- modified = '*'
24
- format = '([$modified]($style)) '
25
-
26
- [python]
27
- format = '[${symbol}${pyenv_prefix}(${version} )(\($virtualenv\) )]($style)'
28
- symbol = ' '
29
- style = 'yellow'
@@ -1,3 +0,0 @@
1
- **/notebooks/* linguist-vendored
2
- .devcontainer/* linguist-vendored
3
- *.ipynb linguist-vendored
@@ -1,51 +0,0 @@
1
- name: CI
2
-
3
- on:
4
- push:
5
- branches: [main]
6
- pull_request:
7
-
8
- concurrency:
9
- group: test-${{ github.head_ref }}
10
- cancel-in-progress: true
11
-
12
- env:
13
- PYTHONUNBUFFERED: "1"
14
- FORCE_COLOR: "1"
15
-
16
- jobs:
17
- run:
18
- name: Python ${{ matrix.python-version }} on ${{ startsWith(matrix.os, 'macos-') && 'macOS' || startsWith(matrix.os, 'windows-') && 'Windows' || 'Linux' }}
19
- runs-on: ${{ matrix.os }}
20
- strategy:
21
- fail-fast: false
22
- matrix:
23
- os: [ubuntu-latest, windows-latest, macos-latest]
24
- python-version: ["3.10", "3.11", "3.12", "3.13"]
25
-
26
- steps:
27
- - uses: actions/checkout@v4
28
- - name: Set up Python ${{ matrix.python-version }}
29
- uses: actions/setup-python@v5
30
- with:
31
- python-version: ${{ matrix.python-version }}
32
- allow-prereleases: true
33
- - name: Install uv and ruff
34
- run: pip install uv ruff
35
- - name: Install the project
36
- run: uv sync
37
- - name: Ruff check
38
- run: ruff check
39
- - name: Run test
40
- run: uv run pytest -v --junitxml=junit.xml
41
- - name: Upload Codecov Results
42
- if: success()
43
- uses: codecov/codecov-action@v4
44
- with:
45
- token: ${{ secrets.CODECOV_TOKEN }}
46
- file: lcov.info
47
- - name: Upload test results to Codecov
48
- if: ${{ !cancelled() }}
49
- uses: codecov/test-results-action@v1
50
- with:
51
- token: ${{ secrets.CODECOV_TOKEN }}
@@ -1,29 +0,0 @@
1
- name: Documentation
2
-
3
- on:
4
- push:
5
- branches: [main]
6
- tags:
7
- - "[0-9]+.[0-9]+.[0-9]+"
8
-
9
- jobs:
10
- docs:
11
- runs-on: ubuntu-latest
12
- permissions:
13
- contents: write
14
- steps:
15
- - uses: actions/checkout@v4
16
- - name: Configure Git Credentials
17
- run: |
18
- git config user.name github-actions[bot]
19
- git config user.email 41898282+github-actions[bot]@users.noreply.github.com
20
- - name: Set up Python 3.13
21
- uses: actions/setup-python@v5
22
- with:
23
- python-version: 3.13
24
- - name: Install uv
25
- run: pip install uv
26
- - name: Install the project
27
- run: uv sync --group docs
28
- - name: Deploy documentation
29
- run: uv run mkdocs gh-deploy --force
@@ -1,37 +0,0 @@
1
- name: Publish
2
-
3
- on:
4
- push:
5
- tags:
6
- - "[0-9]+.[0-9]+.[0-9]+"
7
-
8
- jobs:
9
- publish:
10
- runs-on: ubuntu-latest
11
- permissions:
12
- id-token: write
13
- steps:
14
- - uses: actions/checkout@v4
15
-
16
- - name: Verify version match
17
- run: |
18
- TAG=${GITHUB_REF#refs/tags/}
19
- PYPROJECT_VERSION=$(grep -m 1 "version = " pyproject.toml | cut -d'"' -f2)
20
- if [ "$TAG" != "$PYPROJECT_VERSION" ]; then
21
- echo "Error: Git tag ($TAG) does not match pyproject.toml version ($PYPROJECT_VERSION)"
22
- exit 1
23
- fi
24
- echo "Version verified: $TAG"
25
-
26
- - name: Set up Python
27
- uses: actions/setup-python@v5
28
- with:
29
- python-version: 3.13
30
- - name: Install uv
31
- run: pip install uv
32
- - name: Install the project
33
- run: uv sync
34
- - name: Build the project
35
- run: uv build --no-sources
36
- - name: Upload the project to PyPI
37
- run: uv publish
nbsync-0.3.6/.gitignore DELETED
@@ -1,7 +0,0 @@
1
- .coverage*
2
- .venv/
3
- __pycache__/
4
- dist/
5
- lcov.info
6
- site/
7
- uv.lock
@@ -1 +0,0 @@
1
- # nbsync
nbsync-0.3.6/mkdocs.yaml DELETED
@@ -1,61 +0,0 @@
1
- site_name: nbsync
2
- site_url: https://daizutabi.github.io/nbsync/
3
- site_description: A plugin for mkdocs to convert notebooks to markdown
4
- site_author: daizutabi
5
- repo_url: https://github.com/daizutabi/nbsync/
6
- repo_name: daizutabi/nbsync
7
- edit_uri: edit/main/docs/
8
- theme:
9
- name: material
10
- font:
11
- text: Fira Sans
12
- code: Fira Code
13
- icon:
14
- repo: fontawesome/brands/github
15
- logo: material/notebook-edit-outline
16
- palette:
17
- - scheme: default
18
- primary: indigo
19
- accent: indigo
20
- toggle:
21
- icon: material/weather-sunny
22
- name: Switch to dark mode
23
- - scheme: slate
24
- primary: black
25
- accent: black
26
- toggle:
27
- icon: material/weather-night
28
- name: Switch to light mode
29
- features:
30
- - content.code.annotate
31
- - content.code.copy
32
- - content.tooltips
33
- - navigation.expand
34
- - navigation.footer
35
- - navigation.indexes
36
- - navigation.sections
37
- - navigation.tabs
38
- - navigation.top
39
- - navigation.tracking
40
- - search.highlight
41
- - search.suggest
42
- plugins:
43
- - search
44
- - mkapi:
45
- debug: true
46
- markdown_extensions:
47
- - admonition
48
- - pymdownx.emoji:
49
- emoji_index: !!python/name:material.extensions.emoji.twemoji
50
- emoji_generator: !!python/name:material.extensions.emoji.to_svg
51
- - pymdownx.highlight:
52
- use_pygments: true
53
- - pymdownx.inlinehilite
54
- - pymdownx.magiclink
55
- - pymdownx.snippets
56
- - pymdownx.superfences
57
- - pymdownx.tabbed:
58
- alternate_style: true
59
- nav:
60
- - Home: index.md
61
- - API: $api/nbsync.***
File without changes
@@ -1,7 +0,0 @@
1
- import pytest
2
- from nbstore.store import Store
3
-
4
-
5
- @pytest.fixture(scope="session")
6
- def store():
7
- return Store("tests/notebooks")
@@ -1,162 +0,0 @@
1
- import textwrap
2
-
3
- import nbformat
4
- import nbstore.notebook
5
- import pytest
6
- from nbstore import Store
7
-
8
- from nbsync.cell import Cell
9
- from nbsync.sync import Synchronizer
10
-
11
-
12
- @pytest.fixture(scope="module")
13
- def nb():
14
- nb = nbformat.v4.new_notebook()
15
- for source in [
16
- "# #id\nprint(1+1)",
17
- "# #empty\n",
18
- "# #fig\nimport matplotlib.pyplot as plt\nplt.plot([1])",
19
- "# #func\ndef f():\n pass",
20
- ]:
21
- nb.cells.append(nbformat.v4.new_code_cell(source))
22
- nbstore.notebook.execute(nb)
23
- return nb
24
-
25
-
26
- @pytest.fixture(scope="module")
27
- def store(nb, tmp_path_factory: pytest.TempPathFactory) -> Store:
28
- src_dir = tmp_path_factory.mktemp("src")
29
- nbformat.write(nb, src_dir.joinpath("a.ipynb"))
30
- return Store(src_dir)
31
-
32
-
33
- @pytest.fixture(scope="module")
34
- def sync(store):
35
- return Synchronizer(store)
36
-
37
-
38
- @pytest.fixture(scope="module")
39
- def convert(sync: Synchronizer):
40
- def convert(text: str) -> str:
41
- cell = next(sync.convert(text))
42
- assert isinstance(cell, Cell)
43
- return cell.convert(escape=True)
44
-
45
- return convert
46
-
47
-
48
- def test_text_plain(convert):
49
- assert convert("![a](a.ipynb){#id}") == "2"
50
-
51
-
52
- def test_empty(convert):
53
- assert convert("![a](a.ipynb){#empty}") == ""
54
-
55
-
56
- def test_image(convert):
57
- x = convert("![a](a.ipynb){#fig a b=c}")
58
- assert x.startswith("![a]")
59
- assert x.endswith('.png){#fig a b="c"}')
60
-
61
-
62
- def test_func(convert):
63
- x = convert("![a](a.ipynb){#func a=b c identifier='1'}")
64
- assert x == '```python c a="b"\n# #func\ndef f():\n pass\n```'
65
-
66
-
67
- @pytest.mark.parametrize("kind", ["above", "on", "1"])
68
- def test_above(convert, kind):
69
- x = convert(f"![a](a.ipynb){{#fig source='{kind}' a b='c'}}")
70
- assert x.startswith("```python\n")
71
- assert x.endswith('.png){#fig a b="c"}')
72
-
73
-
74
- def test_below(convert):
75
- x = convert("![a](a.ipynb){#fig source='below' a b=c}")
76
- assert x.startswith("![a](")
77
- assert x.endswith("[1])\n```")
78
-
79
-
80
- def test_material_block(convert):
81
- x = convert("![a](a.ipynb){#fig source='material-block' a b=c}")
82
- assert '<div class="result" markdown="1">\n![a]' in x
83
-
84
-
85
- def test_tabbed_left(convert):
86
- x = convert("![a](a.ipynb){#fig source='tabbed-left' a b=c}")
87
- assert x.startswith('===! "Source"\n\n ```python\n')
88
- assert '=== "Result"\n\n ![a]' in x
89
-
90
-
91
- def test_tabbed_left_title(convert):
92
- x = convert("![a](a.ipynb){#fig source='tabbed-left' tabs='a|b'}")
93
- assert x.startswith('===! "a"\n\n ```python\n')
94
- assert '=== "b"\n\n ![a]' in x
95
-
96
-
97
- def test_tabbed_right_title(convert):
98
- x = convert("![a](a.ipynb){#fig source='tabbed-right' tabs='a|b'}")
99
- assert x.startswith('===! "a"\n\n ![a]')
100
- assert '=== "b"\n\n ```python\n' in x
101
-
102
-
103
- def test_unknown(convert):
104
- x = convert("![a](a.ipynb){#id source='unknown' tabs='a|b'}")
105
- assert x == "2"
106
-
107
-
108
- def test_code_block_exec(convert):
109
- x = convert('```python exec="1" source="1"\nprint(1+1)\n```')
110
- assert x == "```python\nprint(1+1)\n```\n\n2"
111
-
112
-
113
- def test_code_block_exec_escape_print(convert):
114
- x = convert('```python exec="1" source="1"\nprint("<1>")\n```')
115
- assert x == '```python\nprint("<1>")\n```\n\n&lt;1&gt;'
116
-
117
-
118
- def test_code_block_exec_escape_stream(convert):
119
- x = convert('```python exec="1" source="1"\n"<1>"\n```')
120
- assert x == '```python\n"<1>"\n```\n\n&#x27;&lt;1&gt;&#x27;'
121
-
122
-
123
- def test_code_block_exec_result(convert):
124
- x = convert('```python exec="1" result="text"\nprint(1+1)\nprint("<1>")\n```')
125
- assert x == "```text\n2\n<1>\n```"
126
-
127
-
128
- def test_code_block_exec_result_source(convert):
129
- t = '```python exec="1" source="material-block" result="text"\n'
130
- t += 'print(1+1)\nprint("<1>")\n```'
131
- x = convert(t)
132
- y = textwrap.dedent("""\
133
- ```python
134
- print(1+1)
135
- print("<1>")
136
- ```
137
-
138
- <div class="result" markdown="1">
139
- ```text
140
- 2
141
- <1>
142
- ```
143
- </div>""")
144
- assert x == y
145
-
146
-
147
- def test_code_block_html(convert):
148
- src = textwrap.dedent("""\
149
- from IPython.display import HTML
150
- HTML("<div>a</div>")
151
- """)
152
- x = convert(f'```python exec="1"\n{src}```')
153
- assert x == "<div>a</div>"
154
-
155
-
156
- def test_code_block_html_result(convert):
157
- src = textwrap.dedent("""\
158
- from IPython.display import HTML
159
- HTML("<div>a</div>")
160
- """)
161
- x = convert(f'```python exec="1" result="html"\n{src}```')
162
- assert x == "```html\n<div>a</div>\n```"
@@ -1,64 +0,0 @@
1
- import logging
2
- from logging import Logger
3
-
4
- import pytest
5
-
6
- from nbsync import logger
7
-
8
-
9
- @pytest.fixture
10
- def reset_logger():
11
- original_logger = logger._logger
12
-
13
- yield
14
-
15
- logger._logger = original_logger
16
-
17
-
18
- def test_default_logger():
19
- assert isinstance(logger._logger, Logger)
20
- assert logger._logger.name == "nbsync"
21
-
22
-
23
- def test_configure(reset_logger):
24
- custom_logger = logging.getLogger("custom_logger")
25
-
26
- result = logger.set_logger(custom_logger)
27
-
28
- assert result is custom_logger
29
- assert logger._logger is custom_logger
30
- assert logger._logger.name == "custom_logger"
31
-
32
-
33
- def test_configure_no_args(reset_logger):
34
- custom_logger = logging.getLogger("custom_logger")
35
- logger._logger = custom_logger
36
-
37
- result = logger.set_logger()
38
-
39
- assert result is custom_logger
40
- assert logger._logger is custom_logger
41
-
42
-
43
- def test_logging_methods(reset_logger, caplog):
44
- caplog.set_level(logging.DEBUG, logger="nbsync")
45
-
46
- debug_msg = "Debug message"
47
- info_msg = "Info message"
48
- warning_msg = "Warning message"
49
- error_msg = "Error message"
50
-
51
- logger.debug(debug_msg)
52
- logger.info(info_msg)
53
- logger.warning(warning_msg)
54
- logger.error(error_msg)
55
-
56
- assert debug_msg in caplog.text
57
- assert info_msg in caplog.text
58
- assert warning_msg in caplog.text
59
- assert error_msg in caplog.text
60
-
61
- assert (logger._logger.name, logging.DEBUG, debug_msg) in caplog.record_tuples
62
- assert (logger._logger.name, logging.INFO, info_msg) in caplog.record_tuples
63
- assert (logger._logger.name, logging.WARNING, warning_msg) in caplog.record_tuples
64
- assert (logger._logger.name, logging.ERROR, error_msg) in caplog.record_tuples
@@ -1,304 +0,0 @@
1
- import nbstore.markdown
2
- import pytest
3
- from nbstore.markdown import CodeBlock, Image, parse
4
-
5
-
6
- def test_convert_image():
7
- from nbsync.markdown import convert_image
8
-
9
- text = "![a](b.ipynb){#c}"
10
- image = next(parse(text))
11
- assert isinstance(image, Image)
12
- image = next(convert_image(image))
13
- assert isinstance(image, Image)
14
- assert image.alt == "a"
15
- assert image.url == "b.ipynb"
16
- assert image.identifier == "c"
17
-
18
-
19
- def test_convert_image_no_identifier():
20
- from nbsync.markdown import convert_image
21
-
22
- text = "![ a ]( b.ipynb ){ c ' b ' }"
23
- image = next(parse(text))
24
- assert isinstance(image, Image)
25
- text_ = next(convert_image(image))
26
- assert isinstance(text_, str)
27
- assert text_ == text
28
-
29
-
30
- def test_convert_image_source_with_identifier():
31
- from nbsync.markdown import convert_image
32
-
33
- text = '![a](b.ipynb){#c `x=1` exec="1"}'
34
- image = next(parse(text))
35
- assert isinstance(image, Image)
36
- assert image.source
37
- it = convert_image(image)
38
- code_block = next(it)
39
- assert isinstance(code_block, CodeBlock)
40
- assert code_block.text == ""
41
- assert code_block.url == "b.ipynb"
42
- assert code_block.identifier == "c"
43
- assert code_block.classes == []
44
- assert code_block.attributes == {}
45
-
46
- image = next(it)
47
- assert isinstance(image, Image)
48
- assert image.text == text
49
- assert image.url == "b.ipynb"
50
- assert image.identifier == "c"
51
- assert image.classes == []
52
- assert image.attributes == {"exec": "1"}
53
-
54
-
55
- def test_convert_image_source_without_identifier():
56
- from nbsync.markdown import convert_image
57
-
58
- text = '![a](b.ipynb){`x=1` exec="1"}'
59
- image = next(parse(text))
60
- assert isinstance(image, Image)
61
- it = convert_image(image, 0)
62
- code_block = next(it)
63
- image = next(it)
64
- assert isinstance(code_block, CodeBlock)
65
- assert isinstance(image, Image)
66
- assert code_block.identifier == image.identifier
67
- assert code_block.identifier == "image-nbsync-0"
68
-
69
-
70
- def test_convert_image_source_without_identifier_error():
71
- from nbsync.markdown import convert_image
72
-
73
- text = '![a](b.ipynb){`x=1` exec="1"}'
74
- image = next(parse(text))
75
- assert isinstance(image, Image)
76
- with pytest.raises(ValueError, match="index is required"):
77
- list(convert_image(image))
78
-
79
-
80
- SOURCE_TAB_CODE_BLOCK = """\
81
- ````markdown source="tabbed-nbsync"
82
- ```python exec="on"
83
- print("Hello Markdown from markdown-exec!")
84
- ```
85
- ````
86
- """
87
-
88
-
89
- def test_convert_tabbed_code_block_code_block():
90
- from nbsync.markdown import convert_code_block
91
-
92
- elems = nbstore.markdown.parse(SOURCE_TAB_CODE_BLOCK)
93
- code_block = list(elems)[0]
94
- assert isinstance(code_block, CodeBlock)
95
- elems = list(convert_code_block(code_block))
96
- assert isinstance(elems[0], str)
97
- assert elems[0].startswith("===")
98
- assert "tabbed-nbsync" not in elems[0]
99
- assert elems[1] == '=== "Rendered"\n\n'
100
- assert isinstance(elems[2], CodeBlock)
101
- assert elems[2].classes == ["python"]
102
- assert elems[2].source.startswith("print(")
103
- assert elems[2].text.startswith(" ```python")
104
-
105
-
106
- SOURCE_TAB_IMAGE = """\
107
- ````markdown source="tabbed-nbsync"
108
- ![alt](a.py){#.}
109
- ````
110
- """
111
-
112
-
113
- def test_convert_tabbed_code_block_image():
114
- from nbsync.markdown import convert_code_block
115
-
116
- elems = nbstore.markdown.parse(SOURCE_TAB_IMAGE)
117
- code_block = list(elems)[0]
118
- assert isinstance(code_block, CodeBlock)
119
- elems = list(convert_code_block(code_block))
120
- assert elems[1] == '=== "Rendered"\n\n'
121
- assert isinstance(elems[2], Image)
122
- assert elems[2].indent == " "
123
- assert elems[2].text.startswith(" ![")
124
-
125
-
126
- def test_convert_code_block_exec_1():
127
- from nbsync.markdown import convert_code_block
128
-
129
- text = '```python exec="1" a\nprint(1+1)\n```'
130
- elems = nbstore.markdown.parse(text)
131
- code_block = list(elems)[0]
132
- assert isinstance(code_block, CodeBlock)
133
- x = next(convert_code_block(code_block))
134
- assert isinstance(x, Image)
135
- assert x.classes == ["a"]
136
- assert x.url == ".md"
137
-
138
-
139
- def test_convert_code_block_exec_on():
140
- from nbsync.markdown import convert_code_block
141
-
142
- text = '```python exec="on" a\nprint(1+1)\n```'
143
- elems = nbstore.markdown.parse(text)
144
- code_block = list(elems)[0]
145
- assert isinstance(code_block, CodeBlock)
146
- x = next(convert_code_block(code_block))
147
- assert isinstance(x, CodeBlock)
148
-
149
-
150
- def test_convert_code_block_exec_not_python():
151
- from nbsync.markdown import convert_code_block
152
-
153
- text = '```bash exec="1" a\nls\n```'
154
- elems = nbstore.markdown.parse(text)
155
- code_block = list(elems)[0]
156
- assert isinstance(code_block, CodeBlock)
157
- x = next(convert_code_block(code_block))
158
- assert isinstance(x, CodeBlock)
159
-
160
-
161
- def test_set_url():
162
- from nbsync.markdown import set_url
163
-
164
- image = Image("", "a", [], {}, "", "b.ipynb")
165
- image, url = set_url(image, "")
166
- assert isinstance(image, Image)
167
- assert url == "b.ipynb"
168
-
169
-
170
- def test_set_url_not_supported_extension():
171
- from nbsync.markdown import set_url
172
-
173
- image = Image("abc", "a", [], {}, "", "b.txt")
174
- text, url = set_url(image, "a.ipynb")
175
- assert text == "abc"
176
- assert url == "a.ipynb"
177
-
178
-
179
- @pytest.mark.parametrize("url", ["", "."])
180
- def test_set_url_empty_url(url: str):
181
- from nbsync.markdown import set_url
182
-
183
- image = Image("abc", "a", [], {}, "", url)
184
- image, url = set_url(image, "a.ipynb")
185
- assert isinstance(image, Image)
186
- assert image.url == "a.ipynb"
187
- assert url == "a.ipynb"
188
-
189
-
190
- def test_resolve_urls():
191
- from nbsync.markdown import resolve_urls
192
-
193
- images = [
194
- Image("abc", "a", [], {}, "", "a.py"),
195
- Image("abc", "a", [], {}, "", ""),
196
- Image("abc", "a", [], {}, "", "."),
197
- ]
198
- images = list(resolve_urls(images))
199
- for image in images:
200
- assert isinstance(image, Image)
201
- assert image.url == "a.py"
202
-
203
-
204
- def test_resolve_urls_code_block():
205
- from nbsync.markdown import resolve_urls
206
-
207
- code_blocks = [CodeBlock("abc", "a", [], {}, "", "")]
208
- text = list(resolve_urls(code_blocks))[0]
209
- assert text == "abc"
210
-
211
-
212
- def test_resolve_urls_str():
213
- from nbsync.markdown import resolve_urls
214
-
215
- text = list(resolve_urls(["abc"]))[0]
216
- assert text == "abc"
217
-
218
-
219
- SOURCE = """\
220
- ![alt](a.py){#.}
221
- ![alt](){#id}
222
- ```python
223
- a
224
- ```
225
- ```python {.#id2}
226
- b
227
- ```
228
- ![alt](){`x=1`}
229
- """
230
-
231
-
232
- @pytest.fixture(scope="module")
233
- def elems():
234
- from nbsync.markdown import parse
235
-
236
- return list(parse(SOURCE))
237
-
238
-
239
- def test_len(elems):
240
- assert len(elems) == 11
241
-
242
-
243
- def test_elems_0(elems):
244
- image = elems[0]
245
- assert isinstance(image, Image)
246
- assert image.identifier == "."
247
- assert image.url == "a.py"
248
-
249
-
250
- def test_elems_2(elems):
251
- image = elems[2]
252
- assert isinstance(image, Image)
253
- assert image.url == "a.py"
254
- assert image.identifier == "id"
255
-
256
-
257
- def test_elems_4(elems):
258
- assert elems[4] == "```python\na\n```"
259
-
260
-
261
- def test_elems_6(elems):
262
- code_block = elems[6]
263
- assert isinstance(code_block, CodeBlock)
264
- assert code_block.url == "a.py"
265
- assert code_block.identifier == "id2"
266
- assert code_block.source == "b"
267
-
268
-
269
- def test_elems_8(elems):
270
- code_block = elems[8]
271
- assert isinstance(code_block, CodeBlock)
272
- assert code_block.url == "a.py"
273
- assert code_block.source == "x=1"
274
-
275
-
276
- def test_elems_9(elems):
277
- image = elems[9]
278
- assert isinstance(image, Image)
279
- assert image.url == "a.py"
280
- assert image.identifier == elems[8].identifier
281
-
282
-
283
- def test_elems_10(elems):
284
- assert elems[10] == "\n"
285
-
286
-
287
- @pytest.mark.parametrize(
288
- ("value", "expected"),
289
- [
290
- ("1", True),
291
- ("0", False),
292
- ("yes", True),
293
- ("no", False),
294
- ("true", True),
295
- ("false", False),
296
- ("on", True),
297
- ("off", False),
298
- ],
299
- )
300
- def test_is_truelike(value, expected):
301
- from nbsync.markdown import is_truelike
302
-
303
- assert is_truelike(value) == expected
304
- assert is_truelike(value.upper()) == expected
@@ -1,50 +0,0 @@
1
- import nbformat
2
- import nbstore.notebook
3
-
4
- from nbsync.notebook import Notebook
5
-
6
-
7
- def test_notebook():
8
- nb = nbformat.v4.new_notebook()
9
- notebook = Notebook(nb)
10
- assert notebook.execution_needed is False
11
-
12
-
13
- def test_set_execution_needed():
14
- nb = nbformat.v4.new_notebook()
15
- notebook = Notebook(nb)
16
- notebook.set_execution_needed()
17
- assert notebook.execution_needed is True
18
-
19
-
20
- def test_add_cell():
21
- nb = nbformat.v4.new_notebook()
22
- notebook = Notebook(nb)
23
- notebook.add_cell("id", "print('Hello, world!')")
24
- assert not nb.cells
25
- assert id(nb) != id(notebook.nb)
26
- nb = notebook.nb
27
- assert nbstore.notebook.get_source(nb, "id") == "print('Hello, world!')"
28
- assert notebook.execution_needed is True
29
- notebook.add_cell("id2", "1")
30
- assert id(nb) == id(notebook.nb)
31
-
32
-
33
- def test_equals():
34
- nb1 = nbformat.v4.new_notebook()
35
- nb2 = nbformat.v4.new_notebook()
36
- notebook1 = Notebook(nb1)
37
- notebook2 = Notebook(nb2)
38
- assert notebook1.equals(notebook2) is True
39
- notebook1.add_cell("id", "print('Hello, world!')")
40
- assert notebook1.equals(notebook2) is False
41
-
42
-
43
- def test_execute():
44
- nb = nbformat.v4.new_notebook()
45
- notebook = Notebook(nb)
46
- notebook.add_cell("id", "print(1+1)")
47
- x = notebook.execute()
48
- assert x > 0
49
- assert nbstore.notebook.get_stream(notebook.nb, "id") == "2\n"
50
- assert notebook.execution_needed is False
@@ -1,176 +0,0 @@
1
- import nbformat
2
- import nbstore.notebook
3
- import pytest
4
- from nbstore import Store
5
- from nbstore.markdown import CodeBlock, Image
6
-
7
- from nbsync.cell import Cell
8
- from nbsync.sync import Synchronizer
9
-
10
-
11
- @pytest.fixture(scope="module")
12
- def nb():
13
- nb = nbformat.v4.new_notebook()
14
- for source in [
15
- "# #id\nprint(1+1)",
16
- "# #empty\n",
17
- "# #fig\nimport matplotlib.pyplot as plt\nplt.plot([1])",
18
- "# #func\ndef f():\n pass",
19
- ]:
20
- nb.cells.append(nbformat.v4.new_code_cell(source))
21
- nbstore.notebook.execute(nb)
22
- return nb
23
-
24
-
25
- def test_convert_image(nb):
26
- from nbsync.sync import convert_image
27
-
28
- image = Image("abc", "id", [], {}, "", "a.py")
29
- x = convert_image(image, nb)
30
- assert isinstance(x, Cell)
31
- assert x.image.source == "print(1+1)"
32
- assert x.content == "2\n"
33
- assert x.mime == "text/plain"
34
-
35
-
36
- def test_convert_image_invalid(nb):
37
- from nbsync.sync import convert_image
38
-
39
- image = Image("abc", "invalid", [], {}, "", "a.py")
40
- x = convert_image(image, nb)
41
- assert isinstance(x, Cell)
42
- assert x.image.source == ""
43
- assert x.content == ""
44
- assert x.mime == ""
45
-
46
-
47
- @pytest.fixture(scope="module")
48
- def store(tmp_path_factory: pytest.TempPathFactory) -> Store:
49
- src_dir = tmp_path_factory.mktemp("src")
50
- nb = nbformat.v4.new_notebook()
51
- nb.cells.append(nbformat.v4.new_code_cell("# #id\nprint(1+1)"))
52
- nbformat.write(nb, src_dir.joinpath("a.ipynb"))
53
- return Store(src_dir)
54
-
55
-
56
- def test_update_notebooks(store: Store):
57
- from nbsync.sync import Notebook, update_notebooks
58
-
59
- notebooks: dict[str, Notebook] = {}
60
- update_notebooks(Image("abc", "id", [], {}, "", "a.ipynb"), notebooks, store)
61
- assert len(notebooks) == 1
62
- nb = notebooks["a.ipynb"].nb
63
- assert nbstore.notebook.get_source(nb, "id") == "print(1+1)"
64
-
65
-
66
- def test_update_notebooks_exec(store: Store):
67
- from nbsync.sync import Notebook, update_notebooks
68
-
69
- notebooks: dict[str, Notebook] = {}
70
- image = Image("abc", "id", [], {"exec": "1"}, "", "a.ipynb")
71
- update_notebooks(image, notebooks, store)
72
- assert len(notebooks) == 1
73
- assert notebooks["a.ipynb"].execution_needed
74
-
75
-
76
- def test_update_notebooks_add_cell(store: Store):
77
- from nbsync.sync import Notebook, update_notebooks
78
-
79
- notebooks: dict[str, Notebook] = {}
80
- code_block = CodeBlock("abc", "id2", [], {}, "123", "a.ipynb")
81
- update_notebooks(code_block, notebooks, store)
82
- assert len(notebooks) == 1
83
- notebook = notebooks["a.ipynb"]
84
- assert notebook.execution_needed
85
- assert len(notebook.nb.cells) == 2
86
- assert len(store.read("a.ipynb").cells) == 1
87
-
88
-
89
- def test_update_notebooks_self(store: Store):
90
- from nbsync.sync import Notebook, update_notebooks
91
-
92
- notebooks: dict[str, Notebook] = {}
93
- code_block = CodeBlock("abc", "id2", [], {}, "123", ".md")
94
- update_notebooks(code_block, notebooks, store)
95
- assert len(notebooks) == 1
96
- notebook = notebooks[".md"]
97
- assert len(notebook.nb.cells) == 1
98
-
99
-
100
- def test_update_notebooks_error(store: Store):
101
- from nbsync.sync import Notebook, update_notebooks
102
-
103
- notebooks: dict[str, Notebook] = {}
104
- code_block = CodeBlock("abc", "id2", [], {}, "123", "invalid.md")
105
- update_notebooks(code_block, notebooks, store)
106
- assert len(notebooks) == 0
107
-
108
-
109
- def test_convert_code_block_none():
110
- from nbsync.sync import convert_code_block
111
-
112
- code_block = CodeBlock("abc", "id2", [], {}, "123", "a.ipynb")
113
- assert convert_code_block(code_block) == ""
114
-
115
-
116
- def test_convert_code_block_brace():
117
- from nbsync.sync import convert_code_block
118
-
119
- text = ' ```{.python a#id source="on"}\n a\n ```'
120
- code_block = CodeBlock(text, "id", [], {"source": "on"}, "", "")
121
- x = " ```{.python }\n a\n ```"
122
- assert convert_code_block(code_block) == x
123
-
124
-
125
- def test_convert_code_block_space():
126
- from nbsync.sync import convert_code_block
127
-
128
- text = ' ```.python a#id source="on" abc\n a\n ```'
129
- code_block = CodeBlock(text, "id", [], {"source": "on"}, "", "")
130
- x = " ```.python abc\n a\n ```"
131
- assert convert_code_block(code_block) == x
132
-
133
-
134
- @pytest.fixture
135
- def sync(store: Store):
136
- return Synchronizer(store)
137
-
138
-
139
- def test_sync_str(sync: Synchronizer):
140
- x = list(sync.convert("abc"))
141
- assert x[0] == "abc"
142
-
143
-
144
- def test_sync_image(sync: Synchronizer):
145
- x = next(sync.convert('![](a.ipynb){#id exec="1"}'))
146
- assert isinstance(x, Cell)
147
- assert x.image.source == "print(1+1)"
148
- assert x.content == "2\n"
149
- assert x.mime == "text/plain"
150
- notebook = sync.notebooks["a.ipynb"]
151
- x = next(sync.convert('![](a.ipynb){#id exec="1"}'))
152
- assert isinstance(x, Cell)
153
- assert x.image.source == "print(1+1)"
154
- assert x.content == "2\n"
155
- assert notebook is sync.notebooks["a.ipynb"]
156
- assert notebook.nb is sync.notebooks["a.ipynb"].nb
157
-
158
-
159
- def test_sync_code_block(sync: Synchronizer):
160
- x = next(sync.convert("```a b.md#c source=1\nc\n```"))
161
- assert isinstance(x, str)
162
- assert x == "```a \nc\n```"
163
-
164
-
165
- def test_sync_notebook_not_found():
166
- from nbsync.sync import convert
167
-
168
- image = Image("abc", "id", [], {}, "", "a.ipynb")
169
- assert convert(image, {}) == ""
170
-
171
-
172
- def test_convert_no_id():
173
- from nbsync.sync import convert
174
-
175
- image = Image("abc", ".", [], {}, "", "a.ipynb")
176
- assert convert(image, {}) == ""
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes