Repository: beeware/toga

Issue:
`toga.Image()` doesn't accept `None` as a value for `src`
### Describe the bug

`toga.Image()` doesn't accept `None` as a value for `src`, but according to the docs and code: https://github.com/beeware/toga/blob/61dff563d82da71bd2470cde27882731113eda2d/core/src/toga/images.py#L42C9-L42C41 it should accept `None` as a value.

### Steps to reproduce

1. Clone latest toga/main branch
2. `pip install -e ./core -e ./dummy -e ./gtk`
3. Create a new briefcase project
4. Use the following `app.py` script:
```
"""
My first application
"""
import toga
from toga.style import Pack
from toga.style.pack import COLUMN, ROW


class HelloWorld(toga.App):

    def startup(self):
        """
        Construct and show the Toga application.

        Usually, you would add your application to a main content box.
        We then create a main window (with a name matching the app), and
        show the main window.
        """
        main_box = toga.Box()
        img = toga.Image(src=None)
        self.main_window = toga.MainWindow(title=self.formal_name)
        self.main_window.content = main_box
        self.main_window.show()


def main():
    return HelloWorld()
```

5. `briefcase dev`
6. Error encountered:
```

[helloworld] Starting in dev mode...
===========================================================================
Traceback (most recent call last):
  File "/home/proneon267/Pictures/venv/lib/python3.11/site-packages/toga_gtk/app.py", line 87, in gtk_startup
    self.interface._startup()
  File "/home/proneon267/Pictures/venv/lib/python3.11/site-packages/toga/app.py", line 629, in _startup
    self.startup()
  File "/home/proneon267/Pictures/toga/helloworld/src/helloworld/app.py", line 20, in startup
    img = toga.Image(src=None)
          ^^^^^^^^^^^^^^^^^^^^
  File "/home/proneon267/Pictures/venv/lib/python3.11/site-packages/toga/images.py", line 63, in __init__
    raise TypeError(
TypeError: Image.__init__() missing 1 required positional argument: 'src'
```


### Expected behavior

`toga.Image()` should accept `None` as a value for `src`

### Screenshots

_No response_

### Environment

- Operating System: Arch
- Python version: 3.11.6
- Software versions:
  - Briefcase: 0.3.16
  - Toga: latest
  - ...


### Logs

```
No log files were generated.
```


### Additional context

_No response_


Discussion:
Thanks for the report; however, I think it's slightly mistaken. I believe this is a case where type declarations and backwards compatibility are in conflict, and the error message could be *better*, but the behavior you're seeing is fundamentally consistent with the intended behavior.

The docs say that an Image can have "any valid ImageContent type". None is not a valid ImageContent type. Conceptually, a "None image" doesn't make any sense, either. So - an exception in the case of `toga.Image(src=None)` is correct.

The type declaration allows None because it is needed for the purposes of backwards compatibility (the `toga.Image(path="...")` use case, which was legal in Toga 0.3). Until we remove that deprecation path, it's not possible to modify the type declaration. 

The error that is generated in this case could be a better - it should be a `TypeError("Unsupported source type for Image")`, reflecting what the failure will be when the backwards compatibility path is removed; I'll leave the ticket open to track that change. I've been wanting to add some documentation to clarify the difference between Image and Icon, so this is as good a trigger for me to make that change.
Understood. Thanks :)

Resolving patch:
diff --git a/core/src/toga/images.py b/core/src/toga/images.py
--- a/core/src/toga/images.py
+++ b/core/src/toga/images.py
@@ -36,13 +36,16 @@
     ImageContent: TypeAlias = PathLike | BytesLike | ImageLike
 
 
+NOT_PROVIDED = object()
+
+
 class Image:
     def __init__(
         self,
-        src: ImageContent | None = None,
+        src: ImageContent = NOT_PROVIDED,
         *,
-        path=None,  # DEPRECATED
-        data=None,  # DEPRECATED
+        path=NOT_PROVIDED,  # DEPRECATED
+        data=NOT_PROVIDED,  # DEPRECATED
     ):
         """Create a new image.
 
@@ -56,21 +59,21 @@ def __init__(
         ######################################################################
         # 2023-11: Backwards compatibility
         ######################################################################
-        num_provided = sum(arg is not None for arg in (src, path, data))
+        num_provided = sum(arg is not NOT_PROVIDED for arg in (src, path, data))
         if num_provided > 1:
             raise ValueError("Received multiple arguments to constructor.")
         if num_provided == 0:
             raise TypeError(
                 "Image.__init__() missing 1 required positional argument: 'src'"
             )
-        if path is not None:
+        if path is not NOT_PROVIDED:
             src = path
             warn(
                 "Path argument is deprecated, use src instead.",
                 DeprecationWarning,
                 stacklevel=2,
             )
-        elif data is not None:
+        elif data is not NOT_PROVIDED:
             src = data
             warn(
                 "Data argument is deprecated, use src instead.",


Test patch:
diff --git a/core/tests/test_images.py b/core/tests/test_images.py
--- a/core/tests/test_images.py
+++ b/core/tests/test_images.py
@@ -122,14 +122,33 @@ def test_create_from_raw():
     assert_action_performed_with(copy, "load image from raw")
 
 
-def test_not_enough_arguments():
+def test_no_source():
+    """If no source is provided, an error is raised"""
     with pytest.raises(
         TypeError,
         match=r"Image.__init__\(\) missing 1 required positional argument: 'src'",
+    ):
+        toga.Image()
+
+
+def test_empty_image():
+    """If the image source is provided as None, an error is raised"""
+    with pytest.raises(
+        TypeError,
+        match=r"Unsupported source type for Image",
     ):
         toga.Image(None)
 
 
+def test_empty_image_explicit():
+    """If src is explicitly provided as None, an error is raised"""
+    with pytest.raises(
+        TypeError,
+        match=r"Unsupported source type for Image",
+    ):
+        toga.Image(src=None)
+
+
 def test_invalid_input_format():
     """Trying to create an image with an invalid input should raise an error"""
     with pytest.raises(