Repository: koxudaxi/datamodel-code-generator

Issue:
Generated python file names contain invalid caharacters
**Describe the bug**
When generating data models from schemas in a directory (owned by another team in my case), the generated file names contain invalid characters, which doesn't allow python to import the module.

**To Reproduce**

example: INPUT
```
schemas:
  - order-request.schema.json
  - cancel-request.schema.json
```

example: OUTPUT

```
gen:
  - order-request.schema.py
  - cancel-request.schema.py
````




Used commandline:
```
$ datamodel-codegen --input "../schemas" --input-file-type jsonschema --output "./gen" 
```

**Expected behavior**
The file names to be valid python module names, ex: periods and dash characters to be replaced by underscores.

**Version:**
 - OS: MacOs
 - Python version: 3.8
 - datamodel-code-generator version: 0.17.1

**Additional context**
Workaround to import the module: https://stackoverflow.com/questions/8350853/how-to-import-module-when-module-name-has-a-dash-or-hyphen-in-it



Discussion:
@pulasthibandara 
Thank you for creating the issue.
I will fix it.
Apologies, closed the wrong issue.

Resolving patch:
diff --git a/datamodel_code_generator/parser/base.py b/datamodel_code_generator/parser/base.py
--- a/datamodel_code_generator/parser/base.py
+++ b/datamodel_code_generator/parser/base.py
@@ -700,6 +700,7 @@ def __change_from_import(
                     from_, import_ = full_path = relative(
                         model.module_name, data_type.full_name
                     )
+                    import_ = import_.replace('-', '_')
 
                 alias = scoped_model_resolver.add(full_path, import_).name
 
@@ -778,10 +779,18 @@ def __apply_discriminator_type(
                                 discriminator_model.path.split('#/')[-1]
                                 != path.split('#/')[-1]
                             ):
-                                if '#' in path or discriminator_model.path[
-                                    :-1
-                                ] != path.lstrip('./'):
-                                    continue
+                                if (
+                                    path.startswith('#/')
+                                    or discriminator_model.path[:-1]
+                                    != path.split('/')[-1]
+                                ):
+                                    t_path = path[str(path).find('/') + 1 :]
+                                    t_disc = discriminator_model.path[
+                                        : str(discriminator_model.path).find('#')
+                                    ].lstrip('../')
+                                    t_disc_2 = '/'.join(t_disc.split('/')[1:])
+                                    if t_path != t_disc and t_path != t_disc_2:
+                                        continue
                             type_names.append(name)
                     else:
                         type_names = [discriminator_model.path.split('/')[-1]]
@@ -1252,6 +1261,7 @@ class Processed(NamedTuple):
                     init = True
                 else:
                     module = (*module[:-1], f'{module[-1]}.py')
+                    module = tuple(part.replace('-', '_') for part in module)
             else:
                 module = ('__init__.py',)
 


Test patch:
diff --git a/tests/data/expected/main/discriminator_with_external_reference/output.py b/tests/data/expected/main/discriminator_with_external_reference/output.py
--- a/tests/data/expected/main/discriminator_with_external_reference/output.py
+++ b/tests/data/expected/main/discriminator_with_external_reference/output.py
@@ -4,7 +4,7 @@
 
 from __future__ import annotations
 
-from typing import Union
+from typing import Optional, Union
 
 from pydantic import BaseModel, Field
 from typing_extensions import Literal
@@ -16,6 +16,15 @@ class Type1(BaseModel):
 
 class Type2(BaseModel):
     type_: Literal['b'] = Field('b', title='Type ')
+    ref_type: Optional[Type1] = Field(None, description='A referenced type.')
+
+
+class Type4(BaseModel):
+    type_: Literal['d'] = Field('d', title='Type ')
+
+
+class Type5(BaseModel):
+    type_: Literal['e'] = Field('e', title='Type ')
 
 
 class Type3(BaseModel):
@@ -23,4 +32,6 @@ class Type3(BaseModel):
 
 
 class Response(BaseModel):
-    inner: Union[Type1, Type2, Type3] = Field(..., discriminator='type_', title='Inner')
+    inner: Union[Type1, Type2, Type3, Type4, Type5] = Field(
+        ..., discriminator='type_', title='Inner'
+    )
diff --git a/tests/data/expected/main/discriminator_with_external_references_folder/__init__.py b/tests/data/expected/main/discriminator_with_external_references_folder/__init__.py
new file mode 100644
--- /dev/null
+++ b/tests/data/expected/main/discriminator_with_external_references_folder/__init__.py
@@ -0,0 +1,3 @@
+# generated by datamodel-codegen:
+#   filename:  discriminator_with_external_reference
+#   timestamp: 2019-07-26T00:00:00+00:00
diff --git a/tests/data/expected/main/discriminator_with_external_references_folder/inner_folder/__init__.py b/tests/data/expected/main/discriminator_with_external_references_folder/inner_folder/__init__.py
new file mode 100644
--- /dev/null
+++ b/tests/data/expected/main/discriminator_with_external_references_folder/inner_folder/__init__.py
@@ -0,0 +1,3 @@
+# generated by datamodel-codegen:
+#   filename:  discriminator_with_external_reference
+#   timestamp: 2019-07-26T00:00:00+00:00
diff --git a/tests/data/expected/main/discriminator_with_external_references_folder/inner_folder/artificial_folder/__init__.py b/tests/data/expected/main/discriminator_with_external_references_folder/inner_folder/artificial_folder/__init__.py
new file mode 100644
--- /dev/null
+++ b/tests/data/expected/main/discriminator_with_external_references_folder/inner_folder/artificial_folder/__init__.py
@@ -0,0 +1,3 @@
+# generated by datamodel-codegen:
+#   filename:  discriminator_with_external_reference
+#   timestamp: 2019-07-26T00:00:00+00:00
diff --git a/tests/data/expected/main/discriminator_with_external_references_folder/inner_folder/artificial_folder/type_1.py b/tests/data/expected/main/discriminator_with_external_references_folder/inner_folder/artificial_folder/type_1.py
new file mode 100644
--- /dev/null
+++ b/tests/data/expected/main/discriminator_with_external_references_folder/inner_folder/artificial_folder/type_1.py
@@ -0,0 +1,12 @@
+# generated by datamodel-codegen:
+#   filename:  inner_folder/artificial_folder/type-1.json
+#   timestamp: 2019-07-26T00:00:00+00:00
+
+from __future__ import annotations
+
+from pydantic import BaseModel, Field
+from typing_extensions import Literal
+
+
+class Type1(BaseModel):
+    type_: Literal['a'] = Field(..., const=True, title='Type ')
diff --git a/tests/data/expected/main/discriminator_with_external_references_folder/inner_folder/schema.py b/tests/data/expected/main/discriminator_with_external_references_folder/inner_folder/schema.py
new file mode 100644
--- /dev/null
+++ b/tests/data/expected/main/discriminator_with_external_references_folder/inner_folder/schema.py
@@ -0,0 +1,25 @@
+# generated by datamodel-codegen:
+#   filename:  inner_folder/schema.json
+#   timestamp: 2019-07-26T00:00:00+00:00
+
+from __future__ import annotations
+
+from typing import Union
+
+from pydantic import BaseModel, Field
+from typing_extensions import Literal
+
+from .. import type_4
+from ..subfolder import type_5
+from . import type_2
+from .artificial_folder import type_1
+
+
+class Type3(BaseModel):
+    type_: Literal['c'] = Field(..., const=True, title='Type ')
+
+
+class Response(BaseModel):
+    inner: Union[type_1.Type1, type_2.Type2, Type3, type_4.Type4, type_5.Type5] = Field(
+        ..., discriminator='type_', title='Inner'
+    )
diff --git a/tests/data/expected/main/discriminator_with_external_references_folder/inner_folder/type_2.py b/tests/data/expected/main/discriminator_with_external_references_folder/inner_folder/type_2.py
new file mode 100644
--- /dev/null
+++ b/tests/data/expected/main/discriminator_with_external_references_folder/inner_folder/type_2.py
@@ -0,0 +1,16 @@
+# generated by datamodel-codegen:
+#   filename:  inner_folder/type-2.json
+#   timestamp: 2019-07-26T00:00:00+00:00
+
+from __future__ import annotations
+
+from typing import Optional
+
+from pydantic import BaseModel, Field
+
+from .artificial_folder import type_1
+
+
+class Type2(BaseModel):
+    type_: Literal['b'] = Field(..., const=True, title='Type ')
+    ref_type: Optional[type_1.Type1] = Field(None, description='A referenced type.')
diff --git a/tests/data/expected/main/discriminator_with_external_references_folder/subfolder/__init__.py b/tests/data/expected/main/discriminator_with_external_references_folder/subfolder/__init__.py
new file mode 100644
--- /dev/null
+++ b/tests/data/expected/main/discriminator_with_external_references_folder/subfolder/__init__.py
@@ -0,0 +1,3 @@
+# generated by datamodel-codegen:
+#   filename:  discriminator_with_external_reference
+#   timestamp: 2019-07-26T00:00:00+00:00
diff --git a/tests/data/expected/main/discriminator_with_external_references_folder/subfolder/type_5.py b/tests/data/expected/main/discriminator_with_external_references_folder/subfolder/type_5.py
new file mode 100644
--- /dev/null
+++ b/tests/data/expected/main/discriminator_with_external_references_folder/subfolder/type_5.py
@@ -0,0 +1,11 @@
+# generated by datamodel-codegen:
+#   filename:  subfolder/type-5.json
+#   timestamp: 2019-07-26T00:00:00+00:00
+
+from __future__ import annotations
+
+from pydantic import BaseModel, Field
+
+
+class Type5(BaseModel):
+    type_: Literal['e'] = Field(..., const=True, title='Type ')
diff --git a/tests/data/expected/main/discriminator_with_external_references_folder/type_4.py b/tests/data/expected/main/discriminator_with_external_references_folder/type_4.py
new file mode 100644
--- /dev/null
+++ b/tests/data/expected/main/discriminator_with_external_references_folder/type_4.py
@@ -0,0 +1,11 @@
+# generated by datamodel-codegen:
+#   filename:  type-4.json
+#   timestamp: 2019-07-26T00:00:00+00:00
+
+from __future__ import annotations
+
+from pydantic import BaseModel, Field
+
+
+class Type4(BaseModel):
+    type_: Literal['d'] = Field(..., const=True, title='Type ')
diff --git a/tests/data/jsonschema/discriminator_with_external_reference/artificial_folder/type1.json b/tests/data/jsonschema/discriminator_with_external_reference/inner_folder/artificial_folder/type-1.json
similarity index 100%
rename from tests/data/jsonschema/discriminator_with_external_reference/artificial_folder/type1.json
rename to tests/data/jsonschema/discriminator_with_external_reference/inner_folder/artificial_folder/type-1.json
diff --git a/tests/data/jsonschema/discriminator_with_external_reference/schema.json b/tests/data/jsonschema/discriminator_with_external_reference/inner_folder/schema.json
similarity index 59%
rename from tests/data/jsonschema/discriminator_with_external_reference/schema.json
rename to tests/data/jsonschema/discriminator_with_external_reference/inner_folder/schema.json
--- a/tests/data/jsonschema/discriminator_with_external_reference/schema.json
+++ b/tests/data/jsonschema/discriminator_with_external_reference/inner_folder/schema.json
@@ -16,21 +16,29 @@
     "inner": {
       "discriminator": {
         "mapping": {
-          "a": "./artificial_folder/type1.json",
-          "b": "./type2.json",
-          "c": "#/$def/Type3"
+          "a": "./artificial_folder/type-1.json",
+          "b": "./type-2.json",
+          "c": "#/$def/Type3",
+          "d": "../type-4.json",
+          "e": "../subfolder/type-5.json"
         },
         "propertyName": "type_"
       },
       "oneOf": [
         {
-          "$ref": "./artificial_folder/type1.json"
+          "$ref": "./artificial_folder/type-1.json"
         },
         {
-          "$ref": "./type2.json"
+          "$ref": "./type-2.json"
         },
         {
           "$ref": "#/$def/Type3"
+        },
+        {
+          "$ref": "../type-4.json"
+        },
+        {
+          "$ref": "../subfolder/type-5.json"
         }
       ],
       "title": "Inner"
diff --git a/tests/data/jsonschema/discriminator_with_external_reference/inner_folder/type-2.json b/tests/data/jsonschema/discriminator_with_external_reference/inner_folder/type-2.json
new file mode 100644
--- /dev/null
+++ b/tests/data/jsonschema/discriminator_with_external_reference/inner_folder/type-2.json
@@ -0,0 +1,15 @@
+{
+  "properties": {
+    "type_": {
+      "const": "b",
+      "default": "b",
+      "title": "Type "
+    },
+    "ref_type": {
+      "$ref": "./artificial_folder/type-1.json",
+      "description": "A referenced type."
+    }
+  },
+  "title": "Type2",
+  "type": "object"
+}
\ No newline at end of file
diff --git a/tests/data/jsonschema/discriminator_with_external_reference/type2.json b/tests/data/jsonschema/discriminator_with_external_reference/subfolder/type-5.json
similarity index 58%
rename from tests/data/jsonschema/discriminator_with_external_reference/type2.json
rename to tests/data/jsonschema/discriminator_with_external_reference/subfolder/type-5.json
--- a/tests/data/jsonschema/discriminator_with_external_reference/type2.json
+++ b/tests/data/jsonschema/discriminator_with_external_reference/subfolder/type-5.json
@@ -1,11 +1,11 @@
 {
   "properties": {
     "type_": {
-      "const": "b",
-      "default": "b",
+      "const": "e",
+      "default": "e",
       "title": "Type "
     }
   },
-  "title": "Type2",
+  "title": "Type5",
   "type": "object"
 }
\ No newline at end of file
diff --git a/tests/data/jsonschema/discriminator_with_external_reference/type-4.json b/tests/data/jsonschema/discriminator_with_external_reference/type-4.json
new file mode 100644
--- /dev/null
+++ b/tests/data/jsonschema/discriminator_with_external_reference/type-4.json
@@ -0,0 +1,11 @@
+{
+  "properties": {
+    "type_": {
+      "const": "d",
+      "default": "d",
+      "title": "Type "
+    }
+  },
+  "title": "Type4",
+  "type": "object"
+}
\ No newline at end of file
diff --git a/tests/test_main.py b/tests/test_main.py
--- a/tests/test_main.py
+++ b/tests/test_main.py
@@ -6133,6 +6133,7 @@ def test_main_jsonschema_external_discriminator():
                 str(
                     JSON_SCHEMA_DATA_PATH
                     / 'discriminator_with_external_reference'
+                    / 'inner_folder'
                     / 'schema.json'
                 ),
                 '--output',
@@ -6152,6 +6153,29 @@ def test_main_jsonschema_external_discriminator():
         )
 
 
+@freeze_time('2019-07-26')
+def test_main_jsonschema_external_discriminator_folder():
+    with TemporaryDirectory() as output_dir:
+        output_path: Path = Path(output_dir)
+        return_code: Exit = main(
+            [
+                '--input',
+                str(JSON_SCHEMA_DATA_PATH / 'discriminator_with_external_reference'),
+                '--output',
+                str(output_path),
+            ]
+        )
+        assert return_code == Exit.OK
+        main_modular_dir = (
+            EXPECTED_MAIN_PATH / 'discriminator_with_external_references_folder'
+        )
+        for path in main_modular_dir.rglob('*.py'):
+            result = output_path.joinpath(
+                path.relative_to(main_modular_dir)
+            ).read_text()
+            assert result == path.read_text()
+
+
 @freeze_time('2019-07-26')
 @pytest.mark.skipif(
     black.__version__.split('.')[0] == '19',