Repository: Peter-van-Tol/pydantic-shapely

Issue:
[Bug]: Creation of Geometry objects from other geometry objects not working
### Initial Checks

- [X] I confirm that I'm using Pydantic V2

### Description

Creation of a class containing annotated `MultiPolygon` fails with a list of `Polygon`  with validation error. Hopefully this behavior could be supported by changing validation logic in `GeometryField` 

**Error**
```
Traceback (most recent call last):
  File "/home/username/coding_bits/pydantic_checks/pydantic_checks/check_shapely.py", line 33, in <module>
    obj = MyClass(polys=[Polygon(shell=((0.0, 0.0), (0.0, 1.0), (1.0, 1.0), (1.0, 0.0)))])
          ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "/home/username/coding_bits/pydantic_checks/.venv/lib/python3.11/site-packages/pydantic/_internal/_dataclasses.py", line 134, in __init__
    s.__pydantic_validator__.validate_python(ArgsKwargs(args, kwargs), self_instance=s)
pydantic_core._pydantic_core.ValidationError: 1 validation error for MyClass
polys
  Value error, Supplied string is not a valid WKT-string [type=value_error, input_value=[<POLYGON ((0 0, 0 1, 1 1, 1 0, 0 0))>], input_type=list]
    For further information visit https://errors.pydantic.dev/2.5/v/value_error
```

### Example Code

```Python
from shapely import MultiPolygon, Polygon
from pydantic_shapely import GeometryField
from pydantic.dataclasses import dataclass

@dataclass
class MyClass:
    polys: Annotated[MultiPolygon, GeometryField()]

mpoly = MultiPolygon([Polygon(shell=((0.0, 0.0), (0.0, 1.0), (1.0, 1.0), (1.0, 0.0)))])
assert isinstance(mpoly, MultiPolygon)

obj = MyClass(polys=MultiPolygon([
    (
    ((0.0, 0.0), (0.0, 1.0), (1.0, 1.0), (1.0, 0.0)),
    [((0.1,0.1), (0.1,0.2), (0.2,0.2), (0.2,0.1))]
    )
]))

obj = MyClass(polys=[Polygon(shell=((0.0, 0.0), (0.0, 1.0), (1.0, 1.0), (1.0, 0.0)))])
```
```


### Python, Pydantic, Shapely & OS Version

```Text
Pydantic-shapely version: 1.0.0a2
Shapely version: 2.0.6
             pydantic version: 2.5.3
        pydantic-core version: 2.14.6
          pydantic-core build: profile=release pgo=true
                 install path: /home/username/coding_bits/pydantic_checks/.venv/lib/python3.11/site-packages/pydantic
               python version: 3.11.4 (main, Aug 14 2023, 05:19:15) [GCC 7.5.0]
                     platform: Linux-6.8.0-40-generic-x86_64-with-glibc2.35
             related packages: typing_extensions-4.12.2
```
```


Discussion:

Patch:
diff --git a/src/pydantic_shapely/annotations.py b/src/pydantic_shapely/annotations.py
index e0d18c2..e306c5b 100644
--- a/src/pydantic_shapely/annotations.py
+++ b/src/pydantic_shapely/annotations.py
@@ -90,51 +90,53 @@ class GeometryField:
             ValueError: If the input value is not a valid WKT-string or if the
             supplied geometry is not of the expected type.
         """
-        # Test whether user supplied the geometry directly
+        # - Test whether user supplied the geometry directly
         if isinstance(value, BaseGeometry):
+            geometry = value
+        # - convert a (WKT-) string to a object
+        elif isinstance(value, str):
+            try:
+                geometry: BaseGeometry = shapely.from_wkt(value)
+            except Exception as ex:
+                raise ValueError("Supplied string is not a valid WKT-string") from ex
+        # - last resort, pass the value to the constructor of shapely
+        else:
+            # - get the types that are supported by the field
             if isclass(self.__geometry_type__):
-                # The geometry type is a class, check if the geometry is an instance of the class
-                if isinstance(value, self.__geometry_type__):
-                    return self._validate_z_values(value)
-                raise ValueError(
-                    f"Supplied geometry ({value.geom_type}) is not a "
-                    f"{self.__geometry_type__.__name__}."
-                )
+                supported_types = [self.__geometry_type__]
             else:
-                # The geometry type is a Union, check if the geometry is an instance of any of the
-                # classes
                 supported_types = typing.get_args(self.__geometry_type__)
-                if any(isinstance(value, t) for t in supported_types):
-                    return self._validate_z_values(value)
+            # - for each type, check we can instantiate the geometry with the value
+            #   from the field
+            for t in supported_types:
+                try:
+                    geometry = t(value)
+                    break
+                except Exception:
+                    pass
+            else:
                 raise ValueError(
-                    f"Supplied geometry ({value.geom_type}) is not one of the expected "
-                    f"types: {', '.join([t.__name__ for t in supported_types])}."
+                    f"Supplied value ({value}) cannot be converted to a valid geometry."
                 )
 
-        # Convert the geometry to a point, the geometry should be a valid WKT
-        try:
-            geometry: BaseGeometry = shapely.from_wkt(value)
-        except Exception as ex:
-            raise ValueError("Supplied string is not a valid WKT-string") from ex
         if isclass(self.__geometry_type__):
             # The geometry type is a class, check if the geometry is an instance of the class
-            if not isinstance(geometry, self.__geometry_type__):
-                raise ValueError(
-                    f"Supplied geometry ({geometry.geom_type}) is not a "
-                    f"{self.__geometry_type__.__name__}."
-                )
+            if isinstance(geometry, self.__geometry_type__):
+                return self._validate_z_values(geometry)
+            raise ValueError(
+                f"Supplied geometry ({geometry.geom_type}) is not a "
+                f"{self.__geometry_type__.__name__}."
+            )
         else:
             # The geometry type is a Union, check if the geometry is an instance of any of the
             # classes
             supported_types = typing.get_args(self.__geometry_type__)
-            if not any(isinstance(geometry, t) for t in supported_types):
-                raise ValueError(
-                    f"Supplied geometry ({geometry.geom_type}) is not one of the expected "
-                    f"types: {', '.join([t.__name__ for t in supported_types])}."
-                )
-        # Custom validations
-        geometry = self._validate_z_values(geometry)
-        return geometry
+            if any(isinstance(geometry, t) for t in supported_types):
+                return self._validate_z_values(geometry)
+            raise ValueError(
+                f"Supplied geometry ({geometry.geom_type}) is not one of the expected "
+                f"types: {', '.join([t.__name__ for t in supported_types])}."
+            )
 
     @staticmethod
     def serialize(value) -> str:


Test patch:
diff --git a/tests/test_annotations.py b/tests/test_annotations.py
index 5f7543d..3666261 100644
--- a/tests/test_annotations.py
+++ b/tests/test_annotations.py
@@ -38,6 +38,20 @@ EXAMPLES_WKT = {
     ")",
 }
 
+
+EXAMPLES_BASE = {
+    Point: (10, 20),
+    LineString: ((10, 10), (20, 20), (21, 30)),
+    Polygon: (((0, 0), (0, 40), (40, 40), (40, 0), (0, 0))),
+    MultiPoint: (Point(0, 0), Point(10, 20), Point(15, 20), Point(30, 30)),
+    MultiLineString: (LineString(((10, 10), (20, 20))), LineString(((15, 15), (30, 15)))),
+    MultiPolygon: (
+    Polygon([(10, 10), (10, 20), (20, 20), (20, 15), (10, 10)],),
+    Polygon([(60, 60), (70, 70), (80, 60), (60, 60)],)
+    )
+}
+
+
 EXAMPLES_OBJ_2D = {
     Point: Point(10, 20),
     LineString: LineString([(10, 10), (20, 20), (21, 30)]),
@@ -113,6 +127,12 @@ def test_annotation_correct_geom_type_roundtrip(geom_type):
     instance = model(geometry=EXAMPLES_OBJ_2D[geom_type])
     assert model.model_validate_json(instance.model_dump_json()) == instance
 
+    if geom_type != GeometryCollection:
+        instance = model(geometry=EXAMPLES_BASE[geom_type])
+        assert model.model_validate_json(instance.model_dump_json()) == instance
+        instance = model(geometry=EXAMPLES_WKT[geom_type])
+        assert model.model_validate_json(instance.model_dump_json()) == instance
+
 
 @pytest.mark.parametrize(
     "geom_type",