diff --git a/django/utils/inspect.py b/django/utils/inspect.py index 28418f7312..81a15ed2db 100644 --- a/django/utils/inspect.py +++ b/django/utils/inspect.py @@ -16,13 +16,18 @@ def _get_callable_parameters(meth_or_func): return _get_func_parameters(func, remove_first=is_method) +ARG_KINDS = frozenset( + { + inspect.Parameter.POSITIONAL_ONLY, + inspect.Parameter.KEYWORD_ONLY, + inspect.Parameter.POSITIONAL_OR_KEYWORD, + } +) + + def get_func_args(func): params = _get_callable_parameters(func) - return [ - param.name - for param in params - if param.kind == inspect.Parameter.POSITIONAL_OR_KEYWORD - ] + return [param.name for param in params if param.kind in ARG_KINDS] def get_func_full_args(func): diff --git a/tests/custom_migration_operations/operations.py b/tests/custom_migration_operations/operations.py index f63f0b2a3a..6bed8559d1 100644 --- a/tests/custom_migration_operations/operations.py +++ b/tests/custom_migration_operations/operations.py @@ -68,6 +68,11 @@ class ArgsKwargsOperation(TestOperation): ) +class ArgsAndKeywordOnlyArgsOperation(ArgsKwargsOperation): + def __init__(self, arg1, arg2, *, kwarg1, kwarg2): + super().__init__(arg1, arg2, kwarg1=kwarg1, kwarg2=kwarg2) + + class ExpandArgsOperation(TestOperation): serialization_expand_args = ["arg"] diff --git a/tests/migrations/test_writer.py b/tests/migrations/test_writer.py index a2ac673804..891efd8ac7 100644 --- a/tests/migrations/test_writer.py +++ b/tests/migrations/test_writer.py @@ -152,6 +152,24 @@ class OperationWriterTests(SimpleTestCase): "),", ) + def test_keyword_only_args_signature(self): + operation = ( + custom_migration_operations.operations.ArgsAndKeywordOnlyArgsOperation( + 1, 2, kwarg1=3, kwarg2=4 + ) + ) + buff, imports = OperationWriter(operation, indentation=0).serialize() + self.assertEqual(imports, {"import custom_migration_operations.operations"}) + self.assertEqual( + buff, + "custom_migration_operations.operations.ArgsAndKeywordOnlyArgsOperation(\n" + " arg1=1,\n" + " arg2=2,\n" + " kwarg1=3,\n" + " kwarg2=4,\n" + "),", + ) + def test_nested_args_signature(self): operation = custom_migration_operations.operations.ArgsOperation( custom_migration_operations.operations.ArgsOperation(1, 2), diff --git a/tests/postgres_tests/test_operations.py b/tests/postgres_tests/test_operations.py index ff344e3cb0..bc2ae42096 100644 --- a/tests/postgres_tests/test_operations.py +++ b/tests/postgres_tests/test_operations.py @@ -4,6 +4,7 @@ from migrations.test_base import OperationTestBase from django.db import IntegrityError, NotSupportedError, connection, transaction from django.db.migrations.state import ProjectState +from django.db.migrations.writer import OperationWriter from django.db.models import CheckConstraint, Index, Q, UniqueConstraint from django.db.utils import ProgrammingError from django.test import modify_settings, override_settings @@ -393,6 +394,25 @@ class CreateCollationTests(PostgreSQLTestCase): self.assertEqual(len(captured_queries), 1) self.assertIn("DROP COLLATION", captured_queries[0]["sql"]) + def test_writer(self): + operation = CreateCollation( + "sample_collation", + "und-u-ks-level2", + provider="icu", + deterministic=False, + ) + buff, imports = OperationWriter(operation, indentation=0).serialize() + self.assertEqual(imports, {"import django.contrib.postgres.operations"}) + self.assertEqual( + buff, + "django.contrib.postgres.operations.CreateCollation(\n" + " name='sample_collation',\n" + " locale='und-u-ks-level2',\n" + " provider='icu',\n" + " deterministic=False,\n" + "),", + ) + @unittest.skipUnless(connection.vendor == "postgresql", "PostgreSQL specific tests.") class RemoveCollationTests(PostgreSQLTestCase):