Browse Source

avoid implicit dependency on pillow

Falko Schindler 1 year ago
parent
commit
f559257452

+ 11 - 6
nicegui/elements/image.py

@@ -4,15 +4,20 @@ import time
 from pathlib import Path
 from typing import Union
 
-from PIL.Image import Image as PIL_Image
-
+from .. import optional_features
 from .mixins.source_element import SourceElement
 
+try:
+    from PIL.Image import Image as PIL_Image
+    optional_features.register('pillow')
+except ImportError:
+    pass
+
 
 class Image(SourceElement, component='image.js'):
     PIL_CONVERT_FORMAT = 'PNG'
 
-    def __init__(self, source: Union[str, Path, PIL_Image] = '') -> None:
+    def __init__(self, source: Union[str, Path, 'PIL_Image'] = '') -> None:
         """Image
 
         Displays an image.
@@ -22,8 +27,8 @@ class Image(SourceElement, component='image.js'):
         """
         super().__init__(source=source)
 
-    def _set_props(self, source: Union[str, Path]) -> None:
-        if isinstance(source, PIL_Image):
+    def _set_props(self, source: Union[str, Path, 'PIL_Image']) -> None:
+        if optional_features.has('pillow') and isinstance(source, PIL_Image):
             source = pil_to_base64(source, self.PIL_CONVERT_FORMAT)
         super()._set_props(source)
 
@@ -33,7 +38,7 @@ class Image(SourceElement, component='image.js'):
         self.update()
 
 
-def pil_to_base64(pil_image: PIL_Image, image_format: str) -> str:
+def pil_to_base64(pil_image: 'PIL_Image', image_format: str) -> str:
     """Convert a PIL image to a base64 string which can be used as image source.
 
     :param pil_image: the PIL image

+ 10 - 5
nicegui/elements/interactive_image.py

@@ -4,20 +4,25 @@ import time
 from pathlib import Path
 from typing import Any, Callable, List, Optional, Union, cast
 
-from PIL.Image import Image as PIL_Image
-
+from .. import optional_features
 from ..events import GenericEventArguments, MouseEventArguments, handle_event
 from .image import pil_to_base64
 from .mixins.content_element import ContentElement
 from .mixins.source_element import SourceElement
 
+try:
+    from PIL.Image import Image as PIL_Image
+    optional_features.register('pillow')
+except ImportError:
+    pass
+
 
 class InteractiveImage(SourceElement, ContentElement, component='interactive_image.js'):
     CONTENT_PROP = 'content'
     PIL_CONVERT_FORMAT = 'PNG'
 
     def __init__(self,
-                 source: Union[str, Path] = '', *,
+                 source: Union[str, Path, 'PIL_Image'] = '', *,
                  content: str = '',
                  on_mouse: Optional[Callable[..., Any]] = None,
                  events: List[str] = ['click'],
@@ -61,8 +66,8 @@ class InteractiveImage(SourceElement, ContentElement, component='interactive_ima
             handle_event(on_mouse, arguments)
         self.on('mouse', handle_mouse)
 
-    def _set_props(self, source: Union[str, Path]) -> None:
-        if isinstance(source, PIL_Image):
+    def _set_props(self, source: Union[str, Path, 'PIL_Image']) -> None:
+        if optional_features.has('pillow') and isinstance(source, PIL_Image):
             source = pil_to_base64(source, self.PIL_CONVERT_FORMAT)
         super()._set_props(source)
 

+ 2 - 2
nicegui/native/native_mode.py

@@ -21,7 +21,7 @@ try:
         # webview depends on bottle which uses the deprecated CGI function (https://github.com/bottlepy/bottle/issues/1403)
         warnings.filterwarnings('ignore', category=DeprecationWarning)
         import webview
-    optional_features.register('native')
+    optional_features.register('webview')
 except ModuleNotFoundError:
     pass
 
@@ -104,7 +104,7 @@ def activate(host: str, port: int, title: str, width: int, height: int, fullscre
             time.sleep(0.1)
         _thread.interrupt_main()
 
-    if not optional_features.has('native'):
+    if not optional_features.has('webview'):
         log.error('Native mode is not supported in this configuration.\n'
                   'Please run "pip install pywebview" to use it.')
         sys.exit(1)

+ 12 - 3
nicegui/optional_features.py

@@ -1,13 +1,22 @@
-from typing import Set
+from typing import Literal, Set
 
 _optional_features: Set[str] = set()
 
+FEATURE = Literal[
+    'highcharts',
+    'matplotlib',
+    'pandas',
+    'pillow',
+    'plotly',
+    'webview',
+]
 
-def register(feature: str) -> None:
+
+def register(feature: FEATURE) -> None:
     """Register an optional feature."""
     _optional_features.add(feature)
 
 
-def has(feature: str) -> bool:
+def has(feature: FEATURE) -> bool:
     """Check if an optional feature is registered."""
     return feature in _optional_features