diff --git a/conjure-python-core/src/main/java/com/palantir/conjure/python/ConjurePythonGenerator.java b/conjure-python-core/src/main/java/com/palantir/conjure/python/ConjurePythonGenerator.java index a6d386d92..716069256 100644 --- a/conjure-python-core/src/main/java/com/palantir/conjure/python/ConjurePythonGenerator.java +++ b/conjure-python-core/src/main/java/com/palantir/conjure/python/ConjurePythonGenerator.java @@ -139,13 +139,15 @@ private PythonFile getImplPythonFile( implTypeNameProcessor, definitionPackageNameProcessor, definitionTypeNameProcessor, - dealiasingTypeVisitor); + dealiasingTypeVisitor, + config.forceKeywordArgs()); ClientGenerator clientGenerator = new ClientGenerator( implPackageNameProcessor, implTypeNameProcessor, definitionPackageNameProcessor, definitionTypeNameProcessor, - dealiasingTypeVisitor); + dealiasingTypeVisitor, + config.forceKeywordArgs()); List snippets = new ArrayList<>(); snippets.addAll(conjureDefinition.getTypes().stream() diff --git a/conjure-python-core/src/main/java/com/palantir/conjure/python/GeneratorConfiguration.java b/conjure-python-core/src/main/java/com/palantir/conjure/python/GeneratorConfiguration.java index a6ee31169..5b8f4c76a 100644 --- a/conjure-python-core/src/main/java/com/palantir/conjure/python/GeneratorConfiguration.java +++ b/conjure-python-core/src/main/java/com/palantir/conjure/python/GeneratorConfiguration.java @@ -43,6 +43,11 @@ public interface GeneratorConfiguration { boolean generateRawSource(); + @Value.Default + default boolean forceKeywordArgs() { + return false; + } + default Optional pythonicPackageName() { return packageName().map(packageName -> packageName.replace('-', '_')); } diff --git a/conjure-python-core/src/main/java/com/palantir/conjure/python/client/ClientGenerator.java b/conjure-python-core/src/main/java/com/palantir/conjure/python/client/ClientGenerator.java index b52c3d193..a609baedd 100644 --- a/conjure-python-core/src/main/java/com/palantir/conjure/python/client/ClientGenerator.java +++ b/conjure-python-core/src/main/java/com/palantir/conjure/python/client/ClientGenerator.java @@ -48,18 +48,21 @@ public final class ClientGenerator { private final DealiasingTypeVisitor dealiasingTypeVisitor; private final PythonTypeNameVisitor pythonTypeNameVisitor; private final MyPyTypeNameVisitor myPyTypeNameVisitor; + private final boolean forceKeywordArgs; public ClientGenerator( PackageNameProcessor implPackageNameProcessor, TypeNameProcessor implTypeNameProcessor, PackageNameProcessor definitionPackageNameProcessor, TypeNameProcessor definitionTypeNameProcessor, - DealiasingTypeVisitor dealiasingTypeVisitor) { + DealiasingTypeVisitor dealiasingTypeVisitor, + boolean forceKeywordArgs) { this.implPackageNameProcessor = implPackageNameProcessor; this.implTypeNameProcessor = implTypeNameProcessor; this.definitionPackageNameProcessor = definitionPackageNameProcessor; this.definitionTypeNameProcessor = definitionTypeNameProcessor; this.dealiasingTypeVisitor = dealiasingTypeVisitor; + this.forceKeywordArgs = forceKeywordArgs; pythonTypeNameVisitor = new PythonTypeNameVisitor(implTypeNameProcessor); myPyTypeNameVisitor = new MyPyTypeNameVisitor(dealiasingTypeVisitor, implTypeNameProcessor); } @@ -154,6 +157,7 @@ private PythonEndpointDefinition generateEndpoint( .dealias(rt) .fold(_typeDefinition -> false, type -> type.accept(TypeVisitor.IS_OPTIONAL))) .orElse(false)) + .forceKeywordArgs(forceKeywordArgs) .build(); } } diff --git a/conjure-python-core/src/main/java/com/palantir/conjure/python/poet/BeanSnippet.java b/conjure-python-core/src/main/java/com/palantir/conjure/python/poet/BeanSnippet.java index 59e393907..35bbe424c 100644 --- a/conjure-python-core/src/main/java/com/palantir/conjure/python/poet/BeanSnippet.java +++ b/conjure-python-core/src/main/java/com/palantir/conjure/python/poet/BeanSnippet.java @@ -57,6 +57,11 @@ default String idForSorting() { List fields(); + @Value.Default + default boolean forceKeywordArgs() { + return false; + } + @Override default void emit(PythonPoetWriter poetWriter) { poetWriter.writeIndentedLine(String.format("class %s(ConjureBeanType):", className())); @@ -97,22 +102,25 @@ default void emit(PythonPoetWriter poetWriter) { // constructor -- only if there are fields if (!fields().isEmpty()) { - poetWriter.writeIndentedLine(String.format( - "def __init__(self, %s) -> None:", - Joiner.on(", ") - .join(fields().stream() - .sorted(new PythonField.PythonFieldComparator()) - .map(field -> { - String name = String.format( - "%s: %s", - PythonIdentifierSanitizer.sanitize(field.attributeName()), - field.myPyType()); - if (field.isOptional()) { - return String.format("%s = None", name); - } - return name; - }) - .collect(Collectors.toList())))); + String argsString = Joiner.on(", ") + .join(fields().stream() + .sorted(new PythonField.PythonFieldComparator()) + .map(field -> { + String name = String.format( + "%s: %s", + PythonIdentifierSanitizer.sanitize(field.attributeName()), field.myPyType()); + if (field.isOptional()) { + return String.format("%s = None", name); + } + return name; + }) + .collect(Collectors.toList())); + + String initSignature = forceKeywordArgs() + ? String.format("def __init__(self, *, %s) -> None:", argsString) + : String.format("def __init__(self, %s) -> None:", argsString); + + poetWriter.writeIndentedLine(initSignature); poetWriter.increaseIndent(); fields().forEach(field -> poetWriter.writeIndentedLine(String.format( "self._%s = %s", diff --git a/conjure-python-core/src/main/java/com/palantir/conjure/python/poet/PythonEndpointDefinition.java b/conjure-python-core/src/main/java/com/palantir/conjure/python/poet/PythonEndpointDefinition.java index 1425fb491..e0b53d02d 100644 --- a/conjure-python-core/src/main/java/com/palantir/conjure/python/poet/PythonEndpointDefinition.java +++ b/conjure-python-core/src/main/java/com/palantir/conjure/python/poet/PythonEndpointDefinition.java @@ -62,6 +62,11 @@ public interface PythonEndpointDefinition extends Emittable { Optional myPyReturnType(); + @Value.Default + default boolean forceKeywordArgs() { + return false; + } + @Value.Check default void check() { checkState( @@ -90,22 +95,27 @@ default void emit(PythonPoetWriter poetWriter) { .build() : params(); - poetWriter.writeIndentedLine( - "def %s(self, %s) -> %s:", - pythonMethodName(), - Joiner.on(", ") - .join(paramsWithHeader.stream() - .sorted(new PythonEndpointParamComparator()) - .map(param -> { - String typedParam = - String.format("%s: %s", param.pythonParamName(), param.myPyType()); - if (param.isOptional() || param.isCollection()) { - return String.format("%s = None", typedParam); - } - return typedParam; - }) - .collect(Collectors.toList())), - myPyReturnType().orElse("None")); + String paramsString = Joiner.on(", ") + .join(paramsWithHeader.stream() + .sorted(new PythonEndpointParamComparator()) + .map(param -> { + String typedParam = String.format("%s: %s", param.pythonParamName(), param.myPyType()); + if (param.isOptional() || param.isCollection()) { + return String.format("%s = None", typedParam); + } + return typedParam; + }) + .collect(Collectors.toList())); + + String methodSignature = forceKeywordArgs() && !paramsWithHeader.isEmpty() + ? String.format( + "def %s(self, *, %s) -> %s:", + pythonMethodName(), paramsString, myPyReturnType().orElse("None")) + : String.format( + "def %s(self, %s) -> %s:", + pythonMethodName(), paramsString, myPyReturnType().orElse("None")); + + poetWriter.writeIndentedLine(methodSignature); poetWriter.increaseIndent(); docs().ifPresent(poetWriter::writeDocs); diff --git a/conjure-python-core/src/main/java/com/palantir/conjure/python/poet/UnionSnippet.java b/conjure-python-core/src/main/java/com/palantir/conjure/python/poet/UnionSnippet.java index 73e5466f1..28fca5977 100644 --- a/conjure-python-core/src/main/java/com/palantir/conjure/python/poet/UnionSnippet.java +++ b/conjure-python-core/src/main/java/com/palantir/conjure/python/poet/UnionSnippet.java @@ -61,6 +61,11 @@ default String idForSorting() { List options(); + @Value.Default + default boolean forceKeywordArgs() { + return false; + } + /** The name of the option as a constructor / method parameter. */ static String parameterName(PythonField option) { return PythonIdentifierSanitizer.sanitize(option.attributeName()); @@ -120,6 +125,9 @@ default void emit(PythonPoetWriter poetWriter) { poetWriter.increaseIndent(); poetWriter.increaseIndent(); poetWriter.writeIndentedLine("self,"); + if (forceKeywordArgs() && !options().isEmpty()) { + poetWriter.writeIndentedLine("*,"); + } for (int i = 0; i < options().size(); i++) { PythonField option = options().get(i); diff --git a/conjure-python-core/src/main/java/com/palantir/conjure/python/types/PythonTypeGenerator.java b/conjure-python-core/src/main/java/com/palantir/conjure/python/types/PythonTypeGenerator.java index f1e29d9cf..51ae5ab7e 100644 --- a/conjure-python-core/src/main/java/com/palantir/conjure/python/types/PythonTypeGenerator.java +++ b/conjure-python-core/src/main/java/com/palantir/conjure/python/types/PythonTypeGenerator.java @@ -50,18 +50,21 @@ public final class PythonTypeGenerator { private final DealiasingTypeVisitor dealiasingTypeVisitor; private final PythonTypeNameVisitor pythonTypeNameVisitor; private final MyPyTypeNameVisitor myPyTypeNameVisitor; + private final boolean forceKeywordArgs; public PythonTypeGenerator( PackageNameProcessor implPackageNameProcessor, TypeNameProcessor implTypeNameProcessor, PackageNameProcessor definitionPackageNameProcessor, TypeNameProcessor definitionTypeNameProcessor, - DealiasingTypeVisitor dealiasingTypeVisitor) { + DealiasingTypeVisitor dealiasingTypeVisitor, + boolean forceKeywordArgs) { this.implPackageNameProcessor = implPackageNameProcessor; this.implTypeNameProcessor = implTypeNameProcessor; this.definitionPackageNameProcessor = definitionPackageNameProcessor; this.definitionTypeNameProcessor = definitionTypeNameProcessor; this.dealiasingTypeVisitor = dealiasingTypeVisitor; + this.forceKeywordArgs = forceKeywordArgs; pythonTypeNameVisitor = new PythonTypeNameVisitor(implTypeNameProcessor); myPyTypeNameVisitor = new MyPyTypeNameVisitor(dealiasingTypeVisitor, implTypeNameProcessor); } @@ -127,6 +130,7 @@ private BeanSnippet generateBean(ObjectDefinition typeDef) { .addAllImports(imports) .docs(typeDef.getDocs()) .fields(fields) + .forceKeywordArgs(forceKeywordArgs) .build(); } @@ -182,6 +186,7 @@ private UnionSnippet generateUnion(UnionDefinition typeDef) { .addAllImports(imports) .docs(typeDef.getDocs()) .options(options) + .forceKeywordArgs(forceKeywordArgs) .build(); } diff --git a/conjure-python-core/src/test/java/com/palantir/conjure/python/ForceKeywordArgsTest.java b/conjure-python-core/src/test/java/com/palantir/conjure/python/ForceKeywordArgsTest.java new file mode 100644 index 000000000..a2ce01978 --- /dev/null +++ b/conjure-python-core/src/test/java/com/palantir/conjure/python/ForceKeywordArgsTest.java @@ -0,0 +1,104 @@ +/* + * (c) Copyright 2025 Palantir Technologies Inc. All rights reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.palantir.conjure.python; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.palantir.conjure.defs.Conjure; +import com.palantir.conjure.spec.ConjureDefinition; +import java.io.File; +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; +import org.junit.jupiter.api.Test; + +public final class ForceKeywordArgsTest { + + @Test + public void testConstructorKeywordArgsAreForced() throws IOException { + String generated = generateCode(true); + + assertThat(generated).contains("def __init__(self, *, age: int, email: str, name: str) -> None:"); + } + + @Test + public void testConstructorPositionalArgsByDefault() throws IOException { + String generated = generateCode(false); + + assertThat(generated).contains("def __init__(self, age: int, email: str, name: str) -> None:"); + assertThat(generated).doesNotContain("def __init__(self, *,"); + } + + @Test + public void testUnionKeywordArgsAreForced() throws IOException { + String generated = generateCode(true); + // The signature spans multiple lines with specific indentation + assertThat(generated).contains(" def __init__(\n self,\n *,"); + } + + @Test + public void testUnionPositionalArgsByDefault() throws IOException { + String generated = generateCode(false); + + // The signature spans multiple lines with specific indentation + assertThat(generated).contains(" def __init__(\n self,\n foo: Optional[str] = None,"); + assertThat(generated).doesNotContain(" def __init__(\n self,\n *,"); + } + + @Test + public void testServiceEndpointKeywordArgsAreForced() throws IOException { + String generated = generateCode(true); + assertThat(generated).contains("def test_endpoint(self, *, param1: str, param2: int) -> str:"); + } + + @Test + public void testServiceEndpointPositionalArgsByDefault() throws IOException { + String generated = generateCode(false); + assertThat(generated).contains("def test_endpoint(self, param1: str, param2: int) -> str:"); + assertThat(generated).doesNotContain("def test_endpoint(self, *,"); + } + + @SuppressWarnings("for-rollout:deprecation") + private String generateCode(boolean forceKeywordArgs) throws IOException { + ConjurePythonGenerator generator = new ConjurePythonGenerator(GeneratorConfiguration.builder() + .packageName("test") + .packageVersion("0.0.0") + .minConjureClientVersion("2.8.0") + .generatorVersion("0.0.0") + .shouldWriteCondaRecipe(false) + .generateRawSource(false) + .forceKeywordArgs(forceKeywordArgs) + .build()); + + Path testFolder = Path.of("src/test/resources/force-keyword-args"); + List files; + try (Stream walk = Files.walk(testFolder)) { + files = walk.map(Path::toFile) + .filter(file -> file.toString().endsWith(".yml")) + .collect(Collectors.toList()); + } + ConjureDefinition definition = Conjure.parse(files); + + InMemoryPythonFileWriter writer = new InMemoryPythonFileWriter(); + generator.write(definition, writer); + + return writer.getPythonFiles().values().stream().collect(Collectors.joining("\n")); + } +} diff --git a/conjure-python-core/src/test/resources/force-keyword-args/expected/conda_recipe/meta.yaml b/conjure-python-core/src/test/resources/force-keyword-args/expected/conda_recipe/meta.yaml new file mode 100644 index 000000000..398009c87 --- /dev/null +++ b/conjure-python-core/src/test/resources/force-keyword-args/expected/conda_recipe/meta.yaml @@ -0,0 +1,23 @@ +# coding=utf-8 +package: + name: package-name + version: 0.0.0 + +source: + path: ../ + +build: + noarch: python + script: python setup.py install --single-version-externally-managed --record=record.txt + +requirements: + build: + - python + - setuptools + - requests + - conjure-python-client >=2.8.0,<4 + + run: + - python + - requests + - conjure-python-client >=2.8.0,<4 diff --git a/conjure-python-core/src/test/resources/force-keyword-args/expected/package_name/__init__.py b/conjure-python-core/src/test/resources/force-keyword-args/expected/package_name/__init__.py new file mode 100644 index 000000000..4bbfed34e --- /dev/null +++ b/conjure-python-core/src/test/resources/force-keyword-args/expected/package_name/__init__.py @@ -0,0 +1,9 @@ +# coding=utf-8 +__all__ = [ + 'test_api', +] + +__conjure_generator_version__ = "0.0.0" + +__version__ = "0.0.0" + diff --git a/conjure-python-core/src/test/resources/force-keyword-args/expected/package_name/_impl.py b/conjure-python-core/src/test/resources/force-keyword-args/expected/package_name/_impl.py new file mode 100644 index 000000000..7263338d2 --- /dev/null +++ b/conjure-python-core/src/test/resources/force-keyword-args/expected/package_name/_impl.py @@ -0,0 +1,176 @@ +# coding=utf-8 +from abc import ( + abstractmethod, +) +import builtins +from conjure_python_client import ( + ConjureBeanType, + ConjureDecoder, + ConjureEncoder, + ConjureFieldDefinition, + ConjureUnionType, + Service, +) +from requests.adapters import ( + Response, +) +from typing import ( + Any, + Dict, + List, + Optional, +) +from urllib.parse import ( + quote, +) + +class test_api_KeywordArgsTest(ConjureBeanType): + + @builtins.classmethod + def _fields(cls) -> Dict[str, ConjureFieldDefinition]: + return { + 'name': ConjureFieldDefinition('name', str), + 'age': ConjureFieldDefinition('age', int), + 'email': ConjureFieldDefinition('email', str) + } + + __slots__: List[str] = ['_name', '_age', '_email'] + + def __init__(self, age: int, email: str, name: str) -> None: + self._name = name + self._age = age + self._email = email + + @builtins.property + def name(self) -> str: + return self._name + + @builtins.property + def age(self) -> int: + return self._age + + @builtins.property + def email(self) -> str: + return self._email + + +test_api_KeywordArgsTest.__name__ = "KeywordArgsTest" +test_api_KeywordArgsTest.__qualname__ = "KeywordArgsTest" +test_api_KeywordArgsTest.__module__ = "package_name.test_api" + + +class test_api_TestService(Service): + + def test_endpoint(self, param1: str, param2: int) -> str: + _conjure_encoder = ConjureEncoder() + + _headers: Dict[str, Any] = { + 'Accept': 'application/json', + } + + _params: Dict[str, Any] = { + 'param1': _conjure_encoder.default(param1), + 'param2': _conjure_encoder.default(param2), + } + + _path_params: Dict[str, str] = { + } + + _json: Any = None + + _path = '/test/test' + _path = _path.format(**_path_params) + + _response: Response = self._request( + 'POST', + self._uri + _path, + params=_params, + headers=_headers, + json=_json) + + _decoder = ConjureDecoder() + return _decoder.decode(_response.json(), str, self._return_none_for_unknown_union_types) + + +test_api_TestService.__name__ = "TestService" +test_api_TestService.__qualname__ = "TestService" +test_api_TestService.__module__ = "package_name.test_api" + + +class test_api_UnionTest(ConjureUnionType): + _foo: Optional[str] = None + _bar: Optional[int] = None + + @builtins.classmethod + def _options(cls) -> Dict[str, ConjureFieldDefinition]: + return { + 'foo': ConjureFieldDefinition('foo', str), + 'bar': ConjureFieldDefinition('bar', int) + } + + def __init__( + self, + foo: Optional[str] = None, + bar: Optional[int] = None, + type_of_union: Optional[str] = None + ) -> None: + if type_of_union is None: + if (foo is not None) + (bar is not None) != 1: + raise ValueError('a union must contain a single member') + + if foo is not None: + self._foo = foo + self._type = 'foo' + if bar is not None: + self._bar = bar + self._type = 'bar' + + elif type_of_union == 'foo': + if foo is None: + raise ValueError('a union value must not be None') + self._foo = foo + self._type = 'foo' + elif type_of_union == 'bar': + if bar is None: + raise ValueError('a union value must not be None') + self._bar = bar + self._type = 'bar' + + @builtins.property + def foo(self) -> Optional[str]: + return self._foo + + @builtins.property + def bar(self) -> Optional[int]: + return self._bar + + def accept(self, visitor) -> Any: + if not isinstance(visitor, test_api_UnionTestVisitor): + raise ValueError('{} is not an instance of test_api_UnionTestVisitor'.format(visitor.__class__.__name__)) + if self._type == 'foo' and self.foo is not None: + return visitor._foo(self.foo) + if self._type == 'bar' and self.bar is not None: + return visitor._bar(self.bar) + + +test_api_UnionTest.__name__ = "UnionTest" +test_api_UnionTest.__qualname__ = "UnionTest" +test_api_UnionTest.__module__ = "package_name.test_api" + + +class test_api_UnionTestVisitor: + + @abstractmethod + def _foo(self, foo: str) -> Any: + pass + + @abstractmethod + def _bar(self, bar: int) -> Any: + pass + + +test_api_UnionTestVisitor.__name__ = "UnionTestVisitor" +test_api_UnionTestVisitor.__qualname__ = "UnionTestVisitor" +test_api_UnionTestVisitor.__module__ = "package_name.test_api" + + diff --git a/conjure-python-core/src/test/resources/force-keyword-args/expected/package_name/py.typed b/conjure-python-core/src/test/resources/force-keyword-args/expected/package_name/py.typed new file mode 100644 index 000000000..9bad5790a --- /dev/null +++ b/conjure-python-core/src/test/resources/force-keyword-args/expected/package_name/py.typed @@ -0,0 +1 @@ +# coding=utf-8 diff --git a/conjure-python-core/src/test/resources/force-keyword-args/expected/package_name/test_api/__init__.py b/conjure-python-core/src/test/resources/force-keyword-args/expected/package_name/test_api/__init__.py new file mode 100644 index 000000000..388acc17f --- /dev/null +++ b/conjure-python-core/src/test/resources/force-keyword-args/expected/package_name/test_api/__init__.py @@ -0,0 +1,15 @@ +# coding=utf-8 +from .._impl import ( + test_api_KeywordArgsTest as KeywordArgsTest, + test_api_TestService as TestService, + test_api_UnionTest as UnionTest, + test_api_UnionTestVisitor as UnionTestVisitor, +) + +__all__ = [ + 'KeywordArgsTest', + 'UnionTest', + 'UnionTestVisitor', + 'TestService', +] + diff --git a/conjure-python-core/src/test/resources/force-keyword-args/expected/setup.py b/conjure-python-core/src/test/resources/force-keyword-args/expected/setup.py new file mode 100644 index 000000000..42ad2060a --- /dev/null +++ b/conjure-python-core/src/test/resources/force-keyword-args/expected/setup.py @@ -0,0 +1,18 @@ +# coding=utf-8 +from setuptools import ( + find_packages, + setup, +) + +setup( + name='package-name', + version='0.0.0', + python_requires='>=3.8', + description='project description', + package_data={"": ["py.typed"]}, + packages=find_packages(), + install_requires=[ + 'requests', + 'conjure-python-client>=2.8.0,<4', + ], +) diff --git a/conjure-python-core/src/test/resources/force-keyword-args/test-kwargs.yml b/conjure-python-core/src/test/resources/force-keyword-args/test-kwargs.yml new file mode 100644 index 000000000..bd426cd5e --- /dev/null +++ b/conjure-python-core/src/test/resources/force-keyword-args/test-kwargs.yml @@ -0,0 +1,29 @@ +types: + definitions: + default-package: test.api + objects: + KeywordArgsTest: + fields: + name: string + age: integer + email: string + UnionTest: + union: + foo: string + bar: integer +services: + TestService: + name: Test Service + package: test.api + base-path: /test + endpoints: + testEndpoint: + http: POST /test + args: + param1: + type: string + param-type: query + param2: + type: integer + param-type: query + returns: string diff --git a/conjure-python/src/main/java/com/palantir/conjure/python/cli/CliConfiguration.java b/conjure-python/src/main/java/com/palantir/conjure/python/cli/CliConfiguration.java index ad0294b27..564ef63ac 100644 --- a/conjure-python/src/main/java/com/palantir/conjure/python/cli/CliConfiguration.java +++ b/conjure-python/src/main/java/com/palantir/conjure/python/cli/CliConfiguration.java @@ -51,6 +51,12 @@ boolean shouldWriteCondaRecipe() { return false; } + @Value.Default + @SuppressWarnings("DesignForExtension") + boolean forceKeywordArgs() { + return false; + } + @Value.Check final void check() { Preconditions.checkArgument(input().isFile(), "Target must exist and be a file"); diff --git a/conjure-python/src/main/java/com/palantir/conjure/python/cli/ConjurePythonCli.java b/conjure-python/src/main/java/com/palantir/conjure/python/cli/ConjurePythonCli.java index f46d25921..b9c4d17f7 100644 --- a/conjure-python/src/main/java/com/palantir/conjure/python/cli/ConjurePythonCli.java +++ b/conjure-python/src/main/java/com/palantir/conjure/python/cli/ConjurePythonCli.java @@ -89,6 +89,13 @@ public static final class GenerateCommand implements Runnable { description = "Generate a `conda_recipe/meta.yaml`") private boolean writeCondaRecipe; + @CommandLine.Option( + names = "--force-keyword-args", + defaultValue = "false", + description = "Force keyword-only arguments in generated constructors. " + + "Will become default in the next major release.") + private boolean forceKeywordArgs; + @CommandLine.Unmatched @SuppressWarnings("StrictUnusedVariable") private List unmatchedOptions; @@ -118,6 +125,7 @@ CliConfiguration getConfiguration() { .packageUrl(Optional.ofNullable(packageUrl)) .generateRawSource(rawSource) .shouldWriteCondaRecipe(writeCondaRecipe) + .forceKeywordArgs(forceKeywordArgs) .build(); } @@ -134,6 +142,7 @@ static GeneratorConfiguration resolveGeneratorConfiguration( .packageUrl(cliConfig.packageUrl()) .shouldWriteCondaRecipe(cliConfig.shouldWriteCondaRecipe()) .generateRawSource(cliConfig.generateRawSource()) + .forceKeywordArgs(cliConfig.forceKeywordArgs()) .build(); } }