Repository: mirumee/ariadne-codegen (python)

Issue:
Change custom scalar imports
As discussed in #104

If `parse`/`serialize`/`type`, in scalar section, consists `.` then produced import should be generated by splitting provided string by last `.`, eg. `"ab.cd.CustomScalar"` -> `from ab.cd import CustomScalar`

If scalar section has `import` key then imports should be generated according to the existing strategy with raised deprecation warning

Pull request description:
Change logic how custom scalar imports are generated
resolves #120 

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index b8433a9..575ea46 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,5 +1,10 @@
 # CHANGELOG
 
+## UNRELEASED
+
+- Changed logic how custom scalar imports are generated. Deprecated `import_` key.
+
+
 ## 0.5.0 (2023-04-05)
 
 - Added generation of GraphQL schema's Python representation.
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
deleted file mode 100644
index 86b7388..0000000
--- a/CONTRIBUTING.md
+++ /dev/null
@@ -1,48 +0,0 @@
-# Contributing
-
-Thank you for your interest in contributing to Ariadne Codegen!
-
-We welcome bug reports, questions, pull requests, and general feedback.
-
-We also ask all contributors to familiarize themselves with and follow code of conduct, available in the [CODE_OF_CONDUCT.md](https://github.com/mirumee/ariadne/blob/master/CODE_OF_CONDUCT.md) file kept in the main project's repository.
-
-
-# Reporting bugs, asking for help, offering feedback and ideas
-
-You can use [GitHub issues](https://github.com/mirumee/ariadne-codegen/issues) to report bugs, ask for help, share your ideas, or simply offer feedback. We are curious what you think of Ariadne!
-
-
-## Development setup
-
-Ariadne Code Generator is written to support Python 3.9, 3.10 and 3.11.
-
-Codebase is formatted using [black](https://github.com/ambv/black) and [isort](https://github.com/PyCQA/isort), the contents of the `ariadne-codegen` package are annotated with types and validated using [mypy](http://mypy-lang.org/index.html). [Pylint](https://github.com/pylint-dev/pylint) is used to catch errors in code.
-
-Tests are developed using [pytest](https://pytest.org/).
-
-Dev requirements can be installed using Pip extras. For example, to install all dependencies for doing local development and running the tests, run `pip install -e .[dev]`.
-
-We require all changes to be done via pull requests, and to be approved by member-ranked users before merging.
-
-All changes should pass these linter checks:
-
-```bash
-pylint ariadne_codegen tests
-mypy ariadne_codegen --ignore-missing-imports
-mypy --strict tests/main/clients/*/expected_client
-mypy --strict tests/main/graphql_schemas/*/expected_schema.py
-black --check .
-isort . --check-only
-```
-
-
-## Working on issues
-
-We consider all issues which are not assigned to anybody as being available for contributors. The **[help wanted](https://github.com/mirumee/ariadne-codegen/labels/help%20wanted)** label is used to single out issues that we consider easier or higher priority on the list of things that we would like to see.
-
-If you've found issue you want to help with, please add your comment to it - this lets other contributors know what issues are being worked on, as well as allowing maintainers to offer guidance and help.
-
-
-## Pull requests
-
-We don't require pull requests to be followed with bug reports. If you've found a typo or a silly little bug that has no issue or pull request already, you can open your own pull request. We only ask that this PR provides context or explanation for what problem it fixes, or which area of the project it improves.
diff --git a/README.md b/README.md
index b1ca455..490be4e 100644
--- a/README.md
+++ b/README.md
@@ -102,10 +102,10 @@ You can provide information about specific scalar by adding section to `pyprojec
 type = "(required) python type name"
 serialize = "function used to serialize scalar"
 parse = "function used to create scalar instance from serialized form"
-import = "module to import from"
 ```
 
-All occurences of `{graphql scalar name}` will be represented as `type`. If provided, `serialize` and `parse` will be used for serialization and deserialization. In all files which use `type`/`serialize`/`parse` there will be added extra import `from {import} import {type}, {serialize}, {parse}`
+All occurences of `{graphql scalar name}` will be represented as `type`. If provided, `serialize` and `parse` will be used for serialization and deserialization.
+If `type`/`serialize`/`parse` contains at least one `.` then string will be split by it's last occurrence. First part will be used as module to import from, and second part as type/method name. For example, `type = "custom_scalars.a.ScalarA"` will produce `from custom_scalars.a import ScalarA`.
 
 
 ### Example with scalar mapped to built-in type
@@ -124,8 +124,7 @@ In this scenario scalar is represented as `datetime`, so it needs to be imported
 
 ```toml
 [tool.ariadne-codegen.scalars.DATETIME]
-type = "datetime"
-import = "datetime"
+type = "datetime.datetime"
 ```
 
 
@@ -139,10 +138,9 @@ In this example scalar is represented as class `TypeB`. Pydantic can\`t handle
 files_to_include = [".../type_b.py"]
 
 [tool.ariadne-codegen.scalars.SCALARB]
-type = "TypeB"
-parse = "parse_b"
-serialize = "serialize_b"
-import = ".type_b"
+type = ".type_b.TypeB"
+parse = ".type_b.parse_b"
+serialize = ".type_b.serialize_b"
 ```
 
 
@@ -238,8 +236,6 @@ Generated file contains:
 
 We welcome all contributions to Ariadne! If you've found a bug or issue, feel free to use [GitHub issues](https://github.com/mirumee/ariadne-codegen/issues). If you have any questions or feedback, don't hesitate to catch us on [GitHub discussions](https://github.com/mirumee/ariadne/discussions/).
 
-For guidance and instructions, please see [CONTRIBUTING.md](CONTRIBUTING.md).
-
 Also make sure you follow [@AriadneGraphQL](https://twitter.com/AriadneGraphQL) on Twitter for latest updates, news and random musings!
 
 
diff --git a/ariadne_codegen/client_generators/arguments.py b/ariadne_codegen/client_generators/arguments.py
index 1231430..a874205 100644
--- a/ariadne_codegen/client_generators/arguments.py
+++ b/ariadne_codegen/client_generators/arguments.py
@@ -139,7 +139,7 @@ class ArgumentsGenerator:
                 name = SIMPLE_TYPE_MAP.get(name, ANY)
             else:
                 used_custom_scalar = name
-                name = self.custom_scalars[name].type_
+                name = self.custom_scalars[name].type_name
         else:
             raise ParsingError(f"Incorrect argument type {name}")
 
@@ -158,9 +158,9 @@ class ArgumentsGenerator:
         if used_custom_scalar:
             self._used_custom_scalars.append(used_custom_scalar)
             scalar_data = self.custom_scalars[used_custom_scalar]
-            if scalar_data.serialize:
+            if scalar_data.serialize_name:
                 return generate_call(
-                    func=generate_name(scalar_data.serialize),
+                    func=generate_name(scalar_data.serialize_name),
                     args=[generate_name(name)],
                 )
 
diff --git a/ariadne_codegen/client_generators/client.py b/ariadne_codegen/client_generators/client.py
index eb11fbe..6b116c3 100644
--- a/ariadne_codegen/client_generators/client.py
+++ b/ariadne_codegen/client_generators/client.py
@@ -74,6 +74,12 @@ class ClientGenerator:
             import_ = self.plugin_manager.generate_client_import(import_)
         self.imports.append(import_)
 
+    def add_imports(self, imports: List[ast.ImportFrom]):
+        for import_ in imports:
+            if self.plugin_manager:
+                import_ = self.plugin_manager.generate_client_import(import_)
+            self.imports.append(import_)
+
     def add_method(
         self,
         name: str,
diff --git a/ariadne_codegen/client_generators/input_fields.py b/ariadne_codegen/client_generators/input_fields.py
index dd2b9d4..4afa8ff 100644
--- a/ariadne_codegen/client_generators/input_fields.py
+++ b/ariadne_codegen/client_generators/input_fields.py
@@ -56,7 +56,7 @@ def parse_input_field_type(
         if custom_scalars and type_.name in custom_scalars:
             return (
                 generate_annotation_name(
-                    name=custom_scalars[type_.name].type_, nullable=nullable
+                    name=custom_scalars[type_.name].type_name, nullable=nullable
                 ),
                 type_.name,
             )
diff --git a/ariadne_codegen/client_generators/input_types.py b/ariadne_codegen/client_generators/input_types.py
index e89fd17..8c639db 100644
--- a/ariadne_codegen/client_generators/input_types.py
+++ b/ariadne_codegen/client_generators/input_types.py
@@ -33,7 +33,7 @@ from .constants import (
     UPDATE_FORWARD_REFS_METHOD,
 )
 from .input_fields import parse_input_field_default_value, parse_input_field_type
-from .scalars import ScalarData
+from .scalars import ScalarData, generate_scalar_imports
 
 
 class InputTypesGenerator:
@@ -74,12 +74,8 @@ class InputTypesGenerator:
         if self._used_scalars:
             for scalar_name in self._used_scalars:
                 scalar_data = self.custom_scalars[scalar_name]
-                if scalar_data.import_ and scalar_data.names_to_import:
-                    self._imports.append(
-                        generate_import_from(
-                            names=scalar_data.names_to_import, from_=scalar_data.import_
-                        )
-                    )
+                self._imports.extend(generate_scalar_imports(scalar_data))
+
         update_forward_refs_calls = [
             generate_expr(generate_method_call(c.name, UPDATE_FORWARD_REFS_METHOD))
             for c in self._class_defs
diff --git a/ariadne_codegen/client_generators/package.py b/ariadne_codegen/client_generators/package.py
index 8568a50..ec4e2e1 100644
--- a/ariadne_codegen/client_generators/package.py
+++ b/ariadne_codegen/client_generators/package.py
@@ -24,7 +24,7 @@ from .enums import EnumsGenerator
 from .init_file import InitFileGenerator
 from .input_types import InputTypesGenerator
 from .result_types import ResultTypesGenerator
-from .scalars import ScalarData, ScalarsDefinitionsGenerator
+from .scalars import ScalarData, ScalarsDefinitionsGenerator, generate_scalar_imports
 
 
 class PackageGenerator:
@@ -247,10 +247,7 @@ class PackageGenerator:
 
         for custom_scalar_name in self.arguments_generator.get_used_custom_scalars():
             scalar_data = self.custom_scalars[custom_scalar_name]
-            if scalar_data.import_:
-                self.client_generator.add_import(
-                    names=scalar_data.names_to_import, from_=scalar_data.import_
-                )
+            self.client_generator.add_imports(generate_scalar_imports(scalar_data))
 
         self.client_generator.add_import(
             names=[self.base_client_name],
diff --git a/ariadne_codegen/client_generators/result_fields.py b/ariadne_codegen/client_generators/result_fields.py
index a23e3d3..7d5974a 100644
--- a/ariadne_codegen/client_generators/result_fields.py
+++ b/ariadne_codegen/client_generators/result_fields.py
@@ -68,8 +68,10 @@ def parse_operation_field_type(
             return (generate_annotation_name(SIMPLE_TYPE_MAP[type_.name], nullable), [])
         if custom_scalars and type_.name in custom_scalars:
             return (
-                generate_annotation_name(custom_scalars[type_.name].type_, nullable),
-                [FieldNames(custom_scalars[type_.name].type_, type_.name)],
+                generate_annotation_name(
+                    custom_scalars[type_.name].type_name, nullable
+                ),
+                [FieldNames(custom_scalars[type_.name].type_name, type_.name)],
             )
         return (generate_annotation_name(ANY, nullable), [])
 
diff --git a/ariadne_codegen/client_generators/result_types.py b/ariadne_codegen/client_generators/result_types.py
index 6407435..f8f536f 100644
--- a/ariadne_codegen/client_generators/result_types.py
+++ b/ariadne_codegen/client_generators/result_types.py
@@ -50,7 +50,7 @@ from .constants import (
     UPDATE_FORWARD_REFS_METHOD,
 )
 from .result_fields import FieldNames, parse_operation_field
-from .scalars import ScalarData
+from .scalars import ScalarData, generate_scalar_imports
 from .types import CodegenResultFieldType
 
 
@@ -116,12 +116,8 @@ class ResultTypesGenerator:
         if self._used_scalars:
             for scalar_name in self._used_scalars:
                 scalar_data = self.custom_scalars[scalar_name]
-                if scalar_data.import_ and scalar_data.names_to_import:
-                    self._imports.append(
-                        generate_import_from(
-                            names=scalar_data.names_to_import, from_=scalar_data.import_
-                        )
-                    )
+                self._imports.extend(generate_scalar_imports(scalar_data))
+
         update_forward_refs_calls = [
             generate_expr(
                 generate_method_call(class_def.name, UPDATE_FORWARD_REFS_METHOD)
diff --git a/ariadne_codegen/client_generators/scalars.py b/ariadne_codegen/client_generators/scalars.py
index 1f0054b..024bbed 100644
--- a/ariadne_codegen/client_generators/scalars.py
+++ b/ariadne_codegen/client_generators/scalars.py
@@ -1,6 +1,7 @@
 import ast
 from dataclasses import dataclass
 from typing import List, Optional, cast
+from warnings import warn
 
 from ..codegen import (
     generate_ann_assign,
@@ -30,9 +31,47 @@ class ScalarData:
     parse: Optional[str] = None
     import_: Optional[str] = None
 
-    @property
-    def names_to_import(self) -> List[str]:
-        return [name for name in (self.type_, self.serialize, self.parse) if name]
+    def __post_init__(self):
+        self.type_name: str = self._get_object_name(self.type_)
+        self.parse_name: Optional[str] = (
+            self._get_object_name(self.parse) if self.parse else None
+        )
+        self.serialize_name: Optional[str] = (
+            self._get_object_name(self.serialize) if self.serialize else None
+        )
+
+        self.names_to_import: List[str] = [
+            name for name in (self.type_, self.serialize, self.parse) if name
+        ]
+
+    def _get_object_name(self, name: str) -> str:
+        if "." in name:
+            _, object_name = name.rsplit(".", maxsplit=1)
+            return object_name
+        return name
+
+
+def generate_scalar_imports(data: ScalarData) -> List[ast.ImportFrom]:
+    names_to_import = data.names_to_import
+    if data.import_:
+        warn(
+            'Support for "import" key has been deprecated '
+            "and will be dropped in future release. "
+            "Instead provide module in type/serialize/parse string.",
+            DeprecationWarning,
+        )
+        return (
+            [generate_import_from(names=names_to_import, from_=data.import_)]
+            if names_to_import
+            else []
+        )
+
+    imports = []
+    for name in names_to_import:
+        if "." in name:
+            module_name, object_name = name.rsplit(".", maxsplit=1)
+            imports.append(generate_import_from(names=[object_name], from_=module_name))
+    return imports
 
 
 class ScalarsDefinitionsGenerator:
@@ -53,18 +92,16 @@ class ScalarsDefinitionsGenerator:
             self.add_scalar(data)
 
     def add_scalar(self, data: ScalarData) -> None:
-        if data.parse:
-            self._parse_dict.keys.append(generate_name(data.type_))
-            self._parse_dict.values.append(generate_name(data.parse))
+        if data.parse_name:
+            self._parse_dict.keys.append(generate_name(data.type_name))
+            self._parse_dict.values.append(generate_name(data.parse_name))
 
-        if data.serialize:
-            self._serialize_dict.keys.append(generate_name(data.type_))
-            self._serialize_dict.values.append(generate_name(data.serialize))
+        if data.serialize_name:
+            self._serialize_dict.keys.append(generate_name(data.type_name))
+            self._serialize_dict.values.append(generate_name(data.serialize_name))
 
-        if (data.parse or data.serialize) and data.import_:
-            self._imports.append(
-                generate_import_from(names=data.names_to_import, from_=data.import_)
-            )
+        if data.parse_name or data.serialize_name:
+            self._imports.extend(generate_scalar_imports(data))
 
     def generate(self) -> ast.Module:
         if self.plugin_manager:
@@ -77,8 +114,8 @@ class ScalarsDefinitionsGenerator:
         module = generate_module(
             body=cast(
                 List[ast.stmt],
-                [
-                    self._imports,
+                self._imports
+                + [
                     self._generate_dict_assignment(
                         name=SCALARS_PARSE_DICT_NAME,
                         dict_=self._parse_dict,


Test patch:
diff --git a/tests/client_generators/input_types_generator/test_imports.py b/tests/client_generators/input_types_generator/test_imports.py
index 075621a..524e7f4 100644
--- a/tests/client_generators/input_types_generator/test_imports.py
+++ b/tests/client_generators/input_types_generator/test_imports.py
@@ -43,9 +43,7 @@ def test_generate_returns_module_with_used_custom_scalars_imports():
     generator = InputTypesGenerator(
         schema=build_ast_schema(parse(schema_str)),
         enums_module="enums",
-        custom_scalars={
-            "SCALARA": ScalarData(type_="ScalarA", import_=".custom_scalars")
-        },
+        custom_scalars={"SCALARA": ScalarData(type_=".custom_scalars.ScalarA")},
     )
     expected_import = ast.ImportFrom(
         module=".custom_scalars", names=[ast.alias("ScalarA")], level=0
diff --git a/tests/client_generators/result_types_generator/test_imports.py b/tests/client_generators/result_types_generator/test_imports.py
index a4d3f8b..06a55cf 100644
--- a/tests/client_generators/result_types_generator/test_imports.py
+++ b/tests/client_generators/result_types_generator/test_imports.py
@@ -52,9 +52,7 @@ def test_generate_returns_module_with_used_custom_scalars_imports():
         schema=build_ast_schema(parse(SCHEMA_STR)),
         operation_definition=operation_definition,
         enums_module_name="enums",
-        custom_scalars={
-            "SCALARA": ScalarData(type_="ScalarA", import_=".custom_scalars")
-        },
+        custom_scalars={"SCALARA": ScalarData(type_=".custom_scalars.ScalarA")},
     )
     expected_import = ast.ImportFrom(
         module=".custom_scalars", names=[ast.alias("ScalarA")], level=0
diff --git a/tests/client_generators/test_package_generator.py b/tests/client_generators/test_package_generator.py
index 03fd3c5..d2a15b6 100644
--- a/tests/client_generators/test_package_generator.py
+++ b/tests/client_generators/test_package_generator.py
@@ -565,7 +565,7 @@ def test_generate_creates_client_with_custom_scalars_imports(tmp_path):
         package_name,
         tmp_path.as_posix(),
         build_ast_schema(parse(SCHEMA_STR)),
-        custom_scalars={"SCALARABC": ScalarData(type_="ScalarABC", import_=".abc")},
+        custom_scalars={"SCALARABC": ScalarData(type_=".abc.ScalarABC")},
     )
     query_str = """
     query CustomQuery($id: ID!, $param: SCALARABC) {
diff --git a/tests/client_generators/test_scalars.py b/tests/client_generators/test_scalars.py
index 5e377ee..aa42d48 100644
--- a/tests/client_generators/test_scalars.py
+++ b/tests/client_generators/test_scalars.py
@@ -1,5 +1,7 @@
 import ast
 
+import pytest
+
 from ariadne_codegen.client_generators.constants import (
     ANY,
     CALLABLE,
@@ -11,25 +13,84 @@ from ariadne_codegen.client_generators.constants import (
 from ariadne_codegen.client_generators.scalars import (
     ScalarData,
     ScalarsDefinitionsGenerator,
+    generate_scalar_imports,
 )
 
-from ..utils import compare_ast
+from ..utils import compare_ast, sorted_imports
+
+
+@pytest.mark.parametrize(
+    "data, expected_imports",
+    [
+        (ScalarData(type_="Scalar"), []),
+        (ScalarData(type_="Scalar", serialize="serialize"), []),
+        (ScalarData(type_="Scalar", parse="parse"), []),
+        (
+            ScalarData(type_="ab.cd.Scalar"),
+            [ast.ImportFrom(names=[ast.alias("Scalar")], module="ab.cd", level=0)],
+        ),
+        (
+            ScalarData(type_="ab.cd.Scalar", serialize="xyz.serialize"),
+            [
+                ast.ImportFrom(names=[ast.alias("Scalar")], module="ab.cd", level=0),
+                ast.ImportFrom(names=[ast.alias("serialize")], module="xyz", level=0),
+            ],
+        ),
+        (
+            ScalarData(type_="ab.cd.Scalar", parse="xyz.parse"),
+            [
+                ast.ImportFrom(names=[ast.alias("Scalar")], module="ab.cd", level=0),
+                ast.ImportFrom(names=[ast.alias("parse")], module="xyz", level=0),
+            ],
+        ),
+        (
+            ScalarData(type_="a.Scalar", serialize="b.serialize", parse="c.parse"),
+            [
+                ast.ImportFrom(names=[ast.alias("Scalar")], module="a", level=0),
+                ast.ImportFrom(names=[ast.alias("serialize")], module="b", level=0),
+                ast.ImportFrom(names=[ast.alias("parse")], module="c", level=0),
+            ],
+        ),
+    ],
+)
+def test_generate_scalar_imports_returns_correct_imports(data, expected_imports):
+    assert compare_ast(
+        sorted_imports(generate_scalar_imports(data)), sorted_imports(expected_imports)
+    )
+
+
+def test_generate_scalar_imports_for_data_with_import_raises_deprecation_warning():
+    data = ScalarData(
+        type_="Scalar", serialize="serialize", parse="parse", import_="xyz"
+    )
+    expected_imports = [
+        ast.ImportFrom(
+            names=[
+                ast.alias(data.type_),
+                ast.alias(data.serialize),
+                ast.alias(data.parse),
+            ],
+            module=data.import_,
+            level=0,
+        )
+    ]
+
+    with pytest.deprecated_call():
+        assert compare_ast(generate_scalar_imports(data), expected_imports)
 
 
 def test_generate_without_scalars_returns_module_with_empty_dicts():
     expected_module = ast.Module(
         body=[
-            [
-                ast.ImportFrom(
-                    module=TYPING_MODULE,
-                    names=[
-                        ast.alias(name=DICT),
-                        ast.alias(name=ANY),
-                        ast.alias(name=CALLABLE),
-                    ],
-                    level=0,
-                )
-            ],
+            ast.ImportFrom(
+                module=TYPING_MODULE,
+                names=[
+                    ast.alias(name=DICT),
+                    ast.alias(name=ANY),
+                    ast.alias(name=CALLABLE),
+                ],
+                level=0,
+            ),
             ast.AnnAssign(
                 target=ast.Name(id=SCALARS_PARSE_DICT_NAME),
                 annotation=ast.Subscript(
@@ -87,57 +148,54 @@ def test_generate_returns_module_with_dictionaries_with_scalars_methods():
     generator = ScalarsDefinitionsGenerator(
         scalars_data=[
             ScalarData(type_="str"),
-            ScalarData(type_="datetime", import_="datetime"),
+            ScalarData(type_="datetime.datetime"),
             ScalarData(
-                type_="ScalarA",
-                serialize="serialize_a",
-                parse="parse_a",
-                import_=".scalar_a",
+                type_=".scalar_a.ScalarA",
+                serialize=".scalar_a.serialize_a",
+                parse=".scalar_a.parse_a",
             ),
             ScalarData(
-                type_="ScalarB",
-                serialize="serialize_b",
-                import_=".scalar_b",
+                type_=".scalar_b.ScalarB",
+                serialize=".scalar_b.serialize_b",
             ),
             ScalarData(
-                type_="ScalarC",
-                parse="parse_c",
-                import_=".scalar_c",
+                type_=".scalar_c.ScalarC",
+                parse=".scalar_c.parse_c",
             ),
         ]
     )
     expected_module = ast.Module(
         body=[
-            [
-                ast.ImportFrom(
-                    module=TYPING_MODULE,
-                    names=[
-                        ast.alias(name=DICT),
-                        ast.alias(name=ANY),
-                        ast.alias(name=CALLABLE),
-                    ],
-                    level=0,
-                ),
-                ast.ImportFrom(
-                    module=".scalar_a",
-                    names=[
-                        ast.alias(name="ScalarA"),
-                        ast.alias(name="serialize_a"),
-                        ast.alias(name="parse_a"),
-                    ],
-                    level=0,
-                ),
-                ast.ImportFrom(
-                    module=".scalar_b",
-                    names=[ast.alias(name="ScalarB"), ast.alias(name="serialize_b")],
-                    level=0,
-                ),
-                ast.ImportFrom(
-                    module=".scalar_c",
-                    names=[ast.alias(name="ScalarC"), ast.alias(name="parse_c")],
-                    level=0,
-                ),
-            ],
+            ast.ImportFrom(
+                module="typing",
+                names=[
+                    ast.alias(name="Dict"),
+                    ast.alias(name="Any"),
+                    ast.alias(name="Callable"),
+                ],
+                level=0,
+            ),
+            ast.ImportFrom(
+                module=".scalar_a", names=[ast.alias(name="ScalarA")], level=0
+            ),
+            ast.ImportFrom(
+                module=".scalar_a", names=[ast.alias(name="serialize_a")], level=0
+            ),
+            ast.ImportFrom(
+                module=".scalar_a", names=[ast.alias(name="parse_a")], level=0
+            ),
+            ast.ImportFrom(
+                module=".scalar_b", names=[ast.alias(name="ScalarB")], level=0
+            ),
+            ast.ImportFrom(
+                module=".scalar_b", names=[ast.alias(name="serialize_b")], level=0
+            ),
+            ast.ImportFrom(
+                module=".scalar_c", names=[ast.alias(name="ScalarC")], level=0
+            ),
+            ast.ImportFrom(
+                module=".scalar_c", names=[ast.alias(name="parse_c")], level=0
+            ),
             ast.AnnAssign(
                 target=ast.Name(id=SCALARS_PARSE_DICT_NAME),
                 annotation=ast.Subscript(
diff --git a/tests/main/clients/custom_scalars/pyproject.toml b/tests/main/clients/custom_scalars/pyproject.toml
index fe7d539..90acd05 100644
--- a/tests/main/clients/custom_scalars/pyproject.toml
+++ b/tests/main/clients/custom_scalars/pyproject.toml
@@ -9,11 +9,9 @@ files_to_include = ["custom_scalars.py"]
 type = "int"
 
 [tool.ariadne-codegen.scalars.DATETIME]
-type = "datetime"
-import = "datetime"
+type = "datetime.datetime"
 
 [tool.ariadne-codegen.scalars.CODE]
-type = "Code"
-serialize = "serialize_code"
-parse = "parse_code"
-import = ".custom_scalars"
+type = ".custom_scalars.Code"
+serialize = ".custom_scalars.serialize_code"
+parse = ".custom_scalars.parse_code"
diff --git a/tests/utils.py b/tests/utils.py
index b363b71..9fdf665 100644
--- a/tests/utils.py
+++ b/tests/utils.py
@@ -48,3 +48,7 @@ def filter_imports(module: ast.Module) -> List[ast.ImportFrom]:
 
 def format_graphql_str(source: str) -> str:
     return dedent(source).replace(4 * " ", 2 * " ").strip()
+
+
+def sorted_imports(imports: List[ast.ImportFrom]) -> List[ast.ImportFrom]:
+    return sorted(imports, key=lambda import_: import_.module or "")