Repository: psf/black

Issue:
Black as a library import should check for running event loop before installing
https://github.com/psf/black/blob/f2a3fee15c3bc0f4068b2b2fb64f255b417cb4ef/src/black/__init__.py#L57-L63

See comments I left here https://github.com/psf/black/commit/754eecf69eed0bdc75fe224c19a702a4f0676807#r51686621

# what is the issue?

We are using the black library in a system that already had an eventloop set. Importing black the library now clears the event loop settings we had. Impact to us is that we cannot use this latest version of black. 

# Suggestion

Black the library should not assume that it can create its own event loop -- instead it should check to see if a running event loop exists before calling `uvloop.install()`.  

Actually, I'd go further to ask why the library should be thinking about controlling an eventloop in the first place? Much like configuring loggers should not be set in libraries, event loops should not be set in libraries either?



Discussion:
This also caused problems for us, though I haven't had time to investigate exactly what happened yet.

I suggest we should do `uvloop.install` only when Black is invoked from the command line, perhaps in `patched_main()`. This prevents side effects when Black is imported as a library.

Resolving patch:
diff --git a/src/black/__init__.py b/src/black/__init__.py
--- a/src/black/__init__.py
+++ b/src/black/__init__.py
@@ -38,7 +38,7 @@
 from black.mode import Mode, TargetVersion
 from black.mode import Feature, supports_feature, VERSION_TO_FEATURES
 from black.cache import read_cache, write_cache, get_cache_info, filter_cached, Cache
-from black.concurrency import cancel, shutdown
+from black.concurrency import cancel, shutdown, maybe_install_uvloop
 from black.output import dump_to_file, diff, color_diff, out, err
 from black.report import Report, Changed
 from black.files import find_project_root, find_pyproject_toml, parse_pyproject_toml
@@ -54,14 +54,6 @@
 
 from _black_version import version as __version__
 
-# If our environment has uvloop installed lets use it
-try:
-    import uvloop
-
-    uvloop.install()
-except ImportError:
-    pass
-
 # types
 FileContent = str
 Encoding = str
@@ -1112,6 +1104,7 @@ def patch_click() -> None:
 
 
 def patched_main() -> None:
+    maybe_install_uvloop()
     freeze_support()
     patch_click()
     main()
diff --git a/src/black/concurrency.py b/src/black/concurrency.py
--- a/src/black/concurrency.py
+++ b/src/black/concurrency.py
@@ -6,6 +6,21 @@
 from black.output import err
 
 
+def maybe_install_uvloop() -> None:
+    """If our environment has uvloop installed we use it.
+
+    This is called only from command-line entry points to avoid
+    interfering with the parent process if Black is used as a library.
+
+    """
+    try:
+        import uvloop
+
+        uvloop.install()
+    except ImportError:
+        pass
+
+
 def cancel(tasks: Iterable["asyncio.Task[Any]"]) -> None:
     """asyncio signal handler that cancels all `tasks` and reports to stderr."""
     err("Aborted!")
diff --git a/src/blackd/__init__.py b/src/blackd/__init__.py
--- a/src/blackd/__init__.py
+++ b/src/blackd/__init__.py
@@ -20,16 +20,9 @@
     sys.exit(-1)
 
 import black
+from black.concurrency import maybe_install_uvloop
 import click
 
-# If our environment has uvloop installed lets use it
-try:
-    import uvloop
-
-    uvloop.install()
-except ImportError:
-    pass
-
 from _black_version import version as __version__
 
 # This is used internally by tests to shut down the server prematurely
@@ -210,6 +203,7 @@ def parse_python_variant_header(value: str) -> Tuple[bool, Set[black.TargetVersi
 
 
 def patched_main() -> None:
+    maybe_install_uvloop()
     freeze_support()
     black.patch_click()
     main()


Test patch: