Repository: python/mypy
Pull request: Add exist_ok=True in write_junit_xml
 Fixes #16630



Problem statement:
Generate report (mypy.xml) fails when running mypy in parallel with another job
**Bug Report**

In `pyproject.toml` I've configured `mypy` and `coverage` to generate their reports in the same directory: `reports`. In Gitlab CI I'm running 2 jobs in parallel to run `mypy` and `coverage`. Sometimes the `mypy` jobs fails.

**To Reproduce**

Run `mypy` and `coverage` in parallel in Gitlab CI.

**Expected Behavior**

Always generate the report in `reports/mypy.xml`. 

**Actual Behavior**

```bash
Traceback (most recent call last):
  File "/opt/my-package-env/bin/mypy", line 8, in <module>
    sys.exit(console_entry())
             ^^^^^^^^^^^^^^^
  File "/opt/my-package-env/lib/python3.11/site-packages/mypy/__main__.py", line 15, in console_entry
    main()
  File "mypy/main.py", line 99, in main
  File "mypy/main.py", line 203, in run_build
  File "mypy/main.py", line 1489, in maybe_write_junit_xml
  File "mypy/util.py", line 280, in write_junit_xml
  File "<frozen os>", line 225, in makedirs
FileExistsError: [Errno 17] File exists: '/builds/my-package/reports'
Success: no issues found in 10 source files
Error: Sequence aborted after failed subtask 'lint[0]' 
```

**Your Environment**

- Mypy version used: 1.7.0
- Mypy command-line flags: None
- Mypy configuration options from `mypy.ini` (and other config files):
- Python version used: 3.11.6

```toml
[tool.mypy]
junit_xml = "reports/mypy.xml"
plugins = "pydantic.mypy"
strict = true
disallow_subclassing_any = false
disallow_untyped_decorators = false
explicit_package_bases = true
ignore_missing_imports = true
pretty = true
show_column_numbers = true
show_error_codes = true
show_error_context = true
warn_unreachable = true
mypy_path = "src"

[tool.pydantic-mypy]
init_forbid_extra = true
init_typed = true
warn_required_dynamic_aliases = true
warn_untyped_fields = true
```

Hints from issue discussion:
A possible solution can be to change the following in the function `write_junit_xml`: 
```python
os.makedirs(xml_dirs)
```
to
```python
os.makedirs(xml_dirs, exist_ok=True)
```


https://github.com/python/mypy/blame/master/mypy/util.py#L320
Your fix makes sense, feel free to submit a PR.
Thanks. Will make a PR tomorrow.

Commit message: Add exist_ok=True in write_junit_xml

Patch to review:
diff --git a/mypy/util.py b/mypy/util.py
index 7a13de427e8e..bd642249400b 100644
--- a/mypy/util.py
+++ b/mypy/util.py
@@ -317,7 +317,7 @@ def write_junit_xml(
     # checks for a directory structure in path and creates folders if needed
     xml_dirs = os.path.dirname(os.path.abspath(path))
     if not os.path.isdir(xml_dirs):
-        os.makedirs(xml_dirs)
+        os.makedirs(xml_dirs, exist_ok=True)
 
     with open(path, "wb") as f:
         f.write(xml.encode("utf-8"))


Code review comments:
diff_hunk: @@ -317,7 +317,7 @@ def write_junit_xml(
     # checks for a directory structure in path and creates folders if needed
     xml_dirs = os.path.dirname(os.path.abspath(path))
     if not os.path.isdir(xml_dirs):
path: mypy/util.py
text: @user1:
Let's remove this condition as it's now redundant.

@author:
True, did remove it.