Repository: koxudaxi/datamodel-code-generator

Issue:
msgspec output uses regex instead of pattern to Meta constraint
Strings with a pattern constraint are output as `Meta(regex='....')` however they should be output as `Meta(pattern='...')`

**To Reproduce**

Example schema:
```yaml
openapi: "3.0.0"
info:
  version: 1.0.0
  title: Swagger Petstore
  license:
    name: MIT
servers:
  - url: http://petstore.swagger.io/v1
components:
  schemas:
    info:
      type: object
      properties:
        hostName:
          type: string
          format: hostname
        arn:
          type: string
          pattern: '(^arn:([^:]*):([^:]*):([^:]*):(|\*|[\d]{12}):(.+)$)|^\*$'
```

Used commandline:
```bash
$ datamodel-codegen --input pattern.yaml --output output_file.py --input-file-type 'openapi' \
                --target-python '3.9' \
                --output-model-type msgspec.Struct
```

**Expected behavior**
The regex argument to the Meta annotation should be `pattern`

```python
class Info(Struct):
    hostName: Optional[str] = None
    arn: Optional[
        Annotated[
            str,
            Meta(pattern='(^arn:([^:]*):([^:]*):([^:]*):(|\\*|[\\d]{12}):(.+)$)|^\\*$'),
        ]
    ] = None
```

**Version:**
 - OS: macOS
 - Python version: 3.11.4
 - datamodel-code-generator version: 0.22



Discussion:

Resolving patch:
diff --git a/datamodel_code_generator/model/msgspec.py b/datamodel_code_generator/model/msgspec.py
--- a/datamodel_code_generator/model/msgspec.py
+++ b/datamodel_code_generator/model/msgspec.py
@@ -1,6 +1,8 @@
 from pathlib import Path
 from typing import Any, ClassVar, DefaultDict, Dict, List, Optional, Set, Tuple
 
+from pydantic import Field
+
 from datamodel_code_generator.imports import Import
 from datamodel_code_generator.model import DataModel, DataModelFieldBase
 from datamodel_code_generator.model.base import UNDEFINED
@@ -10,10 +12,12 @@
     IMPORT_MSGSPEC_META,
     IMPORT_MSGSPEC_STRUCT,
 )
-from datamodel_code_generator.model.pydantic.base_model import Constraints
+from datamodel_code_generator.model.pydantic.base_model import (
+    Constraints as _Constraints,
+)
 from datamodel_code_generator.model.rootmodel import RootModel
 from datamodel_code_generator.reference import Reference
-from datamodel_code_generator.types import chain_as_tuple
+from datamodel_code_generator.types import chain_as_tuple, get_optional_type
 
 
 def _has_field_assignment(field: DataModelFieldBase) -> bool:
@@ -71,6 +75,12 @@ def imports(self) -> Tuple[Import, ...]:
         return chain_as_tuple(super().imports, extra_imports)
 
 
+class Constraints(_Constraints):
+    # To override existing pattern alias
+    regex: Optional[str] = Field(None, alias='regex')
+    pattern: Optional[str] = Field(None, alias='pattern')
+
+
 class DataModelField(DataModelFieldBase):
     _FIELD_KEYS: ClassVar[Set[str]] = {
         'default',
@@ -88,7 +98,7 @@ class DataModelField(DataModelFieldBase):
         # 'max_items', # not supported by msgspec
         'min_length',
         'max_length',
-        'regex',
+        'pattern',
         # 'unique_items', # not supported by msgspec
     }
     _PARSE_METHOD = 'convert'
@@ -197,6 +207,10 @@ def annotated(self) -> Optional[str]:
 
         meta = f'Meta({", ".join(meta_arguments)})'
 
+        if not self.required:
+            type_hint = self.data_type.type_hint
+            annotated_type = f'Annotated[{type_hint}, {meta}]'
+            return get_optional_type(annotated_type, self.data_type.use_union_operator)
         return f'Annotated[{self.type_hint}, {meta}]'
 
     def _get_default_as_struct_model(self) -> Optional[str]:


Test patch:
diff --git a/tests/data/expected/main/main_msgspec_struct/output.py b/tests/data/expected/main/main_msgspec_struct/output.py
--- a/tests/data/expected/main/main_msgspec_struct/output.py
+++ b/tests/data/expected/main/main_msgspec_struct/output.py
@@ -40,17 +40,17 @@ class Error(Struct):
 
 
 class Api(Struct):
-    apiKey: Annotated[
-        Optional[str], Meta(description='To be used as a dataset parameter value')
+    apiKey: Optional[
+        Annotated[str, Meta(description='To be used as a dataset parameter value')]
     ] = None
-    apiVersionNumber: Annotated[
-        Optional[str], Meta(description='To be used as a version parameter value')
+    apiVersionNumber: Optional[
+        Annotated[str, Meta(description='To be used as a version parameter value')]
     ] = None
-    apiUrl: Annotated[
-        Optional[str], Meta(description="The URL describing the dataset's fields")
+    apiUrl: Optional[
+        Annotated[str, Meta(description="The URL describing the dataset's fields")]
     ] = None
-    apiDocumentationUrl: Annotated[
-        Optional[str], Meta(description='A URL to the API console for each API')
+    apiDocumentationUrl: Optional[
+        Annotated[str, Meta(description='A URL to the API console for each API')]
     ] = None
 
 
diff --git a/tests/data/expected/main/main_pattern_msgspec/output.py b/tests/data/expected/main/main_pattern_msgspec/output.py
new file mode 100644
--- /dev/null
+++ b/tests/data/expected/main/main_pattern_msgspec/output.py
@@ -0,0 +1,25 @@
+# generated by datamodel-codegen:
+#   filename:  pattern.json
+#   timestamp: 2019-07-26T00:00:00+00:00
+
+from __future__ import annotations
+
+from typing import Annotated, Optional
+
+from msgspec import Meta, Struct
+
+
+class Info(Struct):
+    hostName: Optional[str] = None
+    arn: Optional[
+        Annotated[
+            str,
+            Meta(pattern='(^arn:([^:]*):([^:]*):([^:]*):(|\\*|[\\d]{12}):(.+)$)|^\\*$'),
+        ]
+    ] = None
+    tel: Optional[
+        Annotated[str, Meta(pattern='^(\\([0-9]{3}\\))?[0-9]{3}-[0-9]{4}$')]
+    ] = None
+    comment: Optional[
+        Annotated[str, Meta(pattern='[^\\b\\f\\n\\r\\t\\\\a+.?\'"|()]+$')]
+    ] = None
diff --git a/tests/data/expected/main/main_use_annotated_with_msgspec_meta_constraints/output.py b/tests/data/expected/main/main_use_annotated_with_msgspec_meta_constraints/output.py
--- a/tests/data/expected/main/main_use_annotated_with_msgspec_meta_constraints/output.py
+++ b/tests/data/expected/main/main_use_annotated_with_msgspec_meta_constraints/output.py
@@ -12,7 +12,7 @@
 class Pet(Struct):
     id: Annotated[int, Meta(ge=0, le=9223372036854775807)]
     name: Annotated[str, Meta(max_length=256)]
-    tag: Annotated[Optional[str], Meta(max_length=64)] = None
+    tag: Optional[Annotated[str, Meta(max_length=64)]] = None
 
 
 Pets = List[Pet]
@@ -31,13 +31,13 @@ class User(Struct):
     id: Annotated[int, Meta(ge=0)]
     name: Annotated[str, Meta(max_length=256)]
     uid: UID
-    tag: Annotated[Optional[str], Meta(max_length=64)] = None
+    tag: Optional[Annotated[str, Meta(max_length=64)]] = None
     phones: Optional[List[Phone]] = None
     fax: Optional[List[FaxItem]] = None
-    height: Annotated[Optional[Union[int, float]], Meta(ge=1.0, le=300.0)] = None
-    weight: Annotated[Optional[Union[float, int]], Meta(ge=1.0, le=1000.0)] = None
-    age: Annotated[Optional[int], Meta(gt=0, le=200)] = None
-    rating: Annotated[Optional[float], Meta(gt=0.0, le=5.0)] = None
+    height: Optional[Annotated[Union[int, float], Meta(ge=1.0, le=300.0)]] = None
+    weight: Optional[Annotated[Union[float, int], Meta(ge=1.0, le=1000.0)]] = None
+    age: Optional[Annotated[int, Meta(gt=0, le=200)]] = None
+    rating: Optional[Annotated[float, Meta(gt=0.0, le=5.0)]] = None
 
 
 Users = List[User]
@@ -55,17 +55,17 @@ class Error(Struct):
 
 
 class Api(Struct):
-    apiKey: Annotated[
-        Optional[str], Meta(description='To be used as a dataset parameter value')
+    apiKey: Optional[
+        Annotated[str, Meta(description='To be used as a dataset parameter value')]
     ] = None
-    apiVersionNumber: Annotated[
-        Optional[str], Meta(description='To be used as a version parameter value')
+    apiVersionNumber: Optional[
+        Annotated[str, Meta(description='To be used as a version parameter value')]
     ] = None
-    apiUrl: Annotated[
-        Optional[str], Meta(description="The URL describing the dataset's fields")
+    apiUrl: Optional[
+        Annotated[str, Meta(description="The URL describing the dataset's fields")]
     ] = None
-    apiDocumentationUrl: Annotated[
-        Optional[str], Meta(description='A URL to the API console for each API')
+    apiDocumentationUrl: Optional[
+        Annotated[str, Meta(description='A URL to the API console for each API')]
     ] = None
 
 
diff --git a/tests/data/expected/main/main_with_aliases_msgspec/output.py b/tests/data/expected/main/main_with_aliases_msgspec/output.py
--- a/tests/data/expected/main/main_with_aliases_msgspec/output.py
+++ b/tests/data/expected/main/main_with_aliases_msgspec/output.py
@@ -39,17 +39,17 @@ class Error(Struct):
 
 
 class Api(Struct):
-    apiKey: Annotated[
-        Optional[str], Meta(description='To be used as a dataset parameter value')
+    apiKey: Optional[
+        Annotated[str, Meta(description='To be used as a dataset parameter value')]
     ] = None
-    apiVersionNumber: Annotated[
-        Optional[str], Meta(description='To be used as a version parameter value')
+    apiVersionNumber: Optional[
+        Annotated[str, Meta(description='To be used as a version parameter value')]
     ] = None
-    apiUrl: Annotated[
-        Optional[str], Meta(description="The URL describing the dataset's fields")
+    apiUrl: Optional[
+        Annotated[str, Meta(description="The URL describing the dataset's fields")]
     ] = None
-    apiDocumentationUrl: Annotated[
-        Optional[str], Meta(description='A URL to the API console for each API')
+    apiDocumentationUrl: Optional[
+        Annotated[str, Meta(description='A URL to the API console for each API')]
     ] = None
 
 
diff --git a/tests/test_main.py b/tests/test_main.py
--- a/tests/test_main.py
+++ b/tests/test_main.py
@@ -2688,6 +2688,10 @@ def test_main_openapi_nullable_strict_nullable():
             'pydantic_v2.BaseModel',
             'main_pattern_pydantic_v2',
         ),
+        (
+            'msgspec.Struct',
+            'main_pattern_msgspec',
+        ),
     ],
 )
 @freeze_time('2019-07-26')
@@ -2702,6 +2706,8 @@ def test_main_openapi_pattern(output_model, expected_output):
                 str(output_file),
                 '--input-file-type',
                 'openapi',
+                '--target-python',
+                '3.9',
                 '--output-model-type',
                 output_model,
             ]