codecov[bot]: # [Codecov](https://codecov.io/gh/mwouts/jupytext/pull/368?src=pr&el=h1) Report
> Merging [#368](https://codecov.io/gh/mwouts/jupytext/pull/368?src=pr&el=desc) into [master](https://codecov.io/gh/mwouts/jupytext/commit/4d6bef1c6cc78111ce1b3560b07965332c0c951c?src=pr&el=desc) will **decrease** coverage by `0.02%`.
> The diff coverage is `99.29%`.

[![Impacted file tree graph](https://codecov.io/gh/mwouts/jupytext/pull/368/graphs/tree.svg?width=650&token=a1pNjsiILi&height=150&src=pr)](https://codecov.io/gh/mwouts/jupytext/pull/368?src=pr&el=tree)

```diff
@@            Coverage Diff             @@
##           master     #368      +/-   ##
==========================================
- Coverage   99.05%   99.03%   -0.03%     
==========================================
  Files          74       74              
  Lines        7311     7157     -154     
==========================================
- Hits         7242     7088     -154     
  Misses         69       69
```


| [Impacted Files](https://codecov.io/gh/mwouts/jupytext/pull/368?src=pr&el=tree) | Coverage Δ | |
|---|---|---|
| [tests/test\_compare.py](https://codecov.io/gh/mwouts/jupytext/pull/368/diff?src=pr&el=tree#diff-dGVzdHMvdGVzdF9jb21wYXJlLnB5) | `100% <ø> (ø)` | :arrow_up: |
| [tests/test\_cli.py](https://codecov.io/gh/mwouts/jupytext/pull/368/diff?src=pr&el=tree#diff-dGVzdHMvdGVzdF9jbGkucHk=) | `100% <100%> (ø)` | :arrow_up: |
| [tests/test\_read\_simple\_rmd.py](https://codecov.io/gh/mwouts/jupytext/pull/368/diff?src=pr&el=tree#diff-dGVzdHMvdGVzdF9yZWFkX3NpbXBsZV9ybWQucHk=) | `100% <100%> (ø)` | :arrow_up: |
| [tests/test\_escape\_magics.py](https://codecov.io/gh/mwouts/jupytext/pull/368/diff?src=pr&el=tree#diff-dGVzdHMvdGVzdF9lc2NhcGVfbWFnaWNzLnB5) | `100% <100%> (ø)` | :arrow_up: |
| [tests/test\_black.py](https://codecov.io/gh/mwouts/jupytext/pull/368/diff?src=pr&el=tree#diff-dGVzdHMvdGVzdF9ibGFjay5weQ==) | `100% <100%> (ø)` | :arrow_up: |
| [tests/test\_read\_simple\_markdown.py](https://codecov.io/gh/mwouts/jupytext/pull/368/diff?src=pr&el=tree#diff-dGVzdHMvdGVzdF9yZWFkX3NpbXBsZV9tYXJrZG93bi5weQ==) | `100% <100%> (ø)` | :arrow_up: |
| [tests/test\_header.py](https://codecov.io/gh/mwouts/jupytext/pull/368/diff?src=pr&el=tree#diff-dGVzdHMvdGVzdF9oZWFkZXIucHk=) | `100% <100%> (ø)` | :arrow_up: |
| [tests/test\_active\_cells.py](https://codecov.io/gh/mwouts/jupytext/pull/368/diff?src=pr&el=tree#diff-dGVzdHMvdGVzdF9hY3RpdmVfY2VsbHMucHk=) | `100% <100%> (ø)` | :arrow_up: |
| [tests/test\_read\_simple\_julia.py](https://codecov.io/gh/mwouts/jupytext/pull/368/diff?src=pr&el=tree#diff-dGVzdHMvdGVzdF9yZWFkX3NpbXBsZV9qdWxpYS5weQ==) | `100% <100%> (ø)` | :arrow_up: |
| [tests/test\_rmd\_to\_ipynb.py](https://codecov.io/gh/mwouts/jupytext/pull/368/diff?src=pr&el=tree#diff-dGVzdHMvdGVzdF9ybWRfdG9faXB5bmIucHk=) | `100% <100%> (ø)` | :arrow_up: |
| ... and [49 more](https://codecov.io/gh/mwouts/jupytext/pull/368/diff?src=pr&el=tree-more) | |

------

[Continue to review full report at Codecov](https://codecov.io/gh/mwouts/jupytext/pull/368?src=pr&el=continue).
> **Legend** - [Click here to learn more](https://docs.codecov.io/docs/codecov-delta)
> `Δ = absolute <relative> (impact)`, `ø = not affected`, `? = missing data`
> Powered by [Codecov](https://codecov.io/gh/mwouts/jupytext/pull/368?src=pr&el=footer). Last update [4d6bef1...c4d7477](https://codecov.io/gh/mwouts/jupytext/pull/368?src=pr&el=lastupdated). Read the [comment docs](https://docs.codecov.io/docs/pull-request-comments).

diff --git a/HISTORY.rst b/HISTORY.rst
index 9508d62..2cc996b 100644
--- a/HISTORY.rst
+++ b/HISTORY.rst
@@ -28,6 +28,7 @@ Release History
 - Fixed an inconsistent round trip (code cell with ``"cat"`` being converted to a markdown cell) in the ``py:light`` format (#339)
 - ``jupytext --test textfile.ext`` now really compares the text file to its round trip (rather than the corresponding notebook) (#339)
 - Markdown cells that contain code are now preserved in a round trip through the Markdown and R Markdown formats (#361)
+- Code cells with a ``%%python3`` cell magic are now preserved in a round trip through the Markdown format (#365)
 
 
 1.2.4 (2019-09-19)
diff --git a/jupytext/cell_reader.py b/jupytext/cell_reader.py
index 307a966..b8154ae 100644
--- a/jupytext/cell_reader.py
+++ b/jupytext/cell_reader.py
@@ -262,7 +262,7 @@ class BaseCellReader(object):
 class MarkdownCellReader(BaseCellReader):
     """Read notebook cells from Markdown documents"""
     comment = ''
-    start_code_re = re.compile(r"^```({})(.*)".format('|'.join(
+    start_code_re = re.compile(r"^```({})($|\s(.*)$)".format('|'.join(
         _JUPYTER_LANGUAGES.union({str.upper(lang) for lang in _JUPYTER_LANGUAGES})).replace('+', '\\+')))
     non_jupyter_code_re = re.compile(r"^```")
     end_code_re = re.compile(r"^```\s*$")

.md round-tripping Broken on %%python3 magic
Just noticed this in a notebook that uses `%%python3` magic to demonstrate code execution in separate cells.

If I'm editing a markdown file in Jupyter notebook UI under Jupytext, if I have a code cell:

```
%%python3
a = 1
print(a)
```

if I save the file and then reopen it, the `%%python3` block cell magic has been stripped.
diff --git a/tests/test_read_simple_markdown.py b/tests/test_read_simple_markdown.py
index 7a074b8..8e9c38e 100644
--- a/tests/test_read_simple_markdown.py
+++ b/tests/test_read_simple_markdown.py
@@ -590,3 +590,34 @@ def test_two_markdown_cell_with_no_language_code_works(nb=new_notebook(cells=[
     text = jupytext.writes(nb, 'md')
     nb2 = jupytext.reads(text, 'md')
     compare_notebooks(nb2, nb)
+
+
+def test_notebook_with_python3_magic(no_jupytext_version_number,
+                                     nb=new_notebook(metadata={
+                                         'kernelspec': {'display_name': 'Python 3', 'language': 'python',
+                                                        'name': 'python3'}},
+                                                     cells=[new_code_cell('%%python2\na = 1\nprint a'),
+                                                            new_code_cell('%%python3\nb = 2\nprint(b)')]),
+                                     text="""---
+jupyter:
+  kernelspec:
+    display_name: Python 3
+    language: python
+    name: python3
+---
+
+```python2
+a = 1
+print a
+```
+
+```python3
+b = 2
+print(b)
+```
+"""):
+    md = jupytext.writes(nb, 'md')
+    compare(md, text)
+
+    nb2 = jupytext.reads(md, 'md')
+    compare_notebooks(nb2, nb)

pip install -e .[dev]
parse_log_pytest
requirements.txt
pytest-xdist pytest
apt-get update apt-get install -y gcc
3.8
requirements.txt
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
attrs==25.3.0
exceptiongroup==1.2.2
execnet==2.1.1
fastjsonschema==2.21.1
importlib_resources==6.4.5
iniconfig==2.1.0
jsonschema==4.23.0
jsonschema-specifications==2023.12.1
jupyter_core==5.7.2
-e git+https://github.com/mwouts/jupytext.git@4d6bef1c6cc78111ce1b3560b07965332c0c951c#egg=jupytext
nbformat==5.10.4
packaging==24.2
pkgutil_resolve_name==1.3.10
platformdirs==4.3.6
pluggy==1.5.0
pytest==8.3.5
pytest-xdist==3.6.1
PyYAML==6.0.2
referencing==0.35.1
rpds-py==0.20.1
tomli==2.2.1
traitlets==5.14.3
zipp==3.20.2

name: jupytext
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.4.4=h6a678d5_1
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=3.0.16=h5eee18b_0
  - pip=24.2=py38h06a4308_0
  - python=3.8.20=he870216_0
  - readline=8.2=h5eee18b_0
  - setuptools=75.1.0=py38h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - wheel=0.44.0=py38h06a4308_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - attrs==25.3.0
      - exceptiongroup==1.2.2
      - execnet==2.1.1
      - fastjsonschema==2.21.1
      - importlib-resources==6.4.5
      - iniconfig==2.1.0
      - jsonschema==4.23.0
      - jsonschema-specifications==2023.12.1
      - jupyter-core==5.7.2
      - nbformat==5.10.4
      - packaging==24.2
      - pkgutil-resolve-name==1.3.10
      - platformdirs==4.3.6
      - pluggy==1.5.0
      - pytest==8.3.5
      - pytest-xdist==3.6.1
      - pyyaml==6.0.2
      - referencing==0.35.1
      - rpds-py==0.20.1
      - tomli==2.2.1
      - traitlets==5.14.3
      - zipp==3.20.2
prefix: /opt/conda/envs/jupytext

tests/test_read_simple_markdown.py::test_notebook_with_python3_magic
tests/test_read_simple_markdown.py::test_read_mostly_py_markdown_file tests/test_read_simple_markdown.py::test_read_md_and_markdown_regions tests/test_read_simple_markdown.py::test_read_mostly_R_markdown_file tests/test_read_simple_markdown.py::test_code_cell_with_metadata tests/test_read_simple_markdown.py::test_raw_cell_with_metadata_json tests/test_read_simple_markdown.py::test_raw_cell_with_metadata tests/test_read_simple_markdown.py::test_read_raw_cell_markdown_version_1_1 tests/test_read_simple_markdown.py::test_read_raw_cell_markdown_version_1_1_with_mimetype tests/test_read_simple_markdown.py::test_markdown_cell_with_metadata_json tests/test_read_simple_markdown.py::test_markdown_cell_with_metadata tests/test_read_simple_markdown.py::test_two_markdown_cells
tests/test_read_simple_markdown.py::test_read_markdown_file_no_language tests/test_read_simple_markdown.py::test_read_julia_notebook tests/test_read_simple_markdown.py::test_split_on_header tests/test_read_simple_markdown.py::test_split_on_header_after_two_blank_lines tests/test_read_simple_markdown.py::test_combine_md_version_one tests/test_read_simple_markdown.py::test_jupyter_cell_is_not_split tests/test_read_simple_markdown.py::test_indented_code_is_not_split tests/test_read_simple_markdown.py::test_non_jupyter_code_is_not_split tests/test_read_simple_markdown.py::test_read_markdown_idl tests/test_read_simple_markdown.py::test_read_markdown_IDL tests/test_read_simple_markdown.py::test_inactive_cell tests/test_read_simple_markdown.py::test_inactive_cell_using_tag tests/test_read_simple_markdown.py::test_inactive_cell_using_noeval tests/test_read_simple_markdown.py::test_noeval_followed_by_code_works tests/test_read_simple_markdown.py::test_markdown_cell_with_code_works tests/test_read_simple_markdown.py::test_markdown_cell_with_noeval_code_works tests/test_read_simple_markdown.py::test_two_markdown_cell_with_code_works tests/test_read_simple_markdown.py::test_two_markdown_cell_with_no_language_code_works
HISTORY.rst jupytext/cell_reader.py
HISTORY.rst jupytext/cell_reader.py