pythonKeywords = ImmutableSet.of(
+ "and",
+ "as",
+ "assert",
+ "break",
+ "class",
+ "continue",
+ "def",
+ "del",
+ "elif",
+ "else",
+ "except",
+ "exec",
+ "finally",
+ "for",
+ "from",
+ "global",
+ "if",
+ "import",
+ "in",
+ "is",
+ "lambda",
+ "not",
+ "or",
+ "pass",
+ "print",
+ "raise",
+ "return",
+ "try",
+ "while",
+ "with",
+ "yield");
+
+ /**
+ * If the identifier is a python keyword, prepends "_".
+ *
+ * Does no case conversion.
+ */
+ public static String sanitize(String identifier) {
+ return isKeyword(identifier) ? identifier + "_" : identifier;
+ }
+
+ public static boolean isKeyword(String identifier) {
+ return pythonKeywords.contains(identifier);
+ }
+
+ private PythonIdentifierSanitizer() {}
+
+}
diff --git a/conjure-python-core/src/main/java/com/palantir/conjure/python/poet/PythonImport.java b/conjure-python-core/src/main/java/com/palantir/conjure/python/poet/PythonImport.java
new file mode 100644
index 000000000..c3bf9144a
--- /dev/null
+++ b/conjure-python-core/src/main/java/com/palantir/conjure/python/poet/PythonImport.java
@@ -0,0 +1,62 @@
+/*
+ * (c) Copyright 2018 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.poet;
+
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Optional;
+import org.immutables.value.Value;
+
+@Value.Immutable
+public interface PythonImport extends Emittable, Comparable {
+
+ PythonClassName className();
+
+ Optional relativeToPackage();
+
+ static PythonImport of(PythonClassName className) {
+ return ImmutablePythonImport.builder().className(className).build();
+ }
+
+ static PythonImport of(PythonClassName className, String relativeToPackage) {
+ PythonClassName relativizedClassName = PythonClassName.of(
+ relativePackage(relativeToPackage, className.conjurePackage()),
+ className.className());
+ return ImmutablePythonImport.builder().className(relativizedClassName).build();
+ }
+
+ @Override
+ default void emit(PythonPoetWriter poetWriter) {
+ poetWriter.writeIndentedLine(String.format("from %s import %s",
+ className().conjurePackage(), className().className()));
+ }
+
+ @Override
+ default int compareTo(PythonImport other) {
+ return className().compareTo(other.className());
+ }
+
+ static String relativePackage(String curPackage, String toPackage) {
+ if (curPackage.equals(toPackage)) {
+ return ".";
+ }
+ Path curPath = Paths.get(curPackage.replace(".", "/"));
+ Path toPath = Paths.get(toPackage.replace(".", "/"));
+ Path relativeImport = curPath.relativize(toPath);
+ return relativeImport.toString().replace("../", "..").replace("/", ".");
+ }
+}
diff --git a/conjure-python-core/src/main/java/com/palantir/conjure/python/poet/PythonLine.java b/conjure-python-core/src/main/java/com/palantir/conjure/python/poet/PythonLine.java
new file mode 100644
index 000000000..eeb953ac4
--- /dev/null
+++ b/conjure-python-core/src/main/java/com/palantir/conjure/python/poet/PythonLine.java
@@ -0,0 +1,30 @@
+/*
+ * (c) Copyright 2018 Palantir Technologies Inc. All rights reserved.
+ */
+
+package com.palantir.conjure.python.poet;
+
+import org.immutables.value.Value;
+
+@Value.Immutable
+public interface PythonLine extends PythonClass {
+ String text();
+
+ @Override
+ @Value.Default
+ default String className() {
+ return "";
+ }
+
+ @Override
+ default void emit(PythonPoetWriter poetWriter) {
+ poetWriter.writeIndentedLine(text());
+ poetWriter.writeLine();
+ }
+
+ class Builder extends ImmutablePythonLine.Builder {}
+
+ static Builder builder() {
+ return new Builder();
+ }
+}
diff --git a/conjure-python-core/src/main/java/com/palantir/conjure/python/poet/PythonMetaYaml.java b/conjure-python-core/src/main/java/com/palantir/conjure/python/poet/PythonMetaYaml.java
new file mode 100644
index 000000000..d59b60247
--- /dev/null
+++ b/conjure-python-core/src/main/java/com/palantir/conjure/python/poet/PythonMetaYaml.java
@@ -0,0 +1,77 @@
+/*
+ * (c) Copyright 2018 Palantir Technologies Inc. All rights reserved.
+ */
+
+package com.palantir.conjure.python.poet;
+
+import java.util.List;
+import org.immutables.value.Value;
+
+@Value.Immutable
+public interface PythonMetaYaml extends PythonClass {
+
+ @Override
+ default String className() {
+ return "";
+ }
+
+ String condaPackageName();
+
+ String packageVersion();
+
+ List installDependencies();
+
+ @Override
+ default void emit(PythonPoetWriter poetWriter) {
+ poetWriter.maintainingIndent(() -> {
+ poetWriter.writeIndentedLine("package:");
+ poetWriter.increaseIndent();
+ poetWriter.writeIndentedLine("name: %s", condaPackageName());
+ poetWriter.writeIndentedLine("version: %s", packageVersion());
+ poetWriter.decreaseIndent();
+
+ poetWriter.writeLine();
+
+ poetWriter.writeIndentedLine("source:");
+ poetWriter.increaseIndent();
+ poetWriter.writeIndentedLine("path: ../");
+ poetWriter.decreaseIndent();
+
+ poetWriter.writeLine();
+
+ poetWriter.writeIndentedLine("build:");
+ poetWriter.increaseIndent();
+ poetWriter.writeIndentedLine("noarch_python: True");
+ poetWriter.writeIndentedLine("script: python setup.py install");
+ poetWriter.decreaseIndent();
+
+ poetWriter.writeLine();
+
+ poetWriter.writeIndentedLine("requirements:");
+ poetWriter.increaseIndent();
+ poetWriter.writeIndentedLine("build:");
+ poetWriter.increaseIndent();
+ poetWriter.writeIndentedLine("- python");
+ poetWriter.writeIndentedLine("- setuptools");
+ installDependencies().forEach(dependency -> {
+ poetWriter.writeIndentedLine("- %s", dependency);
+ });
+ poetWriter.decreaseIndent();
+ poetWriter.writeLine();
+ poetWriter.writeIndentedLine("run:");
+ poetWriter.increaseIndent();
+ poetWriter.writeIndentedLine("- python");
+ installDependencies().forEach(dependency -> {
+ poetWriter.writeIndentedLine("- %s", dependency);
+ });
+ poetWriter.decreaseIndent();
+ poetWriter.decreaseIndent();
+ });
+ }
+
+ class Builder extends ImmutablePythonMetaYaml.Builder {}
+
+ static Builder builder() {
+ return new Builder();
+ }
+}
diff --git a/conjure-python-core/src/main/java/com/palantir/conjure/python/poet/PythonPoetWriter.java b/conjure-python-core/src/main/java/com/palantir/conjure/python/poet/PythonPoetWriter.java
new file mode 100644
index 000000000..372f0bea8
--- /dev/null
+++ b/conjure-python-core/src/main/java/com/palantir/conjure/python/poet/PythonPoetWriter.java
@@ -0,0 +1,97 @@
+/*
+ * (c) Copyright 2018 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.poet;
+
+import static com.google.common.base.Preconditions.checkState;
+
+import java.io.PrintStream;
+
+public final class PythonPoetWriter {
+
+ private static final String INDENT = " ";
+
+ private int indent;
+ private PrintStream printStream;
+
+ public PythonPoetWriter(PrintStream printStream) {
+ this.indent = 0;
+ this.printStream = printStream;
+ }
+
+ /**
+ * Asserts that the code in runnable leaves the indent unchanged.
+ */
+ public PythonPoetWriter maintainingIndent(Runnable runnable) {
+ int startIndent = indent;
+ runnable.run();
+ checkState(indent == startIndent, "expected indent to be unchanged");
+ return this;
+ }
+
+ public PythonPoetWriter decreaseIndent() {
+ this.indent--;
+ return this;
+ }
+
+ public PythonPoetWriter increaseIndent() {
+ this.indent++;
+ return this;
+ }
+
+ public PythonPoetWriter write(String content) {
+ printStream.print(content);
+ return this;
+ }
+
+ public PythonPoetWriter writeLine() {
+ printStream.print("\n");
+ return this;
+ }
+
+ public PythonPoetWriter writeLine(String content) {
+ printStream.print(content);
+ return writeLine();
+ }
+
+ public PythonPoetWriter writeIndented() {
+ for (int i = 0; i < indent; i++) {
+ printStream.print(INDENT);
+ }
+ return this;
+ }
+
+ public PythonPoetWriter writeIndented(String content) {
+ writeIndented();
+ write(content);
+ return this;
+ }
+
+ public PythonPoetWriter writeIndentedLine(String content) {
+ writeIndented(content);
+ writeLine();
+ return this;
+ }
+
+ public PythonPoetWriter writeIndentedLine(String formatString, Object... args) {
+ return writeIndentedLine(String.format(formatString, args));
+ }
+
+ public PythonPoetWriter emit(Emittable emittable) {
+ emittable.emit(this);
+ return this;
+ }
+}
diff --git a/conjure-python-core/src/main/java/com/palantir/conjure/python/poet/PythonService.java b/conjure-python-core/src/main/java/com/palantir/conjure/python/poet/PythonService.java
new file mode 100644
index 000000000..dd3d029bd
--- /dev/null
+++ b/conjure-python-core/src/main/java/com/palantir/conjure/python/poet/PythonService.java
@@ -0,0 +1,63 @@
+/*
+ * (c) Copyright 2018 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.poet;
+
+import com.google.common.collect.ImmutableSet;
+import java.util.List;
+import java.util.Set;
+import org.immutables.value.Value;
+
+@Value.Immutable
+public interface PythonService extends PythonClass {
+
+ ImmutableSet DEFAULT_IMPORTS = ImmutableSet.of(
+ PythonImport.of(PythonClassName.of("conjure", "ConjureEncoder")),
+ PythonImport.of(PythonClassName.of("conjure", "ConjureDecoder")),
+ PythonImport.of(PythonClassName.of("httpremoting", "Service")));
+
+ @Override
+ @Value.Default
+ default Set requiredImports() {
+ return DEFAULT_IMPORTS;
+ }
+
+ List endpointDefinitions();
+
+ @Override
+ default void emit(PythonPoetWriter poetWriter) {
+ poetWriter.maintainingIndent(() -> {
+ poetWriter.writeIndentedLine(String.format("class %s(Service):", className()));
+ poetWriter.increaseIndent();
+ docs().ifPresent(docs -> poetWriter.writeIndentedLine(String.format("\"\"\"%s\"\"\"", docs.get().trim())));
+
+ endpointDefinitions().forEach(endpointDefinition -> {
+ poetWriter.writeLine();
+ endpointDefinition.emit(poetWriter);
+ });
+ poetWriter.writeLine();
+
+ poetWriter.decreaseIndent();
+ });
+ }
+
+ class Builder extends ImmutablePythonService.Builder {}
+
+ static Builder builder() {
+ return new Builder();
+ }
+
+}
diff --git a/conjure-python-core/src/main/java/com/palantir/conjure/python/poet/PythonSetup.java b/conjure-python-core/src/main/java/com/palantir/conjure/python/poet/PythonSetup.java
new file mode 100644
index 000000000..41135a55f
--- /dev/null
+++ b/conjure-python-core/src/main/java/com/palantir/conjure/python/poet/PythonSetup.java
@@ -0,0 +1,66 @@
+/*
+ * (c) Copyright 2018 Palantir Technologies Inc. All rights reserved.
+ */
+
+package com.palantir.conjure.python.poet;
+
+import com.google.common.collect.ImmutableSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import org.immutables.value.Value;
+
+@Value.Immutable
+public interface PythonSetup extends PythonClass {
+
+ ImmutableSet DEFAULT_IMPORTS = ImmutableSet.of(
+ PythonImport.of(PythonClassName.of("setuptools", "find_packages")),
+ PythonImport.of(PythonClassName.of("setuptools", "setup")));
+
+ @Override
+ @Value.Default
+ default String className() {
+ return "";
+ }
+
+ @Override
+ @Value.Default
+ default Set requiredImports() {
+ return DEFAULT_IMPORTS;
+ }
+
+ Map options();
+
+ List installDependencies();
+
+ @Override
+ default void emit(PythonPoetWriter poetWriter) {
+ poetWriter.maintainingIndent(() -> {
+ poetWriter.writeIndentedLine("setup(");
+ poetWriter.increaseIndent();
+
+ options().forEach((key, value) -> {
+ poetWriter.writeIndentedLine("%s='%s',", key, value);
+ });
+ poetWriter.writeIndentedLine("packages=find_packages(),");
+
+ poetWriter.writeIndentedLine("install_requires=[");
+ poetWriter.increaseIndent();
+ installDependencies().forEach(dependency -> {
+ poetWriter.writeIndentedLine("'%s',", dependency);
+ });
+ poetWriter.decreaseIndent();
+ poetWriter.writeIndentedLine("],");
+
+ poetWriter.decreaseIndent();
+ poetWriter.writeIndentedLine(")");
+ });
+ }
+
+ class Builder extends ImmutablePythonSetup.Builder {}
+
+ static Builder builder() {
+ return new Builder();
+ }
+
+}
diff --git a/conjure-python-core/src/main/java/com/palantir/conjure/python/poet/PythonUnionTypeDefinition.java b/conjure-python-core/src/main/java/com/palantir/conjure/python/poet/PythonUnionTypeDefinition.java
new file mode 100644
index 000000000..33303d174
--- /dev/null
+++ b/conjure-python-core/src/main/java/com/palantir/conjure/python/poet/PythonUnionTypeDefinition.java
@@ -0,0 +1,133 @@
+/*
+ * (c) Copyright 2018 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.poet;
+
+import com.google.common.base.Joiner;
+import com.google.common.collect.ImmutableSet;
+import com.palantir.conjure.python.poet.PythonBean.PythonField;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+import org.immutables.value.Value;
+
+@Value.Immutable
+public interface PythonUnionTypeDefinition extends PythonClass {
+
+ ImmutableSet DEFAULT_IMPORTS = ImmutableSet.of(
+ PythonImport.of(PythonClassName.of("conjure", "ConjureUnionType")),
+ PythonImport.of(PythonClassName.of("conjure", "ConjureFieldDefinition")));
+
+ @Override
+ @Value.Default
+ default Set requiredImports() {
+ return DEFAULT_IMPORTS;
+ }
+
+ List options();
+
+ @Override
+ default void emit(PythonPoetWriter poetWriter) {
+ poetWriter.writeIndentedLine(String.format("class %s(ConjureUnionType):", className()));
+ poetWriter.increaseIndent();
+ docs().ifPresent(docs -> poetWriter.writeIndentedLine(String.format("\"\"\"%s\"\"\"", docs.get().trim())));
+
+ poetWriter.writeLine();
+
+ options().forEach(option -> poetWriter.writeIndentedLine("_%s = None # type: %s",
+ option.attributeName(), option.myPyType()));
+
+ poetWriter.writeLine();
+
+ // record off the options
+ poetWriter.writeIndentedLine("@classmethod");
+ poetWriter.writeIndentedLine("def _options(cls):");
+ poetWriter.increaseIndent();
+ poetWriter.writeIndentedLine("# type: () -> Dict[str, ConjureFieldDefinition]"); // maybe....?
+ poetWriter.writeIndentedLine("return {");
+ poetWriter.increaseIndent();
+ for (int i = 0; i < options().size(); i++) {
+ PythonField option = options().get(i);
+ poetWriter.writeIndentedLine("'%s': ConjureFieldDefinition('%s', %s)%s",
+ PythonIdentifierSanitizer.sanitize(option.attributeName()),
+ option.jsonIdentifier(),
+ option.pythonType(),
+ i == options().size() - 1 ? "" : ",");
+ }
+ poetWriter.decreaseIndent();
+ poetWriter.writeIndentedLine("}");
+ poetWriter.decreaseIndent();
+
+ poetWriter.writeLine();
+
+ // constructor
+ poetWriter.writeIndentedLine(String.format("def __init__(self, %s):",
+ Joiner.on(", ").join(
+ options().stream()
+ .map(PythonField::attributeName)
+ .map(PythonIdentifierSanitizer::sanitize)
+ .map(attributeName -> String.format("%s=None", attributeName))
+ .collect(Collectors.toList()))));
+ poetWriter.increaseIndent();
+ // check we have exactly one non-null
+ poetWriter.writeIndentedLine("if %s != 1:",
+ Joiner.on(" + ").join(
+ options().stream()
+ .map(option -> String.format("(%s is not None)",
+ PythonIdentifierSanitizer.sanitize(option.attributeName())))
+ .collect(Collectors.toList())));
+ poetWriter.increaseIndent();
+ poetWriter.writeIndentedLine("raise ValueError('a union must contain a single member')");
+ poetWriter.decreaseIndent();
+ // keep track of how many non-null there are
+ poetWriter.writeLine();
+ // save off
+ options().forEach(option -> {
+ poetWriter.writeIndentedLine("if %s is not None:",
+ PythonIdentifierSanitizer.sanitize(option.attributeName()));
+ poetWriter.increaseIndent();
+ poetWriter.writeIndentedLine("self._%s = %s", option.attributeName(),
+ PythonIdentifierSanitizer.sanitize(option.attributeName()));
+ poetWriter.writeIndentedLine("self._type = '%s'", option.jsonIdentifier());
+ poetWriter.decreaseIndent();
+ });
+ poetWriter.decreaseIndent();
+
+ // python @property for each member of the union
+ options().forEach(option -> {
+ poetWriter.writeLine();
+ poetWriter.writeIndentedLine("@property");
+ poetWriter.writeIndentedLine(String.format("def %s(self):",
+ PythonIdentifierSanitizer.sanitize(option.attributeName())));
+
+ poetWriter.increaseIndent();
+ poetWriter.writeIndentedLine(String.format("# type: () -> %s", option.myPyType()));
+ option.docs().ifPresent(docs -> poetWriter.writeIndentedLine(String.format("\"\"\"%s\"\"\"",
+ docs.get().trim())));
+ poetWriter.writeIndentedLine(String.format("return self._%s", option.attributeName()));
+ poetWriter.decreaseIndent();
+ });
+
+ poetWriter.decreaseIndent();
+ poetWriter.writeLine();
+ }
+
+ class Builder extends ImmutablePythonUnionTypeDefinition.Builder {}
+
+ static Builder builder() {
+ return new Builder();
+ }
+}
diff --git a/conjure-python-core/src/main/java/com/palantir/conjure/python/service/ServiceGeneratorPython.java b/conjure-python-core/src/main/java/com/palantir/conjure/python/service/ServiceGeneratorPython.java
new file mode 100644
index 000000000..c47e107b0
--- /dev/null
+++ b/conjure-python-core/src/main/java/com/palantir/conjure/python/service/ServiceGeneratorPython.java
@@ -0,0 +1,113 @@
+/*
+ * (c) Copyright 2018 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.service;
+
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.ImmutableSet.Builder;
+import com.palantir.conjure.python.PackageNameProcessor;
+import com.palantir.conjure.python.PythonFileGenerator;
+import com.palantir.conjure.python.poet.PythonEndpointDefinition;
+import com.palantir.conjure.python.poet.PythonEndpointDefinition.PythonEndpointParam;
+import com.palantir.conjure.python.poet.PythonFile;
+import com.palantir.conjure.python.poet.PythonImport;
+import com.palantir.conjure.python.poet.PythonService;
+import com.palantir.conjure.python.types.DefaultTypeNameVisitor;
+import com.palantir.conjure.python.types.MyPyTypeNameVisitor;
+import com.palantir.conjure.python.types.TypeMapper;
+import com.palantir.conjure.python.util.CaseConverter;
+import com.palantir.conjure.python.util.ImportsVisitor;
+import com.palantir.conjure.spec.PrimitiveType;
+import com.palantir.conjure.spec.ServiceDefinition;
+import com.palantir.conjure.spec.Type;
+import com.palantir.conjure.spec.TypeDefinition;
+import com.palantir.conjure.spec.TypeName;
+import com.palantir.conjure.visitor.TypeVisitor;
+import java.util.List;
+import java.util.Map;
+import java.util.stream.Collectors;
+
+public final class ServiceGeneratorPython implements PythonFileGenerator {
+
+ @Override
+ public PythonFile generateFile(
+ Map types,
+ PackageNameProcessor packageNameProcessor,
+ ServiceDefinition serviceDefinition) {
+
+ ImportsVisitor importsVisitor = new ImportsVisitor(
+ serviceDefinition.getServiceName(), packageNameProcessor, types);
+ TypeMapper mapper = new TypeMapper(new DefaultTypeNameVisitor(types.keySet()));
+ TypeMapper myPyMapper = new TypeMapper(new MyPyTypeNameVisitor(types.keySet()));
+ String packageName = packageNameProcessor.getPackageName(serviceDefinition.getServiceName().getPackage());
+
+ Builder referencedTypesBuilder = ImmutableSet.builder();
+
+ List endpoints = serviceDefinition.getEndpoints()
+ .stream()
+ .map(ed -> {
+ ed.getReturns().ifPresent(referencedTypesBuilder::add);
+ ed.getArgs().forEach(arg -> referencedTypesBuilder.add(arg.getType()));
+
+ List params = ed.getArgs()
+ .stream()
+ .map(argEntry -> PythonEndpointParam
+ .builder()
+ .paramName(argEntry.getArgName().get())
+ .pythonParamName(CaseConverter.toCase(
+ argEntry.getArgName().get(), CaseConverter.Case.SNAKE_CASE))
+ .paramType(argEntry.getParamType())
+ .myPyType(myPyMapper.getTypeName(argEntry.getType()))
+ .build())
+ .collect(Collectors.toList());
+
+ return PythonEndpointDefinition.builder()
+ .pythonMethodName(CaseConverter.toCase(
+ ed.getEndpointName().get(), CaseConverter.Case.SNAKE_CASE))
+ .httpMethod(ed.getHttpMethod())
+ .httpPath(ed.getHttpPath())
+ .auth(ed.getAuth())
+ .docs(ed.getDocs())
+ .params(params)
+ .pythonReturnType(ed.getReturns().map(mapper::getTypeName))
+ .myPyReturnType(ed.getReturns().map(myPyMapper::getTypeName))
+ .isBinary(ed.getReturns().map(rt -> {
+ if (rt.accept(TypeVisitor.IS_PRIMITIVE)) {
+ return rt.accept(TypeVisitor.PRIMITIVE).get() == PrimitiveType.Value.BINARY;
+ }
+ return false;
+ }).orElse(false))
+ .build();
+ })
+ .collect(Collectors.toList());
+
+ List imports = referencedTypesBuilder.build()
+ .stream()
+ .flatMap(entry -> entry.accept(importsVisitor).stream())
+ .collect(Collectors.toList());
+
+ return PythonFile.builder()
+ .fileName(String.format("%s.py", serviceDefinition.getServiceName().getName()))
+ .packageName(packageName)
+ .imports(imports)
+ .addContents(PythonService.builder()
+ .className(serviceDefinition.getServiceName().getName())
+ .docs(serviceDefinition.getDocs())
+ .addAllEndpointDefinitions(endpoints)
+ .build())
+ .build();
+ }
+}
diff --git a/conjure-python-core/src/main/java/com/palantir/conjure/python/types/DefaultBeanGeneratorPython.java b/conjure-python-core/src/main/java/com/palantir/conjure/python/types/DefaultBeanGeneratorPython.java
new file mode 100644
index 000000000..069b2c2ce
--- /dev/null
+++ b/conjure-python-core/src/main/java/com/palantir/conjure/python/types/DefaultBeanGeneratorPython.java
@@ -0,0 +1,183 @@
+/*
+ * (c) Copyright 2018 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.types;
+
+import com.palantir.conjure.python.PackageNameProcessor;
+import com.palantir.conjure.python.PythonFileGenerator;
+import com.palantir.conjure.python.poet.PythonAlias;
+import com.palantir.conjure.python.poet.PythonBean;
+import com.palantir.conjure.python.poet.PythonBean.PythonField;
+import com.palantir.conjure.python.poet.PythonEnum;
+import com.palantir.conjure.python.poet.PythonEnum.PythonEnumValue;
+import com.palantir.conjure.python.poet.PythonFile;
+import com.palantir.conjure.python.poet.PythonImport;
+import com.palantir.conjure.python.poet.PythonUnionTypeDefinition;
+import com.palantir.conjure.python.util.CaseConverter;
+import com.palantir.conjure.python.util.ImportsVisitor;
+import com.palantir.conjure.spec.AliasDefinition;
+import com.palantir.conjure.spec.EnumDefinition;
+import com.palantir.conjure.spec.ObjectDefinition;
+import com.palantir.conjure.spec.Type;
+import com.palantir.conjure.spec.TypeDefinition;
+import com.palantir.conjure.spec.TypeName;
+import com.palantir.conjure.spec.UnionDefinition;
+import com.palantir.conjure.visitor.TypeDefinitionVisitor;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+public final class DefaultBeanGeneratorPython implements PythonFileGenerator {
+
+ @Override
+ public PythonFile generateFile(
+ Map types,
+ PackageNameProcessor packageNameProcessor,
+ TypeDefinition typeDef) {
+ if (typeDef.accept(TypeDefinitionVisitor.IS_OBJECT)) {
+ return generateObject(types, packageNameProcessor, typeDef.accept(TypeDefinitionVisitor.OBJECT));
+ } else if (typeDef.accept(TypeDefinitionVisitor.IS_ENUM)) {
+ return generateEnum(packageNameProcessor, typeDef.accept(TypeDefinitionVisitor.ENUM));
+ } else if (typeDef.accept(TypeDefinitionVisitor.IS_UNION)) {
+ return generateUnion(types, packageNameProcessor, typeDef.accept(TypeDefinitionVisitor.UNION));
+ } else if (typeDef.accept(TypeDefinitionVisitor.IS_ALIAS)) {
+ return generateAlias(types, packageNameProcessor, typeDef.accept(TypeDefinitionVisitor.ALIAS));
+ } else {
+ throw new UnsupportedOperationException("cannot generate type for type def: " + typeDef);
+ }
+ }
+
+ private PythonFile generateObject(
+ Map types,
+ PackageNameProcessor packageNameProcessor,
+ ObjectDefinition typeDef) {
+
+ ImportsVisitor importsVisitor = new ImportsVisitor(typeDef.getTypeName(), packageNameProcessor, types);
+ TypeMapper mapper = new TypeMapper(new DefaultTypeNameVisitor(types.keySet()));
+ TypeMapper myPyMapper = new TypeMapper(new MyPyTypeNameVisitor(types.keySet()));
+ String packageName = packageNameProcessor.getPackageName(typeDef.getTypeName().getPackage());
+
+ Set imports = typeDef.getFields()
+ .stream()
+ .flatMap(entry -> entry.getType().accept(importsVisitor).stream())
+ .collect(Collectors.toSet());
+
+ List fields = typeDef.getFields()
+ .stream()
+ .map(entry -> PythonField.builder()
+ .attributeName(CaseConverter.toCase(
+ entry.getFieldName().get(), CaseConverter.Case.SNAKE_CASE))
+ .jsonIdentifier(entry.getFieldName().get())
+ .docs(entry.getDocs())
+ .pythonType(mapper.getTypeName(entry.getType()))
+ .myPyType(myPyMapper.getTypeName(entry.getType()))
+ .build())
+ .collect(Collectors.toList());
+
+ return PythonFile.builder()
+ .fileName(String.format("%s.py", typeDef.getTypeName().getName()))
+ .imports(imports)
+ .packageName(packageName)
+ .addContents(PythonBean.builder()
+ .className(typeDef.getTypeName().getName())
+ .docs(typeDef.getDocs())
+ .fields(fields)
+ .build())
+ .build();
+ }
+
+ private PythonFile generateUnion(
+ Map types,
+ PackageNameProcessor packageNameProcessor,
+ UnionDefinition typeDef) {
+
+ ImportsVisitor importsVisitor = new ImportsVisitor(typeDef.getTypeName(), packageNameProcessor, types);
+ TypeMapper mapper = new TypeMapper(new DefaultTypeNameVisitor(types.keySet()));
+ TypeMapper myPyMapper = new TypeMapper(new MyPyTypeNameVisitor(types.keySet()));
+ String packageName = packageNameProcessor.getPackageName(typeDef.getTypeName().getPackage());
+
+ List options = typeDef.getUnion()
+ .stream()
+ .map(unionMember -> {
+ Type conjureType = unionMember.getType();
+ return PythonField.builder()
+ .attributeName(CaseConverter.toCase(
+ unionMember.getFieldName().get(), CaseConverter.Case.SNAKE_CASE))
+ .docs(unionMember.getDocs())
+ .jsonIdentifier(unionMember.getFieldName().get())
+ .myPyType(myPyMapper.getTypeName(conjureType))
+ .pythonType(mapper.getTypeName(conjureType))
+ .build();
+ })
+ .collect(Collectors.toList());
+
+ Set imports = typeDef.getUnion()
+ .stream()
+ .flatMap(entry -> entry.getType().accept(importsVisitor).stream())
+ .collect(Collectors.toSet());
+
+ return PythonFile.builder()
+ .fileName(String.format("%s.py", typeDef.getTypeName().getName()))
+ .imports(imports)
+ .packageName(packageName)
+ .addContents(
+ PythonUnionTypeDefinition.builder()
+ .className(typeDef.getTypeName().getName())
+ .docs(typeDef.getDocs())
+ .addAllOptions(options)
+ .build())
+ .build();
+ }
+
+ private PythonFile generateEnum(PackageNameProcessor packageNameProcessor, EnumDefinition typeDef) {
+
+ String packageName = packageNameProcessor.getPackageName(typeDef.getTypeName().getPackage());
+
+ return PythonFile.builder()
+ .fileName(String.format("%s.py", typeDef.getTypeName().getName()))
+ .packageName(packageName)
+ .addContents(PythonEnum.builder()
+ .className(typeDef.getTypeName().getName())
+ .docs(typeDef.getDocs())
+ .values(typeDef.getValues().stream()
+ .map(value -> PythonEnumValue.of(value.getValue(), value.getDocs()))
+ .collect(Collectors.toList()))
+ .build())
+ .build();
+ }
+
+ private PythonFile generateAlias(
+ Map types,
+ PackageNameProcessor packageNameProcessor,
+ AliasDefinition typeDef) {
+
+ ImportsVisitor importsVisitor = new ImportsVisitor(typeDef.getTypeName(), packageNameProcessor, types);
+ TypeMapper mapper = new TypeMapper(new DefaultTypeNameVisitor(types.keySet()));
+ String packageName = packageNameProcessor.getPackageName(typeDef.getTypeName().getPackage());
+
+ return PythonFile.builder()
+ .fileName(String.format("%s.py", typeDef.getTypeName().getName()))
+ .imports(typeDef.getAlias().accept(importsVisitor))
+ .packageName(packageName)
+ .addContents(PythonAlias.builder()
+ .className(typeDef.getTypeName().getName())
+ .aliasTarget(mapper.getTypeName(typeDef.getAlias()))
+ .build())
+ .build();
+ }
+
+}
diff --git a/conjure-python-core/src/main/java/com/palantir/conjure/python/types/DefaultTypeNameVisitor.java b/conjure-python-core/src/main/java/com/palantir/conjure/python/types/DefaultTypeNameVisitor.java
new file mode 100644
index 000000000..2fa20f234
--- /dev/null
+++ b/conjure-python-core/src/main/java/com/palantir/conjure/python/types/DefaultTypeNameVisitor.java
@@ -0,0 +1,108 @@
+/*
+ * (c) Copyright 2018 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.types;
+
+import com.palantir.conjure.spec.ExternalReference;
+import com.palantir.conjure.spec.ListType;
+import com.palantir.conjure.spec.MapType;
+import com.palantir.conjure.spec.OptionalType;
+import com.palantir.conjure.spec.PrimitiveType;
+import com.palantir.conjure.spec.SetType;
+import com.palantir.conjure.spec.Type;
+import com.palantir.conjure.spec.TypeName;
+import com.palantir.conjure.visitor.TypeVisitor;
+import java.util.Set;
+
+
+public final class DefaultTypeNameVisitor implements Type.Visitor {
+
+ private Set types;
+
+ public DefaultTypeNameVisitor(Set types) {
+ this.types = types;
+ }
+
+ @Override
+ public String visitList(ListType type) {
+ return "ListType(" + type.getItemType().accept(this) + ")";
+ }
+
+ @Override
+ public String visitMap(MapType type) {
+ return "DictType(" + type.getKeyType().accept(this) + ", " + type.getValueType().accept(this) + ")";
+ }
+
+ @Override
+ public String visitOptional(OptionalType type) {
+ return "OptionalType(" + type.getItemType().accept(this) + ")";
+ }
+
+ @Override
+ @SuppressWarnings("checkstyle:cyclomaticcomplexity")
+ public String visitPrimitive(PrimitiveType type) {
+ switch (type.get()) {
+ case STRING:
+ case RID:
+ case BEARERTOKEN:
+ case DATETIME:
+ case UUID:
+ return "str";
+ case BINARY:
+ return "BinaryType()";
+ case BOOLEAN:
+ return "bool";
+ case DOUBLE:
+ return "float";
+ case INTEGER:
+ case SAFELONG:
+ return "int";
+ case ANY:
+ return "object";
+ default:
+ throw new IllegalArgumentException("unknown type: " + type);
+ }
+ }
+
+ @Override
+ public String visitReference(TypeName type) {
+ if (types.contains(type)) {
+ return type.getName();
+ } else {
+ throw new IllegalStateException("unknown type: " + type);
+ }
+ }
+
+ @Override
+ public String visitExternal(ExternalReference externalType) {
+ if (externalType.getFallback().accept(TypeVisitor.IS_PRIMITIVE)) {
+ return visitPrimitive(externalType.getFallback().accept(TypeVisitor.PRIMITIVE));
+ } else {
+ throw new IllegalStateException("unknown type: " + externalType);
+ }
+ }
+
+ @Override
+ public String visitSet(SetType type) {
+ // TODO (bduffield): real sets
+ return Type.list(ListType.of(type.getItemType())).accept(this);
+ }
+
+ @Override
+ public String visitUnknown(String unknownType) {
+ throw new IllegalStateException("unknown type: " + unknownType);
+ }
+}
diff --git a/conjure-python-core/src/main/java/com/palantir/conjure/python/types/MyPyTypeNameVisitor.java b/conjure-python-core/src/main/java/com/palantir/conjure/python/types/MyPyTypeNameVisitor.java
new file mode 100644
index 000000000..0dbcb99be
--- /dev/null
+++ b/conjure-python-core/src/main/java/com/palantir/conjure/python/types/MyPyTypeNameVisitor.java
@@ -0,0 +1,111 @@
+/*
+ * (c) Copyright 2018 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.types;
+
+import com.palantir.conjure.spec.ExternalReference;
+import com.palantir.conjure.spec.ListType;
+import com.palantir.conjure.spec.MapType;
+import com.palantir.conjure.spec.OptionalType;
+import com.palantir.conjure.spec.PrimitiveType;
+import com.palantir.conjure.spec.SetType;
+import com.palantir.conjure.spec.Type;
+import com.palantir.conjure.spec.TypeName;
+import com.palantir.conjure.visitor.TypeVisitor;
+import java.util.Set;
+
+/**
+ * The mypy type for the conjure type.
+ */
+public final class MyPyTypeNameVisitor implements Type.Visitor {
+
+ private Set types;
+
+ public MyPyTypeNameVisitor(Set types) {
+ this.types = types;
+ }
+
+ @Override
+ public String visitList(ListType type) {
+ return "List[" + type.getItemType().accept(this) + "]";
+ }
+
+ @Override
+ public String visitMap(MapType type) {
+ return "Dict[" + type.getKeyType().accept(this) + ", " + type.getValueType().accept(this) + "]";
+ }
+
+ @Override
+ public String visitOptional(OptionalType type) {
+ return "Optional[" + type.getItemType().accept(this) + "]";
+ }
+
+ @Override
+ @SuppressWarnings("checkstyle:cyclomaticcomplexity")
+ public String visitPrimitive(PrimitiveType type) {
+ switch (type.get()) {
+ case STRING:
+ case RID:
+ case BEARERTOKEN:
+ case UUID:
+ case DATETIME:
+ return "str";
+ case BOOLEAN:
+ return "bool";
+ case DOUBLE:
+ return "float";
+ case INTEGER:
+ case SAFELONG:
+ return "int";
+ case ANY:
+ case BINARY:
+ return "Any";
+ default:
+ throw new IllegalArgumentException("unknown type: " + type);
+ }
+ }
+
+ @Override
+ public String visitReference(TypeName type) {
+ // Types without namespace are either defined locally in this conjure definition, or raw imports.
+ if (types.contains(type)) {
+ return type.getName();
+ } else {
+ throw new IllegalArgumentException("unknown type: " + type);
+ }
+ }
+
+ @Override
+ public String visitExternal(ExternalReference externalReference) {
+ if (externalReference.getFallback().accept(TypeVisitor.IS_PRIMITIVE)) {
+ return visitPrimitive(externalReference.getFallback().accept(TypeVisitor.PRIMITIVE));
+ } else {
+ throw new IllegalArgumentException("unknown type: " + externalReference);
+ }
+ }
+
+ @Override
+ public String visitUnknown(String unknownType) {
+ throw new IllegalArgumentException("unknown type: " + unknownType);
+ }
+
+ @Override
+ public String visitSet(SetType type) {
+ // TODO (bduffield): real sets
+ return Type.list(ListType.of(type.getItemType())).accept(this);
+ }
+
+}
diff --git a/conjure-python-core/src/main/java/com/palantir/conjure/python/types/ReferencedTypeNameVisitor.java b/conjure-python-core/src/main/java/com/palantir/conjure/python/types/ReferencedTypeNameVisitor.java
new file mode 100644
index 000000000..dc44196e8
--- /dev/null
+++ b/conjure-python-core/src/main/java/com/palantir/conjure/python/types/ReferencedTypeNameVisitor.java
@@ -0,0 +1,100 @@
+/*
+ * (c) Copyright 2018 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.types;
+
+import com.google.common.collect.ImmutableSet;
+import com.palantir.conjure.python.PackageNameProcessor;
+import com.palantir.conjure.python.poet.PythonClassName;
+import com.palantir.conjure.spec.ExternalReference;
+import com.palantir.conjure.spec.ListType;
+import com.palantir.conjure.spec.MapType;
+import com.palantir.conjure.spec.OptionalType;
+import com.palantir.conjure.spec.PrimitiveType;
+import com.palantir.conjure.spec.SetType;
+import com.palantir.conjure.spec.Type;
+import com.palantir.conjure.spec.TypeDefinition;
+import com.palantir.conjure.spec.TypeName;
+import com.palantir.conjure.visitor.TypeDefinitionVisitor;
+import com.palantir.conjure.visitor.TypeVisitor;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+public final class ReferencedTypeNameVisitor implements Type.Visitor> {
+
+ private final Set typesByName;
+ private final PackageNameProcessor packageNameProcessor;
+
+ public ReferencedTypeNameVisitor(List types, PackageNameProcessor packageNameProcessor) {
+ this.typesByName = types.stream()
+ .map(type -> type.accept(TypeDefinitionVisitor.TYPE_NAME)).collect(Collectors.toSet());
+ this.packageNameProcessor = packageNameProcessor;
+ }
+
+ @Override
+ public Set visitList(ListType type) {
+ return type.getItemType().accept(this);
+ }
+
+ @Override
+ public Set visitMap(MapType type) {
+ return ImmutableSet.builder()
+ .addAll(type.getKeyType().accept(this))
+ .addAll(type.getValueType().accept(this))
+ .build();
+ }
+
+ @Override
+ public Set visitOptional(OptionalType type) {
+ return type.getItemType().accept(this);
+ }
+
+ @Override
+ public Set visitPrimitive(PrimitiveType type) {
+ return ImmutableSet.of();
+ }
+
+ @Override
+ public Set visitReference(TypeName type) {
+ if (typesByName.contains(type)) {
+ String packageName = packageNameProcessor.getPackageName(type.getPackage());
+ // TODO(rfink): We do we generate with the package of the reference but the name of the referee?
+ return ImmutableSet.of(PythonClassName.of(packageName, type.getName()));
+ } else {
+ return ImmutableSet.of();
+ }
+ }
+
+ @Override
+ public Set visitExternal(ExternalReference externalReference) {
+ if (externalReference.getFallback().accept(TypeVisitor.IS_PRIMITIVE)) {
+ return visitPrimitive(externalReference.getFallback().accept(TypeVisitor.PRIMITIVE));
+ } else {
+ return ImmutableSet.of();
+ }
+ }
+
+ @Override
+ public Set visitUnknown(String unknownType) {
+ return ImmutableSet.of();
+ }
+
+ @Override
+ public Set visitSet(SetType type) {
+ return type.getItemType().accept(this);
+ }
+}
diff --git a/conjure-python-core/src/main/java/com/palantir/conjure/python/types/TypeMapper.java b/conjure-python-core/src/main/java/com/palantir/conjure/python/types/TypeMapper.java
new file mode 100644
index 000000000..65b84f0a1
--- /dev/null
+++ b/conjure-python-core/src/main/java/com/palantir/conjure/python/types/TypeMapper.java
@@ -0,0 +1,33 @@
+/*
+ * (c) Copyright 2018 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.types;
+
+
+import com.palantir.conjure.spec.Type;
+
+public final class TypeMapper {
+
+ private final Type.Visitor typeNameVisitor;
+
+ public TypeMapper(Type.Visitor typeNameVisitor) {
+ this.typeNameVisitor = typeNameVisitor;
+ }
+
+ public String getTypeName(Type type) {
+ return type.accept(typeNameVisitor);
+ }
+}
diff --git a/conjure-python-core/src/main/java/com/palantir/conjure/python/util/CaseConverter.java b/conjure-python-core/src/main/java/com/palantir/conjure/python/util/CaseConverter.java
new file mode 100644
index 000000000..8d2a0ddc6
--- /dev/null
+++ b/conjure-python-core/src/main/java/com/palantir/conjure/python/util/CaseConverter.java
@@ -0,0 +1,102 @@
+/*
+ * (c) Copyright 2018 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.util;
+
+import com.google.common.base.CaseFormat;
+import java.util.regex.Pattern;
+
+public final class CaseConverter {
+ private static final Pattern CAMEL_CASE_PATTERN =
+ Pattern.compile("^[a-z]([A-Z]{1,2}[a-z0-9]|[a-z0-9])+[A-Z]?$");
+ private static final Pattern KEBAB_CASE_PATTERN =
+ Pattern.compile("^[a-z]((-[a-z]){1,2}[a-z0-9]|[a-z0-9])+(-[a-z])?$");
+ private static final Pattern SNAKE_CASE_PATTERN =
+ Pattern.compile("^[a-z]((_[a-z]){1,2}[a-z0-9]|[a-z0-9])+(_[a-z])?$");
+
+ private CaseConverter() {}
+
+ public enum Case {
+ LOWER_CAMEL_CASE(CAMEL_CASE_PATTERN) {
+ @Override
+ String convertTo(String name, Case targetCase) {
+ switch (targetCase) {
+ case LOWER_CAMEL_CASE:
+ return name;
+ case KEBAB_CASE:
+ return CaseFormat.LOWER_CAMEL.to(CaseFormat.LOWER_HYPHEN, name);
+ case SNAKE_CASE:
+ return CaseFormat.LOWER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, name);
+ }
+ throw new IllegalArgumentException("Unknown FieldName case, this is a bug: " + targetCase);
+ }
+ },
+ KEBAB_CASE(KEBAB_CASE_PATTERN) {
+ @Override
+ String convertTo(String name, Case targetCase) {
+ switch (targetCase) {
+ case LOWER_CAMEL_CASE:
+ return CaseFormat.LOWER_HYPHEN.to(CaseFormat.LOWER_CAMEL, name);
+ case KEBAB_CASE:
+ return name;
+ case SNAKE_CASE:
+ return CaseFormat.LOWER_HYPHEN.to(CaseFormat.LOWER_UNDERSCORE, name);
+ }
+ throw new IllegalArgumentException("Unknown FieldName case, this is a bug: " + targetCase);
+ }
+ },
+ SNAKE_CASE(SNAKE_CASE_PATTERN) {
+ @Override
+ String convertTo(String name, Case targetCase) {
+ switch (targetCase) {
+ case LOWER_CAMEL_CASE:
+ return CaseFormat.LOWER_UNDERSCORE.to(CaseFormat.LOWER_CAMEL, name);
+ case KEBAB_CASE:
+ return CaseFormat.LOWER_UNDERSCORE.to(CaseFormat.LOWER_HYPHEN, name);
+ case SNAKE_CASE:
+ return name;
+ }
+ throw new IllegalArgumentException("Unknown FieldName case, this is a bug: " + targetCase);
+ }
+ };
+
+ private final Pattern pattern;
+
+ Case(Pattern pattern) {
+ this.pattern = pattern;
+ }
+
+ @Override
+ public String toString() {
+ return name() + "[" + pattern + "]";
+ }
+
+ abstract String convertTo(String name, Case targetCase);
+ }
+
+ public static String toCase(String name, Case targetCase) {
+ return nameCase(name).convertTo(name, targetCase);
+ }
+
+ private static Case nameCase(String name) {
+ for (Case nameCase : Case.values()) {
+ if (nameCase.pattern.matcher(name).matches()) {
+ return nameCase;
+ }
+ }
+ throw new IllegalStateException("Could not find case for FieldName, this is a bug: " + name);
+ }
+}
diff --git a/conjure-python-core/src/main/java/com/palantir/conjure/python/util/ImportsVisitor.java b/conjure-python-core/src/main/java/com/palantir/conjure/python/util/ImportsVisitor.java
new file mode 100644
index 000000000..371756983
--- /dev/null
+++ b/conjure-python-core/src/main/java/com/palantir/conjure/python/util/ImportsVisitor.java
@@ -0,0 +1,116 @@
+/*
+ * (c) Copyright 2018 Palantir Technologies Inc. All rights reserved.
+ */
+
+package com.palantir.conjure.python.util;
+
+import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableSet;
+import com.palantir.conjure.python.PackageNameProcessor;
+import com.palantir.conjure.python.poet.PythonClassName;
+import com.palantir.conjure.python.poet.PythonImport;
+import com.palantir.conjure.spec.ExternalReference;
+import com.palantir.conjure.spec.ListType;
+import com.palantir.conjure.spec.MapType;
+import com.palantir.conjure.spec.OptionalType;
+import com.palantir.conjure.spec.PrimitiveType;
+import com.palantir.conjure.spec.SetType;
+import com.palantir.conjure.spec.Type;
+import com.palantir.conjure.spec.TypeDefinition;
+import com.palantir.conjure.spec.TypeName;
+import java.util.Collections;
+import java.util.Map;
+import java.util.Set;
+
+public final class ImportsVisitor implements Type.Visitor> {
+ private TypeName currentType;
+ private PackageNameProcessor packageNameProcessor;
+ private Map knownTypes;
+
+ public ImportsVisitor(
+ TypeName currentType,
+ PackageNameProcessor packageNameProcessor,
+ Map knownTypes) {
+ this.currentType = currentType;
+ this.packageNameProcessor = packageNameProcessor;
+ this.knownTypes = knownTypes;
+ }
+
+ @Override
+ public Set visitPrimitive(PrimitiveType value) {
+ if (value.equals(PrimitiveType.ANY)) {
+ return ImmutableSet.of(PythonImport.of(PythonClassName.of("typing", "Any")));
+ } else if (value.equals(PrimitiveType.BINARY)) {
+ return ImmutableSet.of(PythonImport.of(PythonClassName.of("conjure", "BinaryType")));
+ }
+ return Collections.emptySet();
+ }
+
+ @Override
+ public Set visitOptional(OptionalType value) {
+ ImmutableSet.Builder setBuilder = ImmutableSet.builder();
+ return setBuilder
+ .add(PythonImport.of(PythonClassName.of("typing", "Optional")))
+ .add(PythonImport.of(PythonClassName.of("conjure", "OptionalType")))
+ .addAll(value.getItemType().accept(this))
+ .build();
+ }
+
+ @Override
+ public Set visitList(ListType value) {
+ ImmutableSet.Builder setBuilder = ImmutableSet.builder();
+ return setBuilder
+ .add(PythonImport.of(PythonClassName.of("typing", "List")))
+ .add(PythonImport.of(PythonClassName.of("conjure", "ListType")))
+ .addAll(value.getItemType().accept(this))
+ .build();
+ }
+
+ @Override
+ public Set visitSet(SetType value) {
+ ImmutableSet.Builder setBuilder = ImmutableSet.builder();
+ return setBuilder
+ .add(PythonImport.of(PythonClassName.of("typing", "Set")))
+ .add(PythonImport.of(PythonClassName.of("conjure", "ListType")))
+ .addAll(value.getItemType().accept(this))
+ .build();
+ }
+
+ @Override
+ public Set visitMap(MapType value) {
+ ImmutableSet.Builder setBuilder = ImmutableSet.builder();
+ return setBuilder
+ .add(PythonImport.of(PythonClassName.of("typing", "Dict")))
+ .add(PythonImport.of(PythonClassName.of("conjure", "DictType")))
+ .addAll(value.getKeyType().accept(this))
+ .addAll(value.getValueType().accept(this))
+ .build();
+ }
+
+ @Override
+ public Set visitReference(TypeName value) {
+ Preconditions.checkState(knownTypes.containsKey(value), "Unknown TypeName %s", value);
+ return ImmutableSet.of(PythonImport.of(
+ PythonClassName.of(getRelativePath(value), value.getName())));
+ }
+
+ @Override
+ public Set visitExternal(ExternalReference value) {
+ // TODO(forozco): handle python external references
+ return Collections.emptySet();
+ }
+
+ @Override
+ public Set visitUnknown(String unknownType) {
+ return Collections.emptySet();
+ }
+
+ private String getRelativePath(TypeName targetType) {
+ if (targetType.getPackage().equals(currentType.getPackage())) {
+ return String.format(".%s", targetType.getName());
+ } else {
+ String targetPackageName = packageNameProcessor.getPackageName(targetType.getPackage()).split("\\.")[1];
+ return String.format("..%s.%s", targetPackageName, targetType.getName());
+ }
+ }
+}
diff --git a/conjure-python-core/src/main/java/com/palantir/conjure/python/util/TypeNameVisitor.java b/conjure-python-core/src/main/java/com/palantir/conjure/python/util/TypeNameVisitor.java
new file mode 100644
index 000000000..90e5f1be7
--- /dev/null
+++ b/conjure-python-core/src/main/java/com/palantir/conjure/python/util/TypeNameVisitor.java
@@ -0,0 +1,55 @@
+/*
+ * (c) Copyright 2018 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.util;
+
+import com.palantir.conjure.spec.AliasDefinition;
+import com.palantir.conjure.spec.EnumDefinition;
+import com.palantir.conjure.spec.ObjectDefinition;
+import com.palantir.conjure.spec.TypeDefinition;
+import com.palantir.conjure.spec.TypeName;
+import com.palantir.conjure.spec.UnionDefinition;
+
+public final class TypeNameVisitor implements TypeDefinition.Visitor {
+
+ public TypeNameVisitor() {
+ }
+
+ @Override
+ public TypeName visitAlias(AliasDefinition value) {
+ return value.getTypeName();
+ }
+
+ @Override
+ public TypeName visitEnum(EnumDefinition value) {
+ return value.getTypeName();
+ }
+
+ @Override
+ public TypeName visitObject(ObjectDefinition value) {
+ return value.getTypeName();
+ }
+
+ @Override
+ public TypeName visitUnion(UnionDefinition value) {
+ return value.getTypeName();
+ }
+
+ @Override
+ public TypeName visitUnknown(String unknownType) {
+ throw new RuntimeException(String.format("Unknown type definition: %s", unknownType));
+ }
+}
diff --git a/conjure-python-core/src/test/java/com/palantir/conjure/python/ConjurePythonGeneratorTest.java b/conjure-python-core/src/test/java/com/palantir/conjure/python/ConjurePythonGeneratorTest.java
new file mode 100644
index 000000000..8b5f6ba39
--- /dev/null
+++ b/conjure-python-core/src/test/java/com/palantir/conjure/python/ConjurePythonGeneratorTest.java
@@ -0,0 +1,111 @@
+/*
+ * (c) Copyright 2018 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.python.service.ServiceGeneratorPython;
+import com.palantir.conjure.python.types.DefaultBeanGeneratorPython;
+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.Set;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+import org.assertj.core.util.Strings;
+import org.junit.runner.RunWith;
+
+@ConjureSubfolderRunner.ParentFolder("src/test/resources")
+@RunWith(ConjureSubfolderRunner.class)
+public final class ConjurePythonGeneratorTest {
+
+ private final ConjurePythonGenerator generator = new ConjurePythonGenerator(
+ new DefaultBeanGeneratorPython(), new ServiceGeneratorPython(),
+ GeneratorConfiguration.builder()
+ .packageName("package")
+ .packageVersion("0.0.0")
+ .packageDescription("project description")
+ .minConjureClientVersion("0.0.0")
+ .shouldWriteCondaRecipe(true)
+ .build());
+ private final InMemoryPythonFileWriter pythonFileWriter = new InMemoryPythonFileWriter();
+
+ @ConjureSubfolderRunner.Test
+ public void assertThatFilesRenderAsExpected(Path folder) throws IOException {
+ Path expected = folder.resolve("expected");
+ ConjureDefinition definition = getInputDefinitions(folder);
+ maybeResetExpectedDirectory(expected, definition);
+
+ generator.write(definition, pythonFileWriter);
+ assertFoldersEqual(expected);
+ }
+
+ private void assertFoldersEqual(Path expected) throws IOException {
+ Set generatedButNotExpected = pythonFileWriter.getPythonFiles().keySet();
+ long count = 0;
+ try (Stream walk = Files.walk(expected)) {
+ for (Path path : walk.collect(Collectors.toList())) {
+ if (!path.toFile().isFile()) {
+ continue;
+ }
+ String expectedContent = Strings.join(Files.readAllLines(path)).with("\n") + "\n";
+ assertThat(pythonFileWriter.getPythonFiles().get(expected.relativize(path))).isEqualTo(expectedContent);
+ generatedButNotExpected.remove(expected.relativize(path));
+ count += 1;
+ }
+ }
+ assertThat(generatedButNotExpected).isEmpty();
+ System.out.println(count + " files checked");
+ }
+
+ private void maybeResetExpectedDirectory(Path expected, ConjureDefinition definition) throws IOException {
+ if (Boolean.valueOf(System.getProperty("recreate", "false"))
+ || !expected.toFile().isDirectory()) {
+ Files.createDirectories(expected);
+ try (Stream walk = Files.walk(expected)) {
+ walk.filter(path -> path.toFile().isFile()).forEach(path -> path.toFile().delete());
+ }
+ try (Stream walk = Files.walk(expected)) {
+ walk.forEach(path -> path.toFile().delete());
+ }
+ Files.createDirectories(expected);
+
+ generator.write(definition, new DefaultPythonFileWriter(expected));
+ }
+ }
+
+ private ConjureDefinition getInputDefinitions(Path folder) throws IOException {
+ Files.createDirectories(folder);
+ try (Stream walk = Files.walk(folder)) {
+ List files = walk
+ .map(Path::toFile)
+ .filter(file -> file.toString().endsWith(".yml"))
+ .collect(Collectors.toList());
+
+ if (files.isEmpty()) {
+ throw new RuntimeException(
+ folder + " contains no conjure.yml files, please write one to set up a new test");
+ }
+
+ return Conjure.parse(files);
+ }
+ }
+}
diff --git a/conjure-python-core/src/test/java/com/palantir/conjure/python/ConjureSubfolderRunner.java b/conjure-python-core/src/test/java/com/palantir/conjure/python/ConjureSubfolderRunner.java
new file mode 100644
index 000000000..0f5eb7a03
--- /dev/null
+++ b/conjure-python-core/src/test/java/com/palantir/conjure/python/ConjureSubfolderRunner.java
@@ -0,0 +1,167 @@
+/*
+ * (c) Copyright 2018 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 java.io.File;
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.nio.file.Path;
+import java.nio.file.Paths;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Optional;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+import java.util.stream.Collectors;
+import org.junit.internal.runners.model.EachTestNotifier;
+import org.junit.runner.Description;
+import org.junit.runner.notification.RunNotifier;
+import org.junit.runners.ParentRunner;
+import org.junit.runners.model.FrameworkMethod;
+import org.junit.runners.model.InitializationError;
+import org.junit.runners.model.RunnerScheduler;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Inspired by {@link org.junit.runners.BlockJUnit4ClassRunner}, except instead of a test 'method' being the unit of
+ * work, it's a 'directory'.
+ *
+ * Note, this doesn't support @Rule or @ClassRule.
+ */
+public final class ConjureSubfolderRunner extends ParentRunner {
+ private static final Logger log = LoggerFactory.getLogger(ConjureSubfolderRunner.class);
+
+ @Retention(RetentionPolicy.RUNTIME)
+ @Target(ElementType.TYPE)
+ public @interface ParentFolder {
+ /** Parent folder to list. */
+ String value();
+ /** Whether tests should be executed indepenently on a CachedThreadPool. */
+ boolean parallel() default false;
+ }
+
+ /**
+ * Use this annotation to tell {@link ConjureSubfolderRunner} to execute your test method for every subfolder
+ * it finds inside the {@link ParentFolder} you specified.
+ */
+ @Retention(RetentionPolicy.RUNTIME)
+ @Target(ElementType.METHOD)
+ public @interface Test {}
+
+ private final Path parent;
+ private final FrameworkMethod onlyMethod;
+
+ public ConjureSubfolderRunner(Class> klass) throws InitializationError {
+ super(klass);
+ ParentFolder annotation = getParentFolder(getTestClass().getAnnotation(ParentFolder.class));
+ parent = Paths.get(annotation.value());
+ onlyMethod = validateMethod(getTestClass().getAnnotatedMethods(ConjureSubfolderRunner.Test.class));
+ maybeParallelScheduler(annotation).ifPresent(this::setScheduler);
+ }
+
+ @Override
+ protected List getChildren() {
+ return Arrays.stream(parent.toFile().listFiles())
+ .filter(File::isDirectory)
+ .map(File::toPath)
+ .collect(Collectors.toList());
+ }
+
+ @Override
+ protected Description describeChild(Path child) {
+ return Description.createTestDescription(getName(), child.toString());
+ }
+
+ @Override
+ protected void runChild(Path child, RunNotifier notifier) {
+ Description description = describeChild(child);
+ EachTestNotifier eachNotifier = new EachTestNotifier(notifier, description);
+ eachNotifier.fireTestStarted();
+ try {
+ onlyMethod.invokeExplosively(createTestClassInstance(), child);
+ } catch (Throwable e) {
+ eachNotifier.addFailure(e);
+ } finally {
+ eachNotifier.fireTestFinished();
+ }
+ }
+
+ private Object createTestClassInstance()
+ throws InstantiationException, IllegalAccessException, java.lang.reflect.InvocationTargetException {
+ return getTestClass().getOnlyConstructor().newInstance();
+ }
+
+ private ParentFolder getParentFolder(ParentFolder annotation) {
+ if (annotation == null) {
+ throw new RuntimeException("The class must be annotated with @ParentFolder");
+ }
+
+ return annotation;
+ }
+
+ private FrameworkMethod validateMethod(List annotated) {
+ if (annotated.size() != 1) {
+ throw new RuntimeException("There must be exactly one @ConjureSubfolderRunner.Test method");
+ }
+
+ FrameworkMethod method = annotated.get(0);
+
+ if (!method.isPublic()) {
+ throw new RuntimeException("@ConjureSubfolderRunner.Test method must be public: " + method);
+ }
+
+ Class>[] parameters = method.getMethod().getParameterTypes();
+ if (parameters.length != 1 || parameters[0] != Path.class) {
+ throw new RuntimeException(
+ "@ConjureSubfolderRunner.Test method must have exactly one parameter (java.nio.file.Path): "
+ + method);
+ }
+
+ return method;
+ }
+
+ private Optional maybeParallelScheduler(ParentFolder annotation) {
+ if (!annotation.parallel()) {
+ return Optional.empty();
+ }
+
+ return Optional.of(new RunnerScheduler() {
+ private final ExecutorService executor = Executors.newCachedThreadPool();
+
+ @Override
+ @SuppressWarnings("FutureReturnValueIgnored")
+ public void schedule(Runnable childStatement) {
+ executor.submit(childStatement);
+ }
+
+ @Override
+ public void finished() {
+ try {
+ executor.shutdown();
+ executor.awaitTermination(10, TimeUnit.SECONDS);
+ } catch (InterruptedException e) {
+ log.error("Parallel executor interrupted during shutdown", e);
+ Thread.currentThread().interrupt();
+ }
+ }
+ });
+ }
+}
diff --git a/conjure-python-core/src/test/java/com/palantir/conjure/python/InMemoryPythonFileWriter.java b/conjure-python-core/src/test/java/com/palantir/conjure/python/InMemoryPythonFileWriter.java
new file mode 100644
index 000000000..471dbb79f
--- /dev/null
+++ b/conjure-python-core/src/test/java/com/palantir/conjure/python/InMemoryPythonFileWriter.java
@@ -0,0 +1,51 @@
+/*
+ * (c) Copyright 2018 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 com.google.common.collect.Maps;
+import com.palantir.conjure.python.poet.PythonFile;
+import com.palantir.conjure.python.poet.PythonPoetWriter;
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.PrintStream;
+import java.nio.charset.StandardCharsets;
+import java.nio.file.Path;
+import java.util.Map;
+
+public final class InMemoryPythonFileWriter implements PythonFileWriter {
+
+ private final Map pythonFiles = Maps.newHashMap();
+
+ public Map getPythonFiles() {
+ return pythonFiles;
+ }
+
+ @Override
+ public void writePythonFile(PythonFile pythonFile) {
+ try (ByteArrayOutputStream baos = new ByteArrayOutputStream();
+ PrintStream printStream = new PrintStream(baos)) {
+ PythonPoetWriter poetWriter = new PythonPoetWriter(printStream);
+
+ poetWriter.emit(pythonFile);
+ byte[] bytes = baos.toByteArray();
+
+ pythonFiles.put(PythonFileWriter.getPath(pythonFile), new String(bytes, StandardCharsets.UTF_8));
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+}
diff --git a/conjure-python-core/src/test/java/com/palantir/conjure/python/PackageNameProcessorTests.java b/conjure-python-core/src/test/java/com/palantir/conjure/python/PackageNameProcessorTests.java
new file mode 100644
index 000000000..407c3affb
--- /dev/null
+++ b/conjure-python-core/src/test/java/com/palantir/conjure/python/PackageNameProcessorTests.java
@@ -0,0 +1,57 @@
+/*
+ * (c) Copyright 2018 Palantir Technologies Inc. All rights reserved.
+ */
+
+package com.palantir.conjure.python;
+
+import static org.assertj.core.api.Assertions.assertThat;
+
+import org.junit.Test;
+
+public final class PackageNameProcessorTests {
+
+ @Test
+ public void testPackageNameProcessor() {
+ PackageNameProcessor processor = PackageNameProcessor.builder().build();
+ assertThat(processor.getPackageName("com.palantir.test")).isEqualTo("com.palantir.test");
+ }
+
+ @Test
+ public void testTwoComponentStrippingPackageNameProcessor() {
+ PackageNameProcessor processor = PackageNameProcessor.builder()
+ .addProcessors(new TwoComponentStrippingPackageNameProcessor())
+ .build();
+ assertThat(processor.getPackageName("com.palantir.test")).isEqualTo("test");
+ assertThat(processor.getPackageName("com.palantir")).isEqualTo("com.palantir");
+ }
+
+ @Test
+ public void testTopLevelRenamingPackageNameProcessor() {
+ PackageNameProcessor processor = PackageNameProcessor.builder()
+ .addProcessors(new TopLevelAddingPackageNameProcessor("toplevel"))
+ .build();
+ assertThat(processor.getPackageName("test")).isEqualTo("toplevel.test");
+ assertThat(processor.getPackageName("test.whatever")).isEqualTo("toplevel.test.whatever");
+ }
+
+ @Test
+ public void testFlatteningPackageNameProcessor() {
+ PackageNameProcessor processor = PackageNameProcessor.builder()
+ .addProcessors(new FlatteningPackageNameProcessor())
+ .build();
+ assertThat(processor.getPackageName("data.test.api")).isEqualTo("data_test_api");
+ }
+
+ @Test
+ public void testPackageNameProcessorComposition() {
+ PackageNameProcessor processor = PackageNameProcessor.builder()
+ .addProcessors(new TwoComponentStrippingPackageNameProcessor())
+ .addProcessors(new FlatteningPackageNameProcessor())
+ .addProcessors(new TopLevelAddingPackageNameProcessor("toplevel"))
+ .build();
+ assertThat(processor.getPackageName("test")).isEqualTo("toplevel.test");
+ assertThat(processor.getPackageName("test.whatever")).isEqualTo("toplevel.test_whatever");
+ assertThat(processor.getPackageName("com.palantir.test")).isEqualTo("toplevel.test");
+ assertThat(processor.getPackageName("com.palantir.test.whatever")).isEqualTo("toplevel.test_whatever");
+ }
+}
diff --git a/conjure-python-core/src/test/resources/services/example-service.yml b/conjure-python-core/src/test/resources/services/example-service.yml
new file mode 120000
index 000000000..577424d4d
--- /dev/null
+++ b/conjure-python-core/src/test/resources/services/example-service.yml
@@ -0,0 +1 @@
+../types/example-service.yml
\ No newline at end of file
diff --git a/conjure-python-core/src/test/resources/services/expected/conda_recipe/meta.yaml b/conjure-python-core/src/test/resources/services/expected/conda_recipe/meta.yaml
new file mode 100644
index 000000000..371519a3a
--- /dev/null
+++ b/conjure-python-core/src/test/resources/services/expected/conda_recipe/meta.yaml
@@ -0,0 +1,25 @@
+
+package:
+ name: package
+ version: 0.0.0
+
+source:
+ path: ../
+
+build:
+ noarch_python: True
+ script: python setup.py install
+
+requirements:
+ build:
+ - python
+ - setuptools
+ - requests
+ - typing
+ - conjure-client >=0.0.0,<1
+
+ run:
+ - python
+ - requests
+ - typing
+ - conjure-client >=0.0.0,<1
diff --git a/conjure-python-core/src/test/resources/services/expected/package/__init__.py b/conjure-python-core/src/test/resources/services/expected/package/__init__.py
new file mode 100644
index 000000000..01cb8dacd
--- /dev/null
+++ b/conjure-python-core/src/test/resources/services/expected/package/__init__.py
@@ -0,0 +1,9 @@
+
+__all__ = [
+ 'another',
+ 'product',
+ 'product_datasets',
+]
+
+__version__ = "0.0.0"
+
diff --git a/conjure-python-core/src/test/resources/services/expected/package/another/TestService.py b/conjure-python-core/src/test/resources/services/expected/package/another/TestService.py
new file mode 100644
index 000000000..f97343c90
--- /dev/null
+++ b/conjure-python-core/src/test/resources/services/expected/package/another/TestService.py
@@ -0,0 +1,439 @@
+from ..product.CreateDatasetRequest import CreateDatasetRequest
+from ..product_datasets.BackingFileSystem import BackingFileSystem
+from ..product_datasets.Dataset import Dataset
+from conjure import BinaryType
+from conjure import ConjureDecoder
+from conjure import ConjureEncoder
+from conjure import DictType
+from conjure import ListType
+from conjure import OptionalType
+from httpremoting import Service
+from typing import Dict
+from typing import Optional
+from typing import Set
+
+class TestService(Service):
+ """A Markdown description of the service."""
+
+ def get_file_systems(self, auth_header):
+ # type: (str) -> Dict[str, BackingFileSystem]
+ """Returns a mapping from file system id to backing file system configuration."""
+
+ _headers = {
+ 'Accept': 'application/json',
+ 'Authorization': auth_header,
+ } # type: Dict[str, Any]
+
+ _params = {
+ } # type: Dict[str, Any]
+
+ _path_params = {
+ } # type: Dict[str, Any]
+
+ _json = None # type: Any
+
+ _path = '/catalog/fileSystems'
+ _path = _path.format(**_path_params)
+
+ _response = self._request( # type: ignore
+ 'GET',
+ self._uri + _path,
+ params=_params,
+ headers=_headers,
+ json=_json)
+
+ _decoder = ConjureDecoder()
+ return _decoder.decode(_response.json(), DictType(str, BackingFileSystem))
+
+ def create_dataset(self, auth_header, request, test_header_arg):
+ # type: (str, CreateDatasetRequest, str) -> Dataset
+
+ _headers = {
+ 'Accept': 'application/json',
+ 'Content-Type': 'application/json',
+ 'Authorization': auth_header,
+ 'Test-Header': test_header_arg,
+ } # type: Dict[str, Any]
+
+ _params = {
+ } # type: Dict[str, Any]
+
+ _path_params = {
+ } # type: Dict[str, Any]
+
+ _json = ConjureEncoder().default(request) # type: Any
+
+ _path = '/catalog/datasets'
+ _path = _path.format(**_path_params)
+
+ _response = self._request( # type: ignore
+ 'POST',
+ self._uri + _path,
+ params=_params,
+ headers=_headers,
+ json=_json)
+
+ _decoder = ConjureDecoder()
+ return _decoder.decode(_response.json(), Dataset)
+
+ def get_dataset(self, auth_header, dataset_rid):
+ # type: (str, str) -> Optional[Dataset]
+
+ _headers = {
+ 'Accept': 'application/json',
+ 'Authorization': auth_header,
+ } # type: Dict[str, Any]
+
+ _params = {
+ } # type: Dict[str, Any]
+
+ _path_params = {
+ 'datasetRid': dataset_rid,
+ } # type: Dict[str, Any]
+
+ _json = None # type: Any
+
+ _path = '/catalog/datasets/{datasetRid}'
+ _path = _path.format(**_path_params)
+
+ _response = self._request( # type: ignore
+ 'GET',
+ self._uri + _path,
+ params=_params,
+ headers=_headers,
+ json=_json)
+
+ _decoder = ConjureDecoder()
+ return _decoder.decode(_response.json(), OptionalType(Dataset))
+
+ def get_raw_data(self, auth_header, dataset_rid):
+ # type: (str, str) -> Any
+
+ _headers = {
+ 'Accept': 'application/octet-stream',
+ 'Authorization': auth_header,
+ } # type: Dict[str, Any]
+
+ _params = {
+ } # type: Dict[str, Any]
+
+ _path_params = {
+ 'datasetRid': dataset_rid,
+ } # type: Dict[str, Any]
+
+ _json = None # type: Any
+
+ _path = '/catalog/datasets/{datasetRid}/raw'
+ _path = _path.format(**_path_params)
+
+ _response = self._request( # type: ignore
+ 'GET',
+ self._uri + _path,
+ params=_params,
+ headers=_headers,
+ stream=True,
+ json=_json)
+
+ _raw = _response.raw
+ _raw.decode_content = True
+ return _raw
+
+ def maybe_get_raw_data(self, auth_header, dataset_rid):
+ # type: (str, str) -> Optional[Any]
+
+ _headers = {
+ 'Accept': 'application/json',
+ 'Authorization': auth_header,
+ } # type: Dict[str, Any]
+
+ _params = {
+ } # type: Dict[str, Any]
+
+ _path_params = {
+ 'datasetRid': dataset_rid,
+ } # type: Dict[str, Any]
+
+ _json = None # type: Any
+
+ _path = '/catalog/datasets/{datasetRid}/raw-maybe'
+ _path = _path.format(**_path_params)
+
+ _response = self._request( # type: ignore
+ 'GET',
+ self._uri + _path,
+ params=_params,
+ headers=_headers,
+ json=_json)
+
+ _decoder = ConjureDecoder()
+ return _decoder.decode(_response.json(), OptionalType(BinaryType()))
+
+ def upload_raw_data(self, auth_header, input):
+ # type: (str, Any) -> None
+
+ _headers = {
+ 'Accept': 'application/json',
+ 'Content-Type': 'application/json',
+ 'Authorization': auth_header,
+ } # type: Dict[str, Any]
+
+ _params = {
+ } # type: Dict[str, Any]
+
+ _path_params = {
+ } # type: Dict[str, Any]
+
+ _json = ConjureEncoder().default(input) # type: Any
+
+ _path = '/catalog/datasets/upload-raw'
+ _path = _path.format(**_path_params)
+
+ _response = self._request( # type: ignore
+ 'POST',
+ self._uri + _path,
+ params=_params,
+ headers=_headers,
+ json=_json)
+
+ return
+
+ def get_branches(self, auth_header, dataset_rid):
+ # type: (str, str) -> List[str]
+
+ _headers = {
+ 'Accept': 'application/json',
+ 'Authorization': auth_header,
+ } # type: Dict[str, Any]
+
+ _params = {
+ } # type: Dict[str, Any]
+
+ _path_params = {
+ 'datasetRid': dataset_rid,
+ } # type: Dict[str, Any]
+
+ _json = None # type: Any
+
+ _path = '/catalog/datasets/{datasetRid}/branches'
+ _path = _path.format(**_path_params)
+
+ _response = self._request( # type: ignore
+ 'GET',
+ self._uri + _path,
+ params=_params,
+ headers=_headers,
+ json=_json)
+
+ _decoder = ConjureDecoder()
+ return _decoder.decode(_response.json(), ListType(str))
+
+ def get_branches_deprecated(self, auth_header, dataset_rid):
+ # type: (str, str) -> List[str]
+ """Gets all branches of this dataset."""
+
+ _headers = {
+ 'Accept': 'application/json',
+ 'Authorization': auth_header,
+ } # type: Dict[str, Any]
+
+ _params = {
+ } # type: Dict[str, Any]
+
+ _path_params = {
+ 'datasetRid': dataset_rid,
+ } # type: Dict[str, Any]
+
+ _json = None # type: Any
+
+ _path = '/catalog/datasets/{datasetRid}/branchesDeprecated'
+ _path = _path.format(**_path_params)
+
+ _response = self._request( # type: ignore
+ 'GET',
+ self._uri + _path,
+ params=_params,
+ headers=_headers,
+ json=_json)
+
+ _decoder = ConjureDecoder()
+ return _decoder.decode(_response.json(), ListType(str))
+
+ def resolve_branch(self, auth_header, dataset_rid, branch):
+ # type: (str, str, str) -> Optional[str]
+
+ _headers = {
+ 'Accept': 'application/json',
+ 'Authorization': auth_header,
+ } # type: Dict[str, Any]
+
+ _params = {
+ } # type: Dict[str, Any]
+
+ _path_params = {
+ 'datasetRid': dataset_rid,
+ 'branch': branch,
+ } # type: Dict[str, Any]
+
+ _json = None # type: Any
+
+ _path = '/catalog/datasets/{datasetRid}/branches/{branch}/resolve'
+ _path = _path.format(**_path_params)
+
+ _response = self._request( # type: ignore
+ 'GET',
+ self._uri + _path,
+ params=_params,
+ headers=_headers,
+ json=_json)
+
+ _decoder = ConjureDecoder()
+ return _decoder.decode(_response.json(), OptionalType(str))
+
+ def test_param(self, auth_header, dataset_rid):
+ # type: (str, str) -> Optional[str]
+
+ _headers = {
+ 'Accept': 'application/json',
+ 'Authorization': auth_header,
+ } # type: Dict[str, Any]
+
+ _params = {
+ } # type: Dict[str, Any]
+
+ _path_params = {
+ 'datasetRid': dataset_rid,
+ } # type: Dict[str, Any]
+
+ _json = None # type: Any
+
+ _path = '/catalog/datasets/{datasetRid}/testParam'
+ _path = _path.format(**_path_params)
+
+ _response = self._request( # type: ignore
+ 'GET',
+ self._uri + _path,
+ params=_params,
+ headers=_headers,
+ json=_json)
+
+ _decoder = ConjureDecoder()
+ return _decoder.decode(_response.json(), OptionalType(str))
+
+ def test_query_params(self, auth_header, something, implicit):
+ # type: (str, str, str) -> int
+
+ _headers = {
+ 'Accept': 'application/json',
+ 'Authorization': auth_header,
+ } # type: Dict[str, Any]
+
+ _params = {
+ 'different': something,
+ 'implicit': implicit,
+ } # type: Dict[str, Any]
+
+ _path_params = {
+ } # type: Dict[str, Any]
+
+ _json = None # type: Any
+
+ _path = '/catalog/test-query-params'
+ _path = _path.format(**_path_params)
+
+ _response = self._request( # type: ignore
+ 'GET',
+ self._uri + _path,
+ params=_params,
+ headers=_headers,
+ json=_json)
+
+ _decoder = ConjureDecoder()
+ return _decoder.decode(_response.json(), int)
+
+ def test_boolean(self, auth_header):
+ # type: (str) -> bool
+
+ _headers = {
+ 'Accept': 'application/json',
+ 'Authorization': auth_header,
+ } # type: Dict[str, Any]
+
+ _params = {
+ } # type: Dict[str, Any]
+
+ _path_params = {
+ } # type: Dict[str, Any]
+
+ _json = None # type: Any
+
+ _path = '/catalog/boolean'
+ _path = _path.format(**_path_params)
+
+ _response = self._request( # type: ignore
+ 'GET',
+ self._uri + _path,
+ params=_params,
+ headers=_headers,
+ json=_json)
+
+ _decoder = ConjureDecoder()
+ return _decoder.decode(_response.json(), bool)
+
+ def test_double(self, auth_header):
+ # type: (str) -> float
+
+ _headers = {
+ 'Accept': 'application/json',
+ 'Authorization': auth_header,
+ } # type: Dict[str, Any]
+
+ _params = {
+ } # type: Dict[str, Any]
+
+ _path_params = {
+ } # type: Dict[str, Any]
+
+ _json = None # type: Any
+
+ _path = '/catalog/double'
+ _path = _path.format(**_path_params)
+
+ _response = self._request( # type: ignore
+ 'GET',
+ self._uri + _path,
+ params=_params,
+ headers=_headers,
+ json=_json)
+
+ _decoder = ConjureDecoder()
+ return _decoder.decode(_response.json(), float)
+
+ def test_integer(self, auth_header):
+ # type: (str) -> int
+
+ _headers = {
+ 'Accept': 'application/json',
+ 'Authorization': auth_header,
+ } # type: Dict[str, Any]
+
+ _params = {
+ } # type: Dict[str, Any]
+
+ _path_params = {
+ } # type: Dict[str, Any]
+
+ _json = None # type: Any
+
+ _path = '/catalog/integer'
+ _path = _path.format(**_path_params)
+
+ _response = self._request( # type: ignore
+ 'GET',
+ self._uri + _path,
+ params=_params,
+ headers=_headers,
+ json=_json)
+
+ _decoder = ConjureDecoder()
+ return _decoder.decode(_response.json(), int)
+
diff --git a/conjure-python-core/src/test/resources/services/expected/package/another/__init__.py b/conjure-python-core/src/test/resources/services/expected/package/another/__init__.py
new file mode 100644
index 000000000..5465497e8
--- /dev/null
+++ b/conjure-python-core/src/test/resources/services/expected/package/another/__init__.py
@@ -0,0 +1,6 @@
+from .TestService import TestService
+
+__all__ = [
+ 'TestService',
+]
+
diff --git a/conjure-python-core/src/test/resources/services/expected/package/product/CreateDatasetRequest.py b/conjure-python-core/src/test/resources/services/expected/package/product/CreateDatasetRequest.py
new file mode 100644
index 000000000..73bfc003c
--- /dev/null
+++ b/conjure-python-core/src/test/resources/services/expected/package/product/CreateDatasetRequest.py
@@ -0,0 +1,31 @@
+from conjure import ConjureBeanType
+from conjure import ConjureFieldDefinition
+
+class CreateDatasetRequest(ConjureBeanType):
+
+ @classmethod
+ def _fields(cls):
+ # type: () -> Dict[str, ConjureFieldDefinition]
+ return {
+ 'file_system_id': ConjureFieldDefinition('fileSystemId', str),
+ 'path': ConjureFieldDefinition('path', str)
+ }
+
+ _file_system_id = None # type: str
+ _path = None # type: str
+
+ def __init__(self, file_system_id, path):
+ # type: (str, str) -> None
+ self._file_system_id = file_system_id
+ self._path = path
+
+ @property
+ def file_system_id(self):
+ # type: () -> str
+ return self._file_system_id
+
+ @property
+ def path(self):
+ # type: () -> str
+ return self._path
+
diff --git a/conjure-python-core/src/test/resources/services/expected/package/product/__init__.py b/conjure-python-core/src/test/resources/services/expected/package/product/__init__.py
new file mode 100644
index 000000000..b8c6d0f3d
--- /dev/null
+++ b/conjure-python-core/src/test/resources/services/expected/package/product/__init__.py
@@ -0,0 +1,6 @@
+from .CreateDatasetRequest import CreateDatasetRequest
+
+__all__ = [
+ 'CreateDatasetRequest',
+]
+
diff --git a/conjure-python-core/src/test/resources/services/expected/package/product_datasets/BackingFileSystem.py b/conjure-python-core/src/test/resources/services/expected/package/product_datasets/BackingFileSystem.py
new file mode 100644
index 000000000..75672c836
--- /dev/null
+++ b/conjure-python-core/src/test/resources/services/expected/package/product_datasets/BackingFileSystem.py
@@ -0,0 +1,42 @@
+from conjure import ConjureBeanType
+from conjure import ConjureFieldDefinition
+from conjure import DictType
+from typing import Dict
+
+class BackingFileSystem(ConjureBeanType):
+
+ @classmethod
+ def _fields(cls):
+ # type: () -> Dict[str, ConjureFieldDefinition]
+ return {
+ 'file_system_id': ConjureFieldDefinition('fileSystemId', str),
+ 'base_uri': ConjureFieldDefinition('baseUri', str),
+ 'configuration': ConjureFieldDefinition('configuration', DictType(str, str))
+ }
+
+ _file_system_id = None # type: str
+ _base_uri = None # type: str
+ _configuration = None # type: Dict[str, str]
+
+ def __init__(self, file_system_id, base_uri, configuration):
+ # type: (str, str, Dict[str, str]) -> None
+ self._file_system_id = file_system_id
+ self._base_uri = base_uri
+ self._configuration = configuration
+
+ @property
+ def file_system_id(self):
+ # type: () -> str
+ """The name by which this file system is identified."""
+ return self._file_system_id
+
+ @property
+ def base_uri(self):
+ # type: () -> str
+ return self._base_uri
+
+ @property
+ def configuration(self):
+ # type: () -> Dict[str, str]
+ return self._configuration
+
diff --git a/conjure-python-core/src/test/resources/services/expected/package/product_datasets/Dataset.py b/conjure-python-core/src/test/resources/services/expected/package/product_datasets/Dataset.py
new file mode 100644
index 000000000..1b31d4837
--- /dev/null
+++ b/conjure-python-core/src/test/resources/services/expected/package/product_datasets/Dataset.py
@@ -0,0 +1,32 @@
+from conjure import ConjureBeanType
+from conjure import ConjureFieldDefinition
+
+class Dataset(ConjureBeanType):
+
+ @classmethod
+ def _fields(cls):
+ # type: () -> Dict[str, ConjureFieldDefinition]
+ return {
+ 'file_system_id': ConjureFieldDefinition('fileSystemId', str),
+ 'rid': ConjureFieldDefinition('rid', str)
+ }
+
+ _file_system_id = None # type: str
+ _rid = None # type: str
+
+ def __init__(self, file_system_id, rid):
+ # type: (str, str) -> None
+ self._file_system_id = file_system_id
+ self._rid = rid
+
+ @property
+ def file_system_id(self):
+ # type: () -> str
+ return self._file_system_id
+
+ @property
+ def rid(self):
+ # type: () -> str
+ """Uniquely identifies this dataset."""
+ return self._rid
+
diff --git a/conjure-python-core/src/test/resources/services/expected/package/product_datasets/__init__.py b/conjure-python-core/src/test/resources/services/expected/package/product_datasets/__init__.py
new file mode 100644
index 000000000..abadae157
--- /dev/null
+++ b/conjure-python-core/src/test/resources/services/expected/package/product_datasets/__init__.py
@@ -0,0 +1,8 @@
+from .BackingFileSystem import BackingFileSystem
+from .Dataset import Dataset
+
+__all__ = [
+ 'BackingFileSystem',
+ 'Dataset',
+]
+
diff --git a/conjure-python-core/src/test/resources/services/expected/setup.py b/conjure-python-core/src/test/resources/services/expected/setup.py
new file mode 100644
index 000000000..77c44de29
--- /dev/null
+++ b/conjure-python-core/src/test/resources/services/expected/setup.py
@@ -0,0 +1,14 @@
+from setuptools import find_packages
+from setuptools import setup
+
+setup(
+ name='package',
+ version='0.0.0',
+ description='project description',
+ packages=find_packages(),
+ install_requires=[
+ 'requests',
+ 'typing',
+ 'conjure-client>=0.0.0,<1',
+ ],
+)
diff --git a/conjure-python-core/src/test/resources/types/example-conjure-imports.yml b/conjure-python-core/src/test/resources/types/example-conjure-imports.yml
new file mode 100644
index 000000000..6b236636e
--- /dev/null
+++ b/conjure-python-core/src/test/resources/types/example-conjure-imports.yml
@@ -0,0 +1,36 @@
+types:
+ conjure-imports:
+ importedTypes: example-types.yml
+ importedServices: example-service.yml
+ definitions:
+ default-package: test.api.with.imports
+ objects:
+ ComplexObjectWithImports:
+ fields:
+ string: string
+ imported: importedTypes.StringExample
+ UnionWithImports:
+ union:
+ string: string
+ imported: importedTypes.AnyMapExample # make sure this is unique to regression test imports from unions
+ AliasImportedObject:
+ alias: importedTypes.ManyFieldExample
+ AliasImportedPrimitiveAlias:
+ alias: importedTypes.StringAliasExample
+ AliasImportedReferenceAlias:
+ alias: importedTypes.ReferenceAliasExample
+ ImportedAliasInMaps:
+ fields:
+ aliases: map
+
+services:
+ ImportService:
+ name: Test Service
+ package: test.api.with.imports
+ base-path: /catalog
+ endpoints:
+ testEndpoint:
+ http: POST /testEndpoint
+ args:
+ importedString: importedTypes.StringExample
+ returns: importedServices.BackingFileSystem
diff --git a/conjure-python-core/src/test/resources/types/example-nested.yml b/conjure-python-core/src/test/resources/types/example-nested.yml
new file mode 100644
index 000000000..cc2b7f843
--- /dev/null
+++ b/conjure-python-core/src/test/resources/types/example-nested.yml
@@ -0,0 +1,39 @@
+types:
+ definitions:
+ default-package: test.api.nested.service
+ objects:
+ SimpleObject:
+ fields:
+ string: string
+
+services:
+ SimpleNestedService:
+ name: Simple Nested Service
+ package: test.api.nested.service
+ base-path: /catalog
+ endpoints:
+ testEndpoint:
+ http: POST /testEndpoint
+ args:
+ string: string
+ returns: string
+ SimpleNestedService2:
+ name: Simple Nested Service 2
+ package: test.api.nested.service2
+ base-path: /catalog
+ endpoints:
+ testEndpoint:
+ http: POST /testEndpoint
+ args:
+ string: string
+ returns: string
+ DeeplyNestedService:
+ name: Deeply Nested Service
+ package: test.api.nested.deeply.nested.service
+ base-path: /catalog
+ endpoints:
+ testEndpoint:
+ http: POST /testEndpoint
+ args:
+ string: string
+ returns: string
diff --git a/conjure-python-core/src/test/resources/types/example-service.yml b/conjure-python-core/src/test/resources/types/example-service.yml
new file mode 100644
index 000000000..15edc03ad
--- /dev/null
+++ b/conjure-python-core/src/test/resources/types/example-service.yml
@@ -0,0 +1,173 @@
+types:
+ imports:
+ ResourceIdentifier:
+ base-type: string
+ external:
+ java: com.palantir.ri.ResourceIdentifier
+
+ AuthHeader:
+ base-type: string
+ external:
+ java: com.palantir.tokens.auth.AuthHeader
+
+ Safe:
+ external:
+ java: com.palantir.redaction.Safe
+
+ Nonnull:
+ external:
+ java: javax.annotation.Nonnull
+
+ definitions:
+ default-package: com.palantir.product
+ objects:
+ BackingFileSystem:
+ package: com.palantir.product.datasets
+ fields:
+ fileSystemId:
+ type: string
+ docs: The name by which this file system is identified.
+ baseUri: string
+ configuration: map
+
+ Dataset:
+ package: com.palantir.product.datasets
+ fields:
+ fileSystemId: string
+ rid:
+ type: ResourceIdentifier
+ docs: Uniquely identifies this dataset.
+
+ CreateDatasetRequest:
+ fields:
+ fileSystemId: string
+ path: string
+
+services:
+ TestService:
+ name: Test Service
+ package: com.palantir.another
+ default-auth: header
+ base-path: /catalog
+ docs: |
+ A Markdown description of the service.
+
+ endpoints:
+ getFileSystems:
+ markers:
+ - Nonnull # requires ExperimentalFeatures.DangerousGothamMethodMarkers
+ http: GET /fileSystems
+ returns: map
+ docs: |
+ Returns a mapping from file system id to backing file system configuration.
+
+ createDataset:
+ http: POST /datasets
+ args:
+ request: CreateDatasetRequest
+ testHeaderArg:
+ param-id: Test-Header
+ param-type: header
+ type: string
+ returns: Dataset
+
+ getDataset:
+ http: GET /datasets/{datasetRid}
+ args:
+ datasetRid:
+ type: ResourceIdentifier
+ markers:
+ - Safe
+ returns: optional
+
+ getRawData:
+ http: GET /datasets/{datasetRid}/raw
+ args:
+ datasetRid:
+ type: ResourceIdentifier
+ markers:
+ - Safe
+ returns: binary
+
+ maybeGetRawData:
+ http: GET /datasets/{datasetRid}/raw-maybe
+ args:
+ datasetRid:
+ type: ResourceIdentifier
+ markers:
+ - Safe
+ returns: optional
+
+ uploadRawData:
+ http: POST /datasets/upload-raw
+ args:
+ input:
+ type: binary
+ param-type: body
+
+ getBranches:
+ http: GET /datasets/{datasetRid}/branches
+ args:
+ datasetRid:
+ type: ResourceIdentifier
+ docs: |
+ A valid dataset resource identifier.
+ markers:
+ - Safe
+ returns: set
+ getBranchesDeprecated:
+ http: GET /datasets/{datasetRid}/branchesDeprecated
+ args:
+ datasetRid:
+ type: ResourceIdentifier
+ docs: |
+ A valid dataset resource identifier.
+ markers:
+ - Safe
+ returns: set
+ docs: |
+ Gets all branches of this dataset.
+ deprecated: use getBranches instead
+
+ resolveBranch:
+ http: GET /datasets/{datasetRid}/branches/{branch:.+}/resolve
+ args:
+ datasetRid:
+ type: ResourceIdentifier
+ markers:
+ - Safe
+ branch: string
+ returns: optional
+
+ testParam:
+ http: GET /datasets/{datasetRid}/testParam
+ args:
+ datasetRid:
+ type: ResourceIdentifier
+ param-id: datasetRid
+ param-type: path
+ returns: optional
+
+ testQueryParams:
+ http: GET /test-query-params
+ args:
+ something:
+ type: ResourceIdentifier
+ param-id: different
+ param-type: query
+ implicit:
+ type: ResourceIdentifier
+ param-type: query
+ returns: integer
+
+ testBoolean:
+ http: GET /boolean
+ returns: boolean
+
+ testDouble:
+ http: GET /double
+ returns: double
+
+ testInteger:
+ http: GET /integer
+ returns: integer
diff --git a/conjure-python-core/src/test/resources/types/example-types.yml b/conjure-python-core/src/test/resources/types/example-types.yml
new file mode 100644
index 000000000..7b0e3faa9
--- /dev/null
+++ b/conjure-python-core/src/test/resources/types/example-types.yml
@@ -0,0 +1,158 @@
+# don't forget to update conjure-python/src/test/resources/types/example-types.yml
+types:
+ definitions:
+ default-package: com.palantir.product
+ objects:
+ StringExample:
+ fields:
+ string: string
+ IntegerExample:
+ fields:
+ integer: integer
+ SafeLongExample:
+ fields:
+ safeLongValue: safelong
+ RidExample:
+ fields:
+ ridValue: rid
+ BearerTokenExample:
+ fields:
+ bearerTokenValue: bearertoken
+ DateTimeExample:
+ fields:
+ datetime: datetime
+ DoubleExample:
+ fields:
+ doubleValue: double
+ BinaryExample:
+ fields:
+ binary: binary
+ OptionalExample:
+ fields:
+ item: optional
+ ListExample:
+ fields:
+ items: list
+ primitiveItems: list
+ doubleItems: list
+ SetExample:
+ fields:
+ items: set
+ doubleItems: set
+ MapExample:
+ fields:
+ items: map
+ EnumExample:
+ docs: |
+ This enumerates the numbers 1:2.
+ values:
+ - ONE
+ - TWO
+ EnumFieldExample:
+ fields:
+ enum: EnumExample
+ BooleanExample:
+ fields:
+ coin: boolean
+ AnyExample:
+ fields:
+ any: any
+ AnyMapExample:
+ fields:
+ items: map
+ UuidExample:
+ fields:
+ uuid: uuid
+ StringAliasExample:
+ alias: string
+ DoubleAliasExample:
+ alias: double
+ IntegerAliasExample:
+ alias: integer
+ BooleanAliasExample:
+ alias: boolean
+ SafeLongAliasExample:
+ alias: safelong
+ RidAliasExample:
+ alias: rid
+ BearerTokenAliasExample:
+ alias: bearertoken
+ UuidAliasExample:
+ alias: uuid
+ MapAliasExample:
+ alias: map
+ ReferenceAliasExample:
+ alias: AnyExample
+ DateTimeAliasExample:
+ alias: datetime
+ BinaryAliasExample:
+ alias: binary
+ PrimitiveOptionalsExample:
+ fields:
+ num: optional
+ bool: optional
+ integer: optional
+ safelong: optional
+ rid: optional
+ bearertoken: optional
+ uuid: optional
+ ManyFieldExample:
+ fields:
+ string:
+ type: string
+ docs: docs for string field
+ integer:
+ type: integer
+ docs: docs for integer field
+ doubleValue:
+ type: double
+ docs: docs for doubleValue field
+ optionalItem:
+ type: optional
+ docs: docs for optionalItem field
+ items:
+ type: list
+ docs: docs for items field
+ set:
+ type: set
+ docs: docs for set field
+ map:
+ type: map
+ docs: docs for map field
+ alias:
+ type: StringAliasExample
+ docs: docs for alias field
+ UnionTypeExample:
+ docs: A type which can either be a StringExample, a set of strings, or an integer.
+ union:
+ stringExample:
+ type: StringExample
+ docs: Docs for when UnionTypeExample is of type StringExample.
+ set: set
+ thisFieldIsAnInteger: integer
+ alsoAnInteger: integer
+ if: integer # some 'bad' member names!
+ new: integer
+ interface: integer
+ EmptyObjectExample:
+ fields: {}
+ AliasAsMapKeyExample:
+ fields:
+ strings: map
+ rids: map
+ bearertokens: map
+ integers: map
+ # doubles: map # typescript freaks out with the 'NaN'
+ safelongs: map
+ datetimes: map
+ uuids: map
+ ReservedKeyExample:
+ fields:
+ package:
+ type: string
+ interface:
+ type: string
+ field-name-with-dashes:
+ type: string
+ memoizedHashCode:
+ type: integer
diff --git a/conjure-python-core/src/test/resources/types/expected/conda_recipe/meta.yaml b/conjure-python-core/src/test/resources/types/expected/conda_recipe/meta.yaml
new file mode 100644
index 000000000..371519a3a
--- /dev/null
+++ b/conjure-python-core/src/test/resources/types/expected/conda_recipe/meta.yaml
@@ -0,0 +1,25 @@
+
+package:
+ name: package
+ version: 0.0.0
+
+source:
+ path: ../
+
+build:
+ noarch_python: True
+ script: python setup.py install
+
+requirements:
+ build:
+ - python
+ - setuptools
+ - requests
+ - typing
+ - conjure-client >=0.0.0,<1
+
+ run:
+ - python
+ - requests
+ - typing
+ - conjure-client >=0.0.0,<1
diff --git a/conjure-python-core/src/test/resources/types/expected/package/__init__.py b/conjure-python-core/src/test/resources/types/expected/package/__init__.py
new file mode 100644
index 000000000..023153a0b
--- /dev/null
+++ b/conjure-python-core/src/test/resources/types/expected/package/__init__.py
@@ -0,0 +1,13 @@
+
+__all__ = [
+ 'another',
+ 'nested_deeply_nested_service',
+ 'nested_service',
+ 'nested_service2',
+ 'product',
+ 'product_datasets',
+ 'with_imports',
+]
+
+__version__ = "0.0.0"
+
diff --git a/conjure-python-core/src/test/resources/types/expected/package/another/TestService.py b/conjure-python-core/src/test/resources/types/expected/package/another/TestService.py
new file mode 100644
index 000000000..f97343c90
--- /dev/null
+++ b/conjure-python-core/src/test/resources/types/expected/package/another/TestService.py
@@ -0,0 +1,439 @@
+from ..product.CreateDatasetRequest import CreateDatasetRequest
+from ..product_datasets.BackingFileSystem import BackingFileSystem
+from ..product_datasets.Dataset import Dataset
+from conjure import BinaryType
+from conjure import ConjureDecoder
+from conjure import ConjureEncoder
+from conjure import DictType
+from conjure import ListType
+from conjure import OptionalType
+from httpremoting import Service
+from typing import Dict
+from typing import Optional
+from typing import Set
+
+class TestService(Service):
+ """A Markdown description of the service."""
+
+ def get_file_systems(self, auth_header):
+ # type: (str) -> Dict[str, BackingFileSystem]
+ """Returns a mapping from file system id to backing file system configuration."""
+
+ _headers = {
+ 'Accept': 'application/json',
+ 'Authorization': auth_header,
+ } # type: Dict[str, Any]
+
+ _params = {
+ } # type: Dict[str, Any]
+
+ _path_params = {
+ } # type: Dict[str, Any]
+
+ _json = None # type: Any
+
+ _path = '/catalog/fileSystems'
+ _path = _path.format(**_path_params)
+
+ _response = self._request( # type: ignore
+ 'GET',
+ self._uri + _path,
+ params=_params,
+ headers=_headers,
+ json=_json)
+
+ _decoder = ConjureDecoder()
+ return _decoder.decode(_response.json(), DictType(str, BackingFileSystem))
+
+ def create_dataset(self, auth_header, request, test_header_arg):
+ # type: (str, CreateDatasetRequest, str) -> Dataset
+
+ _headers = {
+ 'Accept': 'application/json',
+ 'Content-Type': 'application/json',
+ 'Authorization': auth_header,
+ 'Test-Header': test_header_arg,
+ } # type: Dict[str, Any]
+
+ _params = {
+ } # type: Dict[str, Any]
+
+ _path_params = {
+ } # type: Dict[str, Any]
+
+ _json = ConjureEncoder().default(request) # type: Any
+
+ _path = '/catalog/datasets'
+ _path = _path.format(**_path_params)
+
+ _response = self._request( # type: ignore
+ 'POST',
+ self._uri + _path,
+ params=_params,
+ headers=_headers,
+ json=_json)
+
+ _decoder = ConjureDecoder()
+ return _decoder.decode(_response.json(), Dataset)
+
+ def get_dataset(self, auth_header, dataset_rid):
+ # type: (str, str) -> Optional[Dataset]
+
+ _headers = {
+ 'Accept': 'application/json',
+ 'Authorization': auth_header,
+ } # type: Dict[str, Any]
+
+ _params = {
+ } # type: Dict[str, Any]
+
+ _path_params = {
+ 'datasetRid': dataset_rid,
+ } # type: Dict[str, Any]
+
+ _json = None # type: Any
+
+ _path = '/catalog/datasets/{datasetRid}'
+ _path = _path.format(**_path_params)
+
+ _response = self._request( # type: ignore
+ 'GET',
+ self._uri + _path,
+ params=_params,
+ headers=_headers,
+ json=_json)
+
+ _decoder = ConjureDecoder()
+ return _decoder.decode(_response.json(), OptionalType(Dataset))
+
+ def get_raw_data(self, auth_header, dataset_rid):
+ # type: (str, str) -> Any
+
+ _headers = {
+ 'Accept': 'application/octet-stream',
+ 'Authorization': auth_header,
+ } # type: Dict[str, Any]
+
+ _params = {
+ } # type: Dict[str, Any]
+
+ _path_params = {
+ 'datasetRid': dataset_rid,
+ } # type: Dict[str, Any]
+
+ _json = None # type: Any
+
+ _path = '/catalog/datasets/{datasetRid}/raw'
+ _path = _path.format(**_path_params)
+
+ _response = self._request( # type: ignore
+ 'GET',
+ self._uri + _path,
+ params=_params,
+ headers=_headers,
+ stream=True,
+ json=_json)
+
+ _raw = _response.raw
+ _raw.decode_content = True
+ return _raw
+
+ def maybe_get_raw_data(self, auth_header, dataset_rid):
+ # type: (str, str) -> Optional[Any]
+
+ _headers = {
+ 'Accept': 'application/json',
+ 'Authorization': auth_header,
+ } # type: Dict[str, Any]
+
+ _params = {
+ } # type: Dict[str, Any]
+
+ _path_params = {
+ 'datasetRid': dataset_rid,
+ } # type: Dict[str, Any]
+
+ _json = None # type: Any
+
+ _path = '/catalog/datasets/{datasetRid}/raw-maybe'
+ _path = _path.format(**_path_params)
+
+ _response = self._request( # type: ignore
+ 'GET',
+ self._uri + _path,
+ params=_params,
+ headers=_headers,
+ json=_json)
+
+ _decoder = ConjureDecoder()
+ return _decoder.decode(_response.json(), OptionalType(BinaryType()))
+
+ def upload_raw_data(self, auth_header, input):
+ # type: (str, Any) -> None
+
+ _headers = {
+ 'Accept': 'application/json',
+ 'Content-Type': 'application/json',
+ 'Authorization': auth_header,
+ } # type: Dict[str, Any]
+
+ _params = {
+ } # type: Dict[str, Any]
+
+ _path_params = {
+ } # type: Dict[str, Any]
+
+ _json = ConjureEncoder().default(input) # type: Any
+
+ _path = '/catalog/datasets/upload-raw'
+ _path = _path.format(**_path_params)
+
+ _response = self._request( # type: ignore
+ 'POST',
+ self._uri + _path,
+ params=_params,
+ headers=_headers,
+ json=_json)
+
+ return
+
+ def get_branches(self, auth_header, dataset_rid):
+ # type: (str, str) -> List[str]
+
+ _headers = {
+ 'Accept': 'application/json',
+ 'Authorization': auth_header,
+ } # type: Dict[str, Any]
+
+ _params = {
+ } # type: Dict[str, Any]
+
+ _path_params = {
+ 'datasetRid': dataset_rid,
+ } # type: Dict[str, Any]
+
+ _json = None # type: Any
+
+ _path = '/catalog/datasets/{datasetRid}/branches'
+ _path = _path.format(**_path_params)
+
+ _response = self._request( # type: ignore
+ 'GET',
+ self._uri + _path,
+ params=_params,
+ headers=_headers,
+ json=_json)
+
+ _decoder = ConjureDecoder()
+ return _decoder.decode(_response.json(), ListType(str))
+
+ def get_branches_deprecated(self, auth_header, dataset_rid):
+ # type: (str, str) -> List[str]
+ """Gets all branches of this dataset."""
+
+ _headers = {
+ 'Accept': 'application/json',
+ 'Authorization': auth_header,
+ } # type: Dict[str, Any]
+
+ _params = {
+ } # type: Dict[str, Any]
+
+ _path_params = {
+ 'datasetRid': dataset_rid,
+ } # type: Dict[str, Any]
+
+ _json = None # type: Any
+
+ _path = '/catalog/datasets/{datasetRid}/branchesDeprecated'
+ _path = _path.format(**_path_params)
+
+ _response = self._request( # type: ignore
+ 'GET',
+ self._uri + _path,
+ params=_params,
+ headers=_headers,
+ json=_json)
+
+ _decoder = ConjureDecoder()
+ return _decoder.decode(_response.json(), ListType(str))
+
+ def resolve_branch(self, auth_header, dataset_rid, branch):
+ # type: (str, str, str) -> Optional[str]
+
+ _headers = {
+ 'Accept': 'application/json',
+ 'Authorization': auth_header,
+ } # type: Dict[str, Any]
+
+ _params = {
+ } # type: Dict[str, Any]
+
+ _path_params = {
+ 'datasetRid': dataset_rid,
+ 'branch': branch,
+ } # type: Dict[str, Any]
+
+ _json = None # type: Any
+
+ _path = '/catalog/datasets/{datasetRid}/branches/{branch}/resolve'
+ _path = _path.format(**_path_params)
+
+ _response = self._request( # type: ignore
+ 'GET',
+ self._uri + _path,
+ params=_params,
+ headers=_headers,
+ json=_json)
+
+ _decoder = ConjureDecoder()
+ return _decoder.decode(_response.json(), OptionalType(str))
+
+ def test_param(self, auth_header, dataset_rid):
+ # type: (str, str) -> Optional[str]
+
+ _headers = {
+ 'Accept': 'application/json',
+ 'Authorization': auth_header,
+ } # type: Dict[str, Any]
+
+ _params = {
+ } # type: Dict[str, Any]
+
+ _path_params = {
+ 'datasetRid': dataset_rid,
+ } # type: Dict[str, Any]
+
+ _json = None # type: Any
+
+ _path = '/catalog/datasets/{datasetRid}/testParam'
+ _path = _path.format(**_path_params)
+
+ _response = self._request( # type: ignore
+ 'GET',
+ self._uri + _path,
+ params=_params,
+ headers=_headers,
+ json=_json)
+
+ _decoder = ConjureDecoder()
+ return _decoder.decode(_response.json(), OptionalType(str))
+
+ def test_query_params(self, auth_header, something, implicit):
+ # type: (str, str, str) -> int
+
+ _headers = {
+ 'Accept': 'application/json',
+ 'Authorization': auth_header,
+ } # type: Dict[str, Any]
+
+ _params = {
+ 'different': something,
+ 'implicit': implicit,
+ } # type: Dict[str, Any]
+
+ _path_params = {
+ } # type: Dict[str, Any]
+
+ _json = None # type: Any
+
+ _path = '/catalog/test-query-params'
+ _path = _path.format(**_path_params)
+
+ _response = self._request( # type: ignore
+ 'GET',
+ self._uri + _path,
+ params=_params,
+ headers=_headers,
+ json=_json)
+
+ _decoder = ConjureDecoder()
+ return _decoder.decode(_response.json(), int)
+
+ def test_boolean(self, auth_header):
+ # type: (str) -> bool
+
+ _headers = {
+ 'Accept': 'application/json',
+ 'Authorization': auth_header,
+ } # type: Dict[str, Any]
+
+ _params = {
+ } # type: Dict[str, Any]
+
+ _path_params = {
+ } # type: Dict[str, Any]
+
+ _json = None # type: Any
+
+ _path = '/catalog/boolean'
+ _path = _path.format(**_path_params)
+
+ _response = self._request( # type: ignore
+ 'GET',
+ self._uri + _path,
+ params=_params,
+ headers=_headers,
+ json=_json)
+
+ _decoder = ConjureDecoder()
+ return _decoder.decode(_response.json(), bool)
+
+ def test_double(self, auth_header):
+ # type: (str) -> float
+
+ _headers = {
+ 'Accept': 'application/json',
+ 'Authorization': auth_header,
+ } # type: Dict[str, Any]
+
+ _params = {
+ } # type: Dict[str, Any]
+
+ _path_params = {
+ } # type: Dict[str, Any]
+
+ _json = None # type: Any
+
+ _path = '/catalog/double'
+ _path = _path.format(**_path_params)
+
+ _response = self._request( # type: ignore
+ 'GET',
+ self._uri + _path,
+ params=_params,
+ headers=_headers,
+ json=_json)
+
+ _decoder = ConjureDecoder()
+ return _decoder.decode(_response.json(), float)
+
+ def test_integer(self, auth_header):
+ # type: (str) -> int
+
+ _headers = {
+ 'Accept': 'application/json',
+ 'Authorization': auth_header,
+ } # type: Dict[str, Any]
+
+ _params = {
+ } # type: Dict[str, Any]
+
+ _path_params = {
+ } # type: Dict[str, Any]
+
+ _json = None # type: Any
+
+ _path = '/catalog/integer'
+ _path = _path.format(**_path_params)
+
+ _response = self._request( # type: ignore
+ 'GET',
+ self._uri + _path,
+ params=_params,
+ headers=_headers,
+ json=_json)
+
+ _decoder = ConjureDecoder()
+ return _decoder.decode(_response.json(), int)
+
diff --git a/conjure-python-core/src/test/resources/types/expected/package/another/__init__.py b/conjure-python-core/src/test/resources/types/expected/package/another/__init__.py
new file mode 100644
index 000000000..5465497e8
--- /dev/null
+++ b/conjure-python-core/src/test/resources/types/expected/package/another/__init__.py
@@ -0,0 +1,6 @@
+from .TestService import TestService
+
+__all__ = [
+ 'TestService',
+]
+
diff --git a/conjure-python-core/src/test/resources/types/expected/package/nested_deeply_nested_service/DeeplyNestedService.py b/conjure-python-core/src/test/resources/types/expected/package/nested_deeply_nested_service/DeeplyNestedService.py
new file mode 100644
index 000000000..b2934e752
--- /dev/null
+++ b/conjure-python-core/src/test/resources/types/expected/package/nested_deeply_nested_service/DeeplyNestedService.py
@@ -0,0 +1,35 @@
+from conjure import ConjureDecoder
+from conjure import ConjureEncoder
+from httpremoting import Service
+
+class DeeplyNestedService(Service):
+
+ def test_endpoint(self, string):
+ # type: (str) -> str
+
+ _headers = {
+ 'Accept': 'application/json',
+ 'Content-Type': 'application/json',
+ } # type: Dict[str, Any]
+
+ _params = {
+ } # type: Dict[str, Any]
+
+ _path_params = {
+ } # type: Dict[str, Any]
+
+ _json = ConjureEncoder().default(string) # type: Any
+
+ _path = '/catalog/testEndpoint'
+ _path = _path.format(**_path_params)
+
+ _response = self._request( # type: ignore
+ 'POST',
+ self._uri + _path,
+ params=_params,
+ headers=_headers,
+ json=_json)
+
+ _decoder = ConjureDecoder()
+ return _decoder.decode(_response.json(), str)
+
diff --git a/conjure-python-core/src/test/resources/types/expected/package/nested_deeply_nested_service/__init__.py b/conjure-python-core/src/test/resources/types/expected/package/nested_deeply_nested_service/__init__.py
new file mode 100644
index 000000000..6e42283a3
--- /dev/null
+++ b/conjure-python-core/src/test/resources/types/expected/package/nested_deeply_nested_service/__init__.py
@@ -0,0 +1,6 @@
+from .DeeplyNestedService import DeeplyNestedService
+
+__all__ = [
+ 'DeeplyNestedService',
+]
+
diff --git a/conjure-python-core/src/test/resources/types/expected/package/nested_service/SimpleNestedService.py b/conjure-python-core/src/test/resources/types/expected/package/nested_service/SimpleNestedService.py
new file mode 100644
index 000000000..a09c675a1
--- /dev/null
+++ b/conjure-python-core/src/test/resources/types/expected/package/nested_service/SimpleNestedService.py
@@ -0,0 +1,35 @@
+from conjure import ConjureDecoder
+from conjure import ConjureEncoder
+from httpremoting import Service
+
+class SimpleNestedService(Service):
+
+ def test_endpoint(self, string):
+ # type: (str) -> str
+
+ _headers = {
+ 'Accept': 'application/json',
+ 'Content-Type': 'application/json',
+ } # type: Dict[str, Any]
+
+ _params = {
+ } # type: Dict[str, Any]
+
+ _path_params = {
+ } # type: Dict[str, Any]
+
+ _json = ConjureEncoder().default(string) # type: Any
+
+ _path = '/catalog/testEndpoint'
+ _path = _path.format(**_path_params)
+
+ _response = self._request( # type: ignore
+ 'POST',
+ self._uri + _path,
+ params=_params,
+ headers=_headers,
+ json=_json)
+
+ _decoder = ConjureDecoder()
+ return _decoder.decode(_response.json(), str)
+
diff --git a/conjure-python-core/src/test/resources/types/expected/package/nested_service/SimpleObject.py b/conjure-python-core/src/test/resources/types/expected/package/nested_service/SimpleObject.py
new file mode 100644
index 000000000..cff287b3c
--- /dev/null
+++ b/conjure-python-core/src/test/resources/types/expected/package/nested_service/SimpleObject.py
@@ -0,0 +1,23 @@
+from conjure import ConjureBeanType
+from conjure import ConjureFieldDefinition
+
+class SimpleObject(ConjureBeanType):
+
+ @classmethod
+ def _fields(cls):
+ # type: () -> Dict[str, ConjureFieldDefinition]
+ return {
+ 'string': ConjureFieldDefinition('string', str)
+ }
+
+ _string = None # type: str
+
+ def __init__(self, string):
+ # type: (str) -> None
+ self._string = string
+
+ @property
+ def string(self):
+ # type: () -> str
+ return self._string
+
diff --git a/conjure-python-core/src/test/resources/types/expected/package/nested_service/__init__.py b/conjure-python-core/src/test/resources/types/expected/package/nested_service/__init__.py
new file mode 100644
index 000000000..53b879d61
--- /dev/null
+++ b/conjure-python-core/src/test/resources/types/expected/package/nested_service/__init__.py
@@ -0,0 +1,8 @@
+from .SimpleNestedService import SimpleNestedService
+from .SimpleObject import SimpleObject
+
+__all__ = [
+ 'SimpleNestedService',
+ 'SimpleObject',
+]
+
diff --git a/conjure-python-core/src/test/resources/types/expected/package/nested_service2/SimpleNestedService2.py b/conjure-python-core/src/test/resources/types/expected/package/nested_service2/SimpleNestedService2.py
new file mode 100644
index 000000000..822e94da6
--- /dev/null
+++ b/conjure-python-core/src/test/resources/types/expected/package/nested_service2/SimpleNestedService2.py
@@ -0,0 +1,35 @@
+from conjure import ConjureDecoder
+from conjure import ConjureEncoder
+from httpremoting import Service
+
+class SimpleNestedService2(Service):
+
+ def test_endpoint(self, string):
+ # type: (str) -> str
+
+ _headers = {
+ 'Accept': 'application/json',
+ 'Content-Type': 'application/json',
+ } # type: Dict[str, Any]
+
+ _params = {
+ } # type: Dict[str, Any]
+
+ _path_params = {
+ } # type: Dict[str, Any]
+
+ _json = ConjureEncoder().default(string) # type: Any
+
+ _path = '/catalog/testEndpoint'
+ _path = _path.format(**_path_params)
+
+ _response = self._request( # type: ignore
+ 'POST',
+ self._uri + _path,
+ params=_params,
+ headers=_headers,
+ json=_json)
+
+ _decoder = ConjureDecoder()
+ return _decoder.decode(_response.json(), str)
+
diff --git a/conjure-python-core/src/test/resources/types/expected/package/nested_service2/__init__.py b/conjure-python-core/src/test/resources/types/expected/package/nested_service2/__init__.py
new file mode 100644
index 000000000..1ea302491
--- /dev/null
+++ b/conjure-python-core/src/test/resources/types/expected/package/nested_service2/__init__.py
@@ -0,0 +1,6 @@
+from .SimpleNestedService2 import SimpleNestedService2
+
+__all__ = [
+ 'SimpleNestedService2',
+]
+
diff --git a/conjure-python-core/src/test/resources/types/expected/package/product/AliasAsMapKeyExample.py b/conjure-python-core/src/test/resources/types/expected/package/product/AliasAsMapKeyExample.py
new file mode 100644
index 000000000..763e9c2fb
--- /dev/null
+++ b/conjure-python-core/src/test/resources/types/expected/package/product/AliasAsMapKeyExample.py
@@ -0,0 +1,81 @@
+from .BearerTokenAliasExample import BearerTokenAliasExample
+from .DateTimeAliasExample import DateTimeAliasExample
+from .IntegerAliasExample import IntegerAliasExample
+from .ManyFieldExample import ManyFieldExample
+from .RidAliasExample import RidAliasExample
+from .SafeLongAliasExample import SafeLongAliasExample
+from .StringAliasExample import StringAliasExample
+from .UuidAliasExample import UuidAliasExample
+from conjure import ConjureBeanType
+from conjure import ConjureFieldDefinition
+from conjure import DictType
+from typing import Dict
+
+class AliasAsMapKeyExample(ConjureBeanType):
+
+ @classmethod
+ def _fields(cls):
+ # type: () -> Dict[str, ConjureFieldDefinition]
+ return {
+ 'strings': ConjureFieldDefinition('strings', DictType(StringAliasExample, ManyFieldExample)),
+ 'rids': ConjureFieldDefinition('rids', DictType(RidAliasExample, ManyFieldExample)),
+ 'bearertokens': ConjureFieldDefinition('bearertokens', DictType(BearerTokenAliasExample, ManyFieldExample)),
+ 'integers': ConjureFieldDefinition('integers', DictType(IntegerAliasExample, ManyFieldExample)),
+ 'safelongs': ConjureFieldDefinition('safelongs', DictType(SafeLongAliasExample, ManyFieldExample)),
+ 'datetimes': ConjureFieldDefinition('datetimes', DictType(DateTimeAliasExample, ManyFieldExample)),
+ 'uuids': ConjureFieldDefinition('uuids', DictType(UuidAliasExample, ManyFieldExample))
+ }
+
+ _strings = None # type: Dict[StringAliasExample, ManyFieldExample]
+ _rids = None # type: Dict[RidAliasExample, ManyFieldExample]
+ _bearertokens = None # type: Dict[BearerTokenAliasExample, ManyFieldExample]
+ _integers = None # type: Dict[IntegerAliasExample, ManyFieldExample]
+ _safelongs = None # type: Dict[SafeLongAliasExample, ManyFieldExample]
+ _datetimes = None # type: Dict[DateTimeAliasExample, ManyFieldExample]
+ _uuids = None # type: Dict[UuidAliasExample, ManyFieldExample]
+
+ def __init__(self, strings, rids, bearertokens, integers, safelongs, datetimes, uuids):
+ # type: (Dict[StringAliasExample, ManyFieldExample], Dict[RidAliasExample, ManyFieldExample], Dict[BearerTokenAliasExample, ManyFieldExample], Dict[IntegerAliasExample, ManyFieldExample], Dict[SafeLongAliasExample, ManyFieldExample], Dict[DateTimeAliasExample, ManyFieldExample], Dict[UuidAliasExample, ManyFieldExample]) -> None
+ self._strings = strings
+ self._rids = rids
+ self._bearertokens = bearertokens
+ self._integers = integers
+ self._safelongs = safelongs
+ self._datetimes = datetimes
+ self._uuids = uuids
+
+ @property
+ def strings(self):
+ # type: () -> Dict[StringAliasExample, ManyFieldExample]
+ return self._strings
+
+ @property
+ def rids(self):
+ # type: () -> Dict[RidAliasExample, ManyFieldExample]
+ return self._rids
+
+ @property
+ def bearertokens(self):
+ # type: () -> Dict[BearerTokenAliasExample, ManyFieldExample]
+ return self._bearertokens
+
+ @property
+ def integers(self):
+ # type: () -> Dict[IntegerAliasExample, ManyFieldExample]
+ return self._integers
+
+ @property
+ def safelongs(self):
+ # type: () -> Dict[SafeLongAliasExample, ManyFieldExample]
+ return self._safelongs
+
+ @property
+ def datetimes(self):
+ # type: () -> Dict[DateTimeAliasExample, ManyFieldExample]
+ return self._datetimes
+
+ @property
+ def uuids(self):
+ # type: () -> Dict[UuidAliasExample, ManyFieldExample]
+ return self._uuids
+
diff --git a/conjure-python-core/src/test/resources/types/expected/package/product/AnyExample.py b/conjure-python-core/src/test/resources/types/expected/package/product/AnyExample.py
new file mode 100644
index 000000000..b23fb1074
--- /dev/null
+++ b/conjure-python-core/src/test/resources/types/expected/package/product/AnyExample.py
@@ -0,0 +1,24 @@
+from conjure import ConjureBeanType
+from conjure import ConjureFieldDefinition
+from typing import Any
+
+class AnyExample(ConjureBeanType):
+
+ @classmethod
+ def _fields(cls):
+ # type: () -> Dict[str, ConjureFieldDefinition]
+ return {
+ 'any': ConjureFieldDefinition('any', object)
+ }
+
+ _any = None # type: Any
+
+ def __init__(self, any):
+ # type: (Any) -> None
+ self._any = any
+
+ @property
+ def any(self):
+ # type: () -> Any
+ return self._any
+
diff --git a/conjure-python-core/src/test/resources/types/expected/package/product/AnyMapExample.py b/conjure-python-core/src/test/resources/types/expected/package/product/AnyMapExample.py
new file mode 100644
index 000000000..a8f5f1edf
--- /dev/null
+++ b/conjure-python-core/src/test/resources/types/expected/package/product/AnyMapExample.py
@@ -0,0 +1,26 @@
+from conjure import ConjureBeanType
+from conjure import ConjureFieldDefinition
+from conjure import DictType
+from typing import Any
+from typing import Dict
+
+class AnyMapExample(ConjureBeanType):
+
+ @classmethod
+ def _fields(cls):
+ # type: () -> Dict[str, ConjureFieldDefinition]
+ return {
+ 'items': ConjureFieldDefinition('items', DictType(str, object))
+ }
+
+ _items = None # type: Dict[str, Any]
+
+ def __init__(self, items):
+ # type: (Dict[str, Any]) -> None
+ self._items = items
+
+ @property
+ def items(self):
+ # type: () -> Dict[str, Any]
+ return self._items
+
diff --git a/conjure-python-core/src/test/resources/types/expected/package/product/BearerTokenAliasExample.py b/conjure-python-core/src/test/resources/types/expected/package/product/BearerTokenAliasExample.py
new file mode 100644
index 000000000..efc60a540
--- /dev/null
+++ b/conjure-python-core/src/test/resources/types/expected/package/product/BearerTokenAliasExample.py
@@ -0,0 +1,3 @@
+
+BearerTokenAliasExample = str
+
diff --git a/conjure-python-core/src/test/resources/types/expected/package/product/BearerTokenExample.py b/conjure-python-core/src/test/resources/types/expected/package/product/BearerTokenExample.py
new file mode 100644
index 000000000..420c50f2f
--- /dev/null
+++ b/conjure-python-core/src/test/resources/types/expected/package/product/BearerTokenExample.py
@@ -0,0 +1,23 @@
+from conjure import ConjureBeanType
+from conjure import ConjureFieldDefinition
+
+class BearerTokenExample(ConjureBeanType):
+
+ @classmethod
+ def _fields(cls):
+ # type: () -> Dict[str, ConjureFieldDefinition]
+ return {
+ 'bearer_token_value': ConjureFieldDefinition('bearerTokenValue', str)
+ }
+
+ _bearer_token_value = None # type: str
+
+ def __init__(self, bearer_token_value):
+ # type: (str) -> None
+ self._bearer_token_value = bearer_token_value
+
+ @property
+ def bearer_token_value(self):
+ # type: () -> str
+ return self._bearer_token_value
+
diff --git a/conjure-python-core/src/test/resources/types/expected/package/product/BinaryAliasExample.py b/conjure-python-core/src/test/resources/types/expected/package/product/BinaryAliasExample.py
new file mode 100644
index 000000000..0a3228dc5
--- /dev/null
+++ b/conjure-python-core/src/test/resources/types/expected/package/product/BinaryAliasExample.py
@@ -0,0 +1,4 @@
+from conjure import BinaryType
+
+BinaryAliasExample = BinaryType()
+
diff --git a/conjure-python-core/src/test/resources/types/expected/package/product/BinaryExample.py b/conjure-python-core/src/test/resources/types/expected/package/product/BinaryExample.py
new file mode 100644
index 000000000..40d541ecf
--- /dev/null
+++ b/conjure-python-core/src/test/resources/types/expected/package/product/BinaryExample.py
@@ -0,0 +1,24 @@
+from conjure import BinaryType
+from conjure import ConjureBeanType
+from conjure import ConjureFieldDefinition
+
+class BinaryExample(ConjureBeanType):
+
+ @classmethod
+ def _fields(cls):
+ # type: () -> Dict[str, ConjureFieldDefinition]
+ return {
+ 'binary': ConjureFieldDefinition('binary', BinaryType())
+ }
+
+ _binary = None # type: Any
+
+ def __init__(self, binary):
+ # type: (Any) -> None
+ self._binary = binary
+
+ @property
+ def binary(self):
+ # type: () -> Any
+ return self._binary
+
diff --git a/conjure-python-core/src/test/resources/types/expected/package/product/BooleanAliasExample.py b/conjure-python-core/src/test/resources/types/expected/package/product/BooleanAliasExample.py
new file mode 100644
index 000000000..dc4ba7e44
--- /dev/null
+++ b/conjure-python-core/src/test/resources/types/expected/package/product/BooleanAliasExample.py
@@ -0,0 +1,3 @@
+
+BooleanAliasExample = bool
+
diff --git a/conjure-python-core/src/test/resources/types/expected/package/product/BooleanExample.py b/conjure-python-core/src/test/resources/types/expected/package/product/BooleanExample.py
new file mode 100644
index 000000000..a5dd85f5d
--- /dev/null
+++ b/conjure-python-core/src/test/resources/types/expected/package/product/BooleanExample.py
@@ -0,0 +1,23 @@
+from conjure import ConjureBeanType
+from conjure import ConjureFieldDefinition
+
+class BooleanExample(ConjureBeanType):
+
+ @classmethod
+ def _fields(cls):
+ # type: () -> Dict[str, ConjureFieldDefinition]
+ return {
+ 'coin': ConjureFieldDefinition('coin', bool)
+ }
+
+ _coin = None # type: bool
+
+ def __init__(self, coin):
+ # type: (bool) -> None
+ self._coin = coin
+
+ @property
+ def coin(self):
+ # type: () -> bool
+ return self._coin
+
diff --git a/conjure-python-core/src/test/resources/types/expected/package/product/CreateDatasetRequest.py b/conjure-python-core/src/test/resources/types/expected/package/product/CreateDatasetRequest.py
new file mode 100644
index 000000000..73bfc003c
--- /dev/null
+++ b/conjure-python-core/src/test/resources/types/expected/package/product/CreateDatasetRequest.py
@@ -0,0 +1,31 @@
+from conjure import ConjureBeanType
+from conjure import ConjureFieldDefinition
+
+class CreateDatasetRequest(ConjureBeanType):
+
+ @classmethod
+ def _fields(cls):
+ # type: () -> Dict[str, ConjureFieldDefinition]
+ return {
+ 'file_system_id': ConjureFieldDefinition('fileSystemId', str),
+ 'path': ConjureFieldDefinition('path', str)
+ }
+
+ _file_system_id = None # type: str
+ _path = None # type: str
+
+ def __init__(self, file_system_id, path):
+ # type: (str, str) -> None
+ self._file_system_id = file_system_id
+ self._path = path
+
+ @property
+ def file_system_id(self):
+ # type: () -> str
+ return self._file_system_id
+
+ @property
+ def path(self):
+ # type: () -> str
+ return self._path
+
diff --git a/conjure-python-core/src/test/resources/types/expected/package/product/DateTimeAliasExample.py b/conjure-python-core/src/test/resources/types/expected/package/product/DateTimeAliasExample.py
new file mode 100644
index 000000000..7849e2546
--- /dev/null
+++ b/conjure-python-core/src/test/resources/types/expected/package/product/DateTimeAliasExample.py
@@ -0,0 +1,3 @@
+
+DateTimeAliasExample = str
+
diff --git a/conjure-python-core/src/test/resources/types/expected/package/product/DateTimeExample.py b/conjure-python-core/src/test/resources/types/expected/package/product/DateTimeExample.py
new file mode 100644
index 000000000..d992fabaa
--- /dev/null
+++ b/conjure-python-core/src/test/resources/types/expected/package/product/DateTimeExample.py
@@ -0,0 +1,23 @@
+from conjure import ConjureBeanType
+from conjure import ConjureFieldDefinition
+
+class DateTimeExample(ConjureBeanType):
+
+ @classmethod
+ def _fields(cls):
+ # type: () -> Dict[str, ConjureFieldDefinition]
+ return {
+ 'datetime': ConjureFieldDefinition('datetime', str)
+ }
+
+ _datetime = None # type: str
+
+ def __init__(self, datetime):
+ # type: (str) -> None
+ self._datetime = datetime
+
+ @property
+ def datetime(self):
+ # type: () -> str
+ return self._datetime
+
diff --git a/conjure-python-core/src/test/resources/types/expected/package/product/DoubleAliasExample.py b/conjure-python-core/src/test/resources/types/expected/package/product/DoubleAliasExample.py
new file mode 100644
index 000000000..8bdda955e
--- /dev/null
+++ b/conjure-python-core/src/test/resources/types/expected/package/product/DoubleAliasExample.py
@@ -0,0 +1,3 @@
+
+DoubleAliasExample = float
+
diff --git a/conjure-python-core/src/test/resources/types/expected/package/product/DoubleExample.py b/conjure-python-core/src/test/resources/types/expected/package/product/DoubleExample.py
new file mode 100644
index 000000000..98950ad1c
--- /dev/null
+++ b/conjure-python-core/src/test/resources/types/expected/package/product/DoubleExample.py
@@ -0,0 +1,23 @@
+from conjure import ConjureBeanType
+from conjure import ConjureFieldDefinition
+
+class DoubleExample(ConjureBeanType):
+
+ @classmethod
+ def _fields(cls):
+ # type: () -> Dict[str, ConjureFieldDefinition]
+ return {
+ 'double_value': ConjureFieldDefinition('doubleValue', float)
+ }
+
+ _double_value = None # type: float
+
+ def __init__(self, double_value):
+ # type: (float) -> None
+ self._double_value = double_value
+
+ @property
+ def double_value(self):
+ # type: () -> float
+ return self._double_value
+
diff --git a/conjure-python-core/src/test/resources/types/expected/package/product/EmptyObjectExample.py b/conjure-python-core/src/test/resources/types/expected/package/product/EmptyObjectExample.py
new file mode 100644
index 000000000..01eada21b
--- /dev/null
+++ b/conjure-python-core/src/test/resources/types/expected/package/product/EmptyObjectExample.py
@@ -0,0 +1,13 @@
+from conjure import ConjureBeanType
+from conjure import ConjureFieldDefinition
+
+class EmptyObjectExample(ConjureBeanType):
+
+ @classmethod
+ def _fields(cls):
+ # type: () -> Dict[str, ConjureFieldDefinition]
+ return {
+ }
+
+
+
diff --git a/conjure-python-core/src/test/resources/types/expected/package/product/EnumExample.py b/conjure-python-core/src/test/resources/types/expected/package/product/EnumExample.py
new file mode 100644
index 000000000..049a352db
--- /dev/null
+++ b/conjure-python-core/src/test/resources/types/expected/package/product/EnumExample.py
@@ -0,0 +1,15 @@
+from conjure import ConjureEnumType
+
+class EnumExample(ConjureEnumType):
+ """This enumerates the numbers 1:2."""
+
+ ONE = 'ONE'
+ '''ONE'''
+ TWO = 'TWO'
+ '''TWO'''
+ UNKNOWN = 'UNKNOWN'
+ '''UNKNOWN'''
+
+ def __reduce_ex__(self, proto):
+ return self.__class__, (self.name,)
+
diff --git a/conjure-python-core/src/test/resources/types/expected/package/product/EnumFieldExample.py b/conjure-python-core/src/test/resources/types/expected/package/product/EnumFieldExample.py
new file mode 100644
index 000000000..fb8fac3c5
--- /dev/null
+++ b/conjure-python-core/src/test/resources/types/expected/package/product/EnumFieldExample.py
@@ -0,0 +1,24 @@
+from .EnumExample import EnumExample
+from conjure import ConjureBeanType
+from conjure import ConjureFieldDefinition
+
+class EnumFieldExample(ConjureBeanType):
+
+ @classmethod
+ def _fields(cls):
+ # type: () -> Dict[str, ConjureFieldDefinition]
+ return {
+ 'enum': ConjureFieldDefinition('enum', EnumExample)
+ }
+
+ _enum = None # type: EnumExample
+
+ def __init__(self, enum):
+ # type: (EnumExample) -> None
+ self._enum = enum
+
+ @property
+ def enum(self):
+ # type: () -> EnumExample
+ return self._enum
+
diff --git a/conjure-python-core/src/test/resources/types/expected/package/product/IntegerAliasExample.py b/conjure-python-core/src/test/resources/types/expected/package/product/IntegerAliasExample.py
new file mode 100644
index 000000000..5fa2fe8b7
--- /dev/null
+++ b/conjure-python-core/src/test/resources/types/expected/package/product/IntegerAliasExample.py
@@ -0,0 +1,3 @@
+
+IntegerAliasExample = int
+
diff --git a/conjure-python-core/src/test/resources/types/expected/package/product/IntegerExample.py b/conjure-python-core/src/test/resources/types/expected/package/product/IntegerExample.py
new file mode 100644
index 000000000..31cc33e38
--- /dev/null
+++ b/conjure-python-core/src/test/resources/types/expected/package/product/IntegerExample.py
@@ -0,0 +1,23 @@
+from conjure import ConjureBeanType
+from conjure import ConjureFieldDefinition
+
+class IntegerExample(ConjureBeanType):
+
+ @classmethod
+ def _fields(cls):
+ # type: () -> Dict[str, ConjureFieldDefinition]
+ return {
+ 'integer': ConjureFieldDefinition('integer', int)
+ }
+
+ _integer = None # type: int
+
+ def __init__(self, integer):
+ # type: (int) -> None
+ self._integer = integer
+
+ @property
+ def integer(self):
+ # type: () -> int
+ return self._integer
+
diff --git a/conjure-python-core/src/test/resources/types/expected/package/product/ListExample.py b/conjure-python-core/src/test/resources/types/expected/package/product/ListExample.py
new file mode 100644
index 000000000..f1ad87c67
--- /dev/null
+++ b/conjure-python-core/src/test/resources/types/expected/package/product/ListExample.py
@@ -0,0 +1,41 @@
+from conjure import ConjureBeanType
+from conjure import ConjureFieldDefinition
+from conjure import ListType
+from typing import List
+
+class ListExample(ConjureBeanType):
+
+ @classmethod
+ def _fields(cls):
+ # type: () -> Dict[str, ConjureFieldDefinition]
+ return {
+ 'items': ConjureFieldDefinition('items', ListType(str)),
+ 'primitive_items': ConjureFieldDefinition('primitiveItems', ListType(int)),
+ 'double_items': ConjureFieldDefinition('doubleItems', ListType(float))
+ }
+
+ _items = None # type: List[str]
+ _primitive_items = None # type: List[int]
+ _double_items = None # type: List[float]
+
+ def __init__(self, items, primitive_items, double_items):
+ # type: (List[str], List[int], List[float]) -> None
+ self._items = items
+ self._primitive_items = primitive_items
+ self._double_items = double_items
+
+ @property
+ def items(self):
+ # type: () -> List[str]
+ return self._items
+
+ @property
+ def primitive_items(self):
+ # type: () -> List[int]
+ return self._primitive_items
+
+ @property
+ def double_items(self):
+ # type: () -> List[float]
+ return self._double_items
+
diff --git a/conjure-python-core/src/test/resources/types/expected/package/product/ManyFieldExample.py b/conjure-python-core/src/test/resources/types/expected/package/product/ManyFieldExample.py
new file mode 100644
index 000000000..762b58448
--- /dev/null
+++ b/conjure-python-core/src/test/resources/types/expected/package/product/ManyFieldExample.py
@@ -0,0 +1,95 @@
+from .StringAliasExample import StringAliasExample
+from conjure import ConjureBeanType
+from conjure import ConjureFieldDefinition
+from conjure import DictType
+from conjure import ListType
+from conjure import OptionalType
+from typing import Dict
+from typing import List
+from typing import Optional
+from typing import Set
+
+class ManyFieldExample(ConjureBeanType):
+
+ @classmethod
+ def _fields(cls):
+ # type: () -> Dict[str, ConjureFieldDefinition]
+ return {
+ 'string': ConjureFieldDefinition('string', str),
+ 'integer': ConjureFieldDefinition('integer', int),
+ 'double_value': ConjureFieldDefinition('doubleValue', float),
+ 'optional_item': ConjureFieldDefinition('optionalItem', OptionalType(str)),
+ 'items': ConjureFieldDefinition('items', ListType(str)),
+ 'set': ConjureFieldDefinition('set', ListType(str)),
+ 'map': ConjureFieldDefinition('map', DictType(str, str)),
+ 'alias': ConjureFieldDefinition('alias', StringAliasExample)
+ }
+
+ _string = None # type: str
+ _integer = None # type: int
+ _double_value = None # type: float
+ _optional_item = None # type: Optional[str]
+ _items = None # type: List[str]
+ _set = None # type: List[str]
+ _map = None # type: Dict[str, str]
+ _alias = None # type: StringAliasExample
+
+ def __init__(self, string, integer, double_value, optional_item, items, set, map, alias):
+ # type: (str, int, float, Optional[str], List[str], List[str], Dict[str, str], StringAliasExample) -> None
+ self._string = string
+ self._integer = integer
+ self._double_value = double_value
+ self._optional_item = optional_item
+ self._items = items
+ self._set = set
+ self._map = map
+ self._alias = alias
+
+ @property
+ def string(self):
+ # type: () -> str
+ """docs for string field"""
+ return self._string
+
+ @property
+ def integer(self):
+ # type: () -> int
+ """docs for integer field"""
+ return self._integer
+
+ @property
+ def double_value(self):
+ # type: () -> float
+ """docs for doubleValue field"""
+ return self._double_value
+
+ @property
+ def optional_item(self):
+ # type: () -> Optional[str]
+ """docs for optionalItem field"""
+ return self._optional_item
+
+ @property
+ def items(self):
+ # type: () -> List[str]
+ """docs for items field"""
+ return self._items
+
+ @property
+ def set(self):
+ # type: () -> List[str]
+ """docs for set field"""
+ return self._set
+
+ @property
+ def map(self):
+ # type: () -> Dict[str, str]
+ """docs for map field"""
+ return self._map
+
+ @property
+ def alias(self):
+ # type: () -> StringAliasExample
+ """docs for alias field"""
+ return self._alias
+
diff --git a/conjure-python-core/src/test/resources/types/expected/package/product/MapAliasExample.py b/conjure-python-core/src/test/resources/types/expected/package/product/MapAliasExample.py
new file mode 100644
index 000000000..dcf373a95
--- /dev/null
+++ b/conjure-python-core/src/test/resources/types/expected/package/product/MapAliasExample.py
@@ -0,0 +1,6 @@
+from conjure import DictType
+from typing import Any
+from typing import Dict
+
+MapAliasExample = DictType(str, object)
+
diff --git a/conjure-python-core/src/test/resources/types/expected/package/product/MapExample.py b/conjure-python-core/src/test/resources/types/expected/package/product/MapExample.py
new file mode 100644
index 000000000..e9cbd6e83
--- /dev/null
+++ b/conjure-python-core/src/test/resources/types/expected/package/product/MapExample.py
@@ -0,0 +1,25 @@
+from conjure import ConjureBeanType
+from conjure import ConjureFieldDefinition
+from conjure import DictType
+from typing import Dict
+
+class MapExample(ConjureBeanType):
+
+ @classmethod
+ def _fields(cls):
+ # type: () -> Dict[str, ConjureFieldDefinition]
+ return {
+ 'items': ConjureFieldDefinition('items', DictType(str, str))
+ }
+
+ _items = None # type: Dict[str, str]
+
+ def __init__(self, items):
+ # type: (Dict[str, str]) -> None
+ self._items = items
+
+ @property
+ def items(self):
+ # type: () -> Dict[str, str]
+ return self._items
+
diff --git a/conjure-python-core/src/test/resources/types/expected/package/product/OptionalExample.py b/conjure-python-core/src/test/resources/types/expected/package/product/OptionalExample.py
new file mode 100644
index 000000000..4a2d7b819
--- /dev/null
+++ b/conjure-python-core/src/test/resources/types/expected/package/product/OptionalExample.py
@@ -0,0 +1,25 @@
+from conjure import ConjureBeanType
+from conjure import ConjureFieldDefinition
+from conjure import OptionalType
+from typing import Optional
+
+class OptionalExample(ConjureBeanType):
+
+ @classmethod
+ def _fields(cls):
+ # type: () -> Dict[str, ConjureFieldDefinition]
+ return {
+ 'item': ConjureFieldDefinition('item', OptionalType(str))
+ }
+
+ _item = None # type: Optional[str]
+
+ def __init__(self, item):
+ # type: (Optional[str]) -> None
+ self._item = item
+
+ @property
+ def item(self):
+ # type: () -> Optional[str]
+ return self._item
+
diff --git a/conjure-python-core/src/test/resources/types/expected/package/product/PrimitiveOptionalsExample.py b/conjure-python-core/src/test/resources/types/expected/package/product/PrimitiveOptionalsExample.py
new file mode 100644
index 000000000..c9e611a89
--- /dev/null
+++ b/conjure-python-core/src/test/resources/types/expected/package/product/PrimitiveOptionalsExample.py
@@ -0,0 +1,73 @@
+from conjure import ConjureBeanType
+from conjure import ConjureFieldDefinition
+from conjure import OptionalType
+from typing import Optional
+
+class PrimitiveOptionalsExample(ConjureBeanType):
+
+ @classmethod
+ def _fields(cls):
+ # type: () -> Dict[str, ConjureFieldDefinition]
+ return {
+ 'num': ConjureFieldDefinition('num', OptionalType(float)),
+ 'bool': ConjureFieldDefinition('bool', OptionalType(bool)),
+ 'integer': ConjureFieldDefinition('integer', OptionalType(int)),
+ 'safelong': ConjureFieldDefinition('safelong', OptionalType(int)),
+ 'rid': ConjureFieldDefinition('rid', OptionalType(str)),
+ 'bearertoken': ConjureFieldDefinition('bearertoken', OptionalType(str)),
+ 'uuid': ConjureFieldDefinition('uuid', OptionalType(str))
+ }
+
+ _num = None # type: Optional[float]
+ _bool = None # type: Optional[bool]
+ _integer = None # type: Optional[int]
+ _safelong = None # type: Optional[int]
+ _rid = None # type: Optional[str]
+ _bearertoken = None # type: Optional[str]
+ _uuid = None # type: Optional[str]
+
+ def __init__(self, num, bool, integer, safelong, rid, bearertoken, uuid):
+ # type: (Optional[float], Optional[bool], Optional[int], Optional[int], Optional[str], Optional[str], Optional[str]) -> None
+ self._num = num
+ self._bool = bool
+ self._integer = integer
+ self._safelong = safelong
+ self._rid = rid
+ self._bearertoken = bearertoken
+ self._uuid = uuid
+
+ @property
+ def num(self):
+ # type: () -> Optional[float]
+ return self._num
+
+ @property
+ def bool(self):
+ # type: () -> Optional[bool]
+ return self._bool
+
+ @property
+ def integer(self):
+ # type: () -> Optional[int]
+ return self._integer
+
+ @property
+ def safelong(self):
+ # type: () -> Optional[int]
+ return self._safelong
+
+ @property
+ def rid(self):
+ # type: () -> Optional[str]
+ return self._rid
+
+ @property
+ def bearertoken(self):
+ # type: () -> Optional[str]
+ return self._bearertoken
+
+ @property
+ def uuid(self):
+ # type: () -> Optional[str]
+ return self._uuid
+
diff --git a/conjure-python-core/src/test/resources/types/expected/package/product/ReferenceAliasExample.py b/conjure-python-core/src/test/resources/types/expected/package/product/ReferenceAliasExample.py
new file mode 100644
index 000000000..9e07b0c92
--- /dev/null
+++ b/conjure-python-core/src/test/resources/types/expected/package/product/ReferenceAliasExample.py
@@ -0,0 +1,4 @@
+from .AnyExample import AnyExample
+
+ReferenceAliasExample = AnyExample
+
diff --git a/conjure-python-core/src/test/resources/types/expected/package/product/ReservedKeyExample.py b/conjure-python-core/src/test/resources/types/expected/package/product/ReservedKeyExample.py
new file mode 100644
index 000000000..b8c679b83
--- /dev/null
+++ b/conjure-python-core/src/test/resources/types/expected/package/product/ReservedKeyExample.py
@@ -0,0 +1,47 @@
+from conjure import ConjureBeanType
+from conjure import ConjureFieldDefinition
+
+class ReservedKeyExample(ConjureBeanType):
+
+ @classmethod
+ def _fields(cls):
+ # type: () -> Dict[str, ConjureFieldDefinition]
+ return {
+ 'package': ConjureFieldDefinition('package', str),
+ 'interface': ConjureFieldDefinition('interface', str),
+ 'field_name_with_dashes': ConjureFieldDefinition('field-name-with-dashes', str),
+ 'memoized_hash_code': ConjureFieldDefinition('memoizedHashCode', int)
+ }
+
+ _package = None # type: str
+ _interface = None # type: str
+ _field_name_with_dashes = None # type: str
+ _memoized_hash_code = None # type: int
+
+ def __init__(self, package, interface, field_name_with_dashes, memoized_hash_code):
+ # type: (str, str, str, int) -> None
+ self._package = package
+ self._interface = interface
+ self._field_name_with_dashes = field_name_with_dashes
+ self._memoized_hash_code = memoized_hash_code
+
+ @property
+ def package(self):
+ # type: () -> str
+ return self._package
+
+ @property
+ def interface(self):
+ # type: () -> str
+ return self._interface
+
+ @property
+ def field_name_with_dashes(self):
+ # type: () -> str
+ return self._field_name_with_dashes
+
+ @property
+ def memoized_hash_code(self):
+ # type: () -> int
+ return self._memoized_hash_code
+
diff --git a/conjure-python-core/src/test/resources/types/expected/package/product/RidAliasExample.py b/conjure-python-core/src/test/resources/types/expected/package/product/RidAliasExample.py
new file mode 100644
index 000000000..dc46df370
--- /dev/null
+++ b/conjure-python-core/src/test/resources/types/expected/package/product/RidAliasExample.py
@@ -0,0 +1,3 @@
+
+RidAliasExample = str
+
diff --git a/conjure-python-core/src/test/resources/types/expected/package/product/RidExample.py b/conjure-python-core/src/test/resources/types/expected/package/product/RidExample.py
new file mode 100644
index 000000000..ac2613c88
--- /dev/null
+++ b/conjure-python-core/src/test/resources/types/expected/package/product/RidExample.py
@@ -0,0 +1,23 @@
+from conjure import ConjureBeanType
+from conjure import ConjureFieldDefinition
+
+class RidExample(ConjureBeanType):
+
+ @classmethod
+ def _fields(cls):
+ # type: () -> Dict[str, ConjureFieldDefinition]
+ return {
+ 'rid_value': ConjureFieldDefinition('ridValue', str)
+ }
+
+ _rid_value = None # type: str
+
+ def __init__(self, rid_value):
+ # type: (str) -> None
+ self._rid_value = rid_value
+
+ @property
+ def rid_value(self):
+ # type: () -> str
+ return self._rid_value
+
diff --git a/conjure-python-core/src/test/resources/types/expected/package/product/SafeLongAliasExample.py b/conjure-python-core/src/test/resources/types/expected/package/product/SafeLongAliasExample.py
new file mode 100644
index 000000000..a4c7cde42
--- /dev/null
+++ b/conjure-python-core/src/test/resources/types/expected/package/product/SafeLongAliasExample.py
@@ -0,0 +1,3 @@
+
+SafeLongAliasExample = int
+
diff --git a/conjure-python-core/src/test/resources/types/expected/package/product/SafeLongExample.py b/conjure-python-core/src/test/resources/types/expected/package/product/SafeLongExample.py
new file mode 100644
index 000000000..b13400309
--- /dev/null
+++ b/conjure-python-core/src/test/resources/types/expected/package/product/SafeLongExample.py
@@ -0,0 +1,23 @@
+from conjure import ConjureBeanType
+from conjure import ConjureFieldDefinition
+
+class SafeLongExample(ConjureBeanType):
+
+ @classmethod
+ def _fields(cls):
+ # type: () -> Dict[str, ConjureFieldDefinition]
+ return {
+ 'safe_long_value': ConjureFieldDefinition('safeLongValue', int)
+ }
+
+ _safe_long_value = None # type: int
+
+ def __init__(self, safe_long_value):
+ # type: (int) -> None
+ self._safe_long_value = safe_long_value
+
+ @property
+ def safe_long_value(self):
+ # type: () -> int
+ return self._safe_long_value
+
diff --git a/conjure-python-core/src/test/resources/types/expected/package/product/SetExample.py b/conjure-python-core/src/test/resources/types/expected/package/product/SetExample.py
new file mode 100644
index 000000000..f402d1afd
--- /dev/null
+++ b/conjure-python-core/src/test/resources/types/expected/package/product/SetExample.py
@@ -0,0 +1,33 @@
+from conjure import ConjureBeanType
+from conjure import ConjureFieldDefinition
+from conjure import ListType
+from typing import Set
+
+class SetExample(ConjureBeanType):
+
+ @classmethod
+ def _fields(cls):
+ # type: () -> Dict[str, ConjureFieldDefinition]
+ return {
+ 'items': ConjureFieldDefinition('items', ListType(str)),
+ 'double_items': ConjureFieldDefinition('doubleItems', ListType(float))
+ }
+
+ _items = None # type: List[str]
+ _double_items = None # type: List[float]
+
+ def __init__(self, items, double_items):
+ # type: (List[str], List[float]) -> None
+ self._items = items
+ self._double_items = double_items
+
+ @property
+ def items(self):
+ # type: () -> List[str]
+ return self._items
+
+ @property
+ def double_items(self):
+ # type: () -> List[float]
+ return self._double_items
+
diff --git a/conjure-python-core/src/test/resources/types/expected/package/product/StringAliasExample.py b/conjure-python-core/src/test/resources/types/expected/package/product/StringAliasExample.py
new file mode 100644
index 000000000..2d9946b65
--- /dev/null
+++ b/conjure-python-core/src/test/resources/types/expected/package/product/StringAliasExample.py
@@ -0,0 +1,3 @@
+
+StringAliasExample = str
+
diff --git a/conjure-python-core/src/test/resources/types/expected/package/product/StringExample.py b/conjure-python-core/src/test/resources/types/expected/package/product/StringExample.py
new file mode 100644
index 000000000..fe7c4428b
--- /dev/null
+++ b/conjure-python-core/src/test/resources/types/expected/package/product/StringExample.py
@@ -0,0 +1,23 @@
+from conjure import ConjureBeanType
+from conjure import ConjureFieldDefinition
+
+class StringExample(ConjureBeanType):
+
+ @classmethod
+ def _fields(cls):
+ # type: () -> Dict[str, ConjureFieldDefinition]
+ return {
+ 'string': ConjureFieldDefinition('string', str)
+ }
+
+ _string = None # type: str
+
+ def __init__(self, string):
+ # type: (str) -> None
+ self._string = string
+
+ @property
+ def string(self):
+ # type: () -> str
+ return self._string
+
diff --git a/conjure-python-core/src/test/resources/types/expected/package/product/UnionTypeExample.py b/conjure-python-core/src/test/resources/types/expected/package/product/UnionTypeExample.py
new file mode 100644
index 000000000..a30d82980
--- /dev/null
+++ b/conjure-python-core/src/test/resources/types/expected/package/product/UnionTypeExample.py
@@ -0,0 +1,92 @@
+from .StringExample import StringExample
+from conjure import ConjureFieldDefinition
+from conjure import ConjureUnionType
+from conjure import ListType
+from typing import Set
+
+class UnionTypeExample(ConjureUnionType):
+ """A type which can either be a StringExample, a set of strings, or an integer."""
+
+ _string_example = None # type: StringExample
+ _set = None # type: List[str]
+ _this_field_is_an_integer = None # type: int
+ _also_an_integer = None # type: int
+ _if = None # type: int
+ _new = None # type: int
+ _interface = None # type: int
+
+ @classmethod
+ def _options(cls):
+ # type: () -> Dict[str, ConjureFieldDefinition]
+ return {
+ 'string_example': ConjureFieldDefinition('stringExample', StringExample),
+ 'set': ConjureFieldDefinition('set', ListType(str)),
+ 'this_field_is_an_integer': ConjureFieldDefinition('thisFieldIsAnInteger', int),
+ 'also_an_integer': ConjureFieldDefinition('alsoAnInteger', int),
+ 'if_': ConjureFieldDefinition('if', int),
+ 'new': ConjureFieldDefinition('new', int),
+ 'interface': ConjureFieldDefinition('interface', int)
+ }
+
+ def __init__(self, string_example=None, set=None, this_field_is_an_integer=None, also_an_integer=None, if_=None, new=None, interface=None):
+ if (string_example is not None) + (set is not None) + (this_field_is_an_integer is not None) + (also_an_integer is not None) + (if_ is not None) + (new is not None) + (interface is not None) != 1:
+ raise ValueError('a union must contain a single member')
+
+ if string_example is not None:
+ self._string_example = string_example
+ self._type = 'stringExample'
+ if set is not None:
+ self._set = set
+ self._type = 'set'
+ if this_field_is_an_integer is not None:
+ self._this_field_is_an_integer = this_field_is_an_integer
+ self._type = 'thisFieldIsAnInteger'
+ if also_an_integer is not None:
+ self._also_an_integer = also_an_integer
+ self._type = 'alsoAnInteger'
+ if if_ is not None:
+ self._if = if_
+ self._type = 'if'
+ if new is not None:
+ self._new = new
+ self._type = 'new'
+ if interface is not None:
+ self._interface = interface
+ self._type = 'interface'
+
+ @property
+ def string_example(self):
+ # type: () -> StringExample
+ """Docs for when UnionTypeExample is of type StringExample."""
+ return self._string_example
+
+ @property
+ def set(self):
+ # type: () -> List[str]
+ return self._set
+
+ @property
+ def this_field_is_an_integer(self):
+ # type: () -> int
+ return self._this_field_is_an_integer
+
+ @property
+ def also_an_integer(self):
+ # type: () -> int
+ return self._also_an_integer
+
+ @property
+ def if_(self):
+ # type: () -> int
+ return self._if
+
+ @property
+ def new(self):
+ # type: () -> int
+ return self._new
+
+ @property
+ def interface(self):
+ # type: () -> int
+ return self._interface
+
diff --git a/conjure-python-core/src/test/resources/types/expected/package/product/UuidAliasExample.py b/conjure-python-core/src/test/resources/types/expected/package/product/UuidAliasExample.py
new file mode 100644
index 000000000..75248ae37
--- /dev/null
+++ b/conjure-python-core/src/test/resources/types/expected/package/product/UuidAliasExample.py
@@ -0,0 +1,3 @@
+
+UuidAliasExample = str
+
diff --git a/conjure-python-core/src/test/resources/types/expected/package/product/UuidExample.py b/conjure-python-core/src/test/resources/types/expected/package/product/UuidExample.py
new file mode 100644
index 000000000..08b4c2e47
--- /dev/null
+++ b/conjure-python-core/src/test/resources/types/expected/package/product/UuidExample.py
@@ -0,0 +1,23 @@
+from conjure import ConjureBeanType
+from conjure import ConjureFieldDefinition
+
+class UuidExample(ConjureBeanType):
+
+ @classmethod
+ def _fields(cls):
+ # type: () -> Dict[str, ConjureFieldDefinition]
+ return {
+ 'uuid': ConjureFieldDefinition('uuid', str)
+ }
+
+ _uuid = None # type: str
+
+ def __init__(self, uuid):
+ # type: (str) -> None
+ self._uuid = uuid
+
+ @property
+ def uuid(self):
+ # type: () -> str
+ return self._uuid
+
diff --git a/conjure-python-core/src/test/resources/types/expected/package/product/__init__.py b/conjure-python-core/src/test/resources/types/expected/package/product/__init__.py
new file mode 100644
index 000000000..4c97b2570
--- /dev/null
+++ b/conjure-python-core/src/test/resources/types/expected/package/product/__init__.py
@@ -0,0 +1,78 @@
+from .AliasAsMapKeyExample import AliasAsMapKeyExample
+from .AnyExample import AnyExample
+from .AnyMapExample import AnyMapExample
+from .BearerTokenAliasExample import BearerTokenAliasExample
+from .BearerTokenExample import BearerTokenExample
+from .BinaryAliasExample import BinaryAliasExample
+from .BinaryExample import BinaryExample
+from .BooleanAliasExample import BooleanAliasExample
+from .BooleanExample import BooleanExample
+from .CreateDatasetRequest import CreateDatasetRequest
+from .DateTimeAliasExample import DateTimeAliasExample
+from .DateTimeExample import DateTimeExample
+from .DoubleAliasExample import DoubleAliasExample
+from .DoubleExample import DoubleExample
+from .EmptyObjectExample import EmptyObjectExample
+from .EnumExample import EnumExample
+from .EnumFieldExample import EnumFieldExample
+from .IntegerAliasExample import IntegerAliasExample
+from .IntegerExample import IntegerExample
+from .ListExample import ListExample
+from .ManyFieldExample import ManyFieldExample
+from .MapAliasExample import MapAliasExample
+from .MapExample import MapExample
+from .OptionalExample import OptionalExample
+from .PrimitiveOptionalsExample import PrimitiveOptionalsExample
+from .ReferenceAliasExample import ReferenceAliasExample
+from .ReservedKeyExample import ReservedKeyExample
+from .RidAliasExample import RidAliasExample
+from .RidExample import RidExample
+from .SafeLongAliasExample import SafeLongAliasExample
+from .SafeLongExample import SafeLongExample
+from .SetExample import SetExample
+from .StringAliasExample import StringAliasExample
+from .StringExample import StringExample
+from .UnionTypeExample import UnionTypeExample
+from .UuidAliasExample import UuidAliasExample
+from .UuidExample import UuidExample
+
+__all__ = [
+ 'AliasAsMapKeyExample',
+ 'AnyExample',
+ 'AnyMapExample',
+ 'BearerTokenAliasExample',
+ 'BearerTokenExample',
+ 'BinaryAliasExample',
+ 'BinaryExample',
+ 'BooleanAliasExample',
+ 'BooleanExample',
+ 'CreateDatasetRequest',
+ 'DateTimeAliasExample',
+ 'DateTimeExample',
+ 'DoubleAliasExample',
+ 'DoubleExample',
+ 'EmptyObjectExample',
+ 'EnumExample',
+ 'EnumFieldExample',
+ 'IntegerAliasExample',
+ 'IntegerExample',
+ 'ListExample',
+ 'ManyFieldExample',
+ 'MapAliasExample',
+ 'MapExample',
+ 'OptionalExample',
+ 'PrimitiveOptionalsExample',
+ 'ReferenceAliasExample',
+ 'ReservedKeyExample',
+ 'RidAliasExample',
+ 'RidExample',
+ 'SafeLongAliasExample',
+ 'SafeLongExample',
+ 'SetExample',
+ 'StringAliasExample',
+ 'StringExample',
+ 'UnionTypeExample',
+ 'UuidAliasExample',
+ 'UuidExample',
+]
+
diff --git a/conjure-python-core/src/test/resources/types/expected/package/product_datasets/BackingFileSystem.py b/conjure-python-core/src/test/resources/types/expected/package/product_datasets/BackingFileSystem.py
new file mode 100644
index 000000000..75672c836
--- /dev/null
+++ b/conjure-python-core/src/test/resources/types/expected/package/product_datasets/BackingFileSystem.py
@@ -0,0 +1,42 @@
+from conjure import ConjureBeanType
+from conjure import ConjureFieldDefinition
+from conjure import DictType
+from typing import Dict
+
+class BackingFileSystem(ConjureBeanType):
+
+ @classmethod
+ def _fields(cls):
+ # type: () -> Dict[str, ConjureFieldDefinition]
+ return {
+ 'file_system_id': ConjureFieldDefinition('fileSystemId', str),
+ 'base_uri': ConjureFieldDefinition('baseUri', str),
+ 'configuration': ConjureFieldDefinition('configuration', DictType(str, str))
+ }
+
+ _file_system_id = None # type: str
+ _base_uri = None # type: str
+ _configuration = None # type: Dict[str, str]
+
+ def __init__(self, file_system_id, base_uri, configuration):
+ # type: (str, str, Dict[str, str]) -> None
+ self._file_system_id = file_system_id
+ self._base_uri = base_uri
+ self._configuration = configuration
+
+ @property
+ def file_system_id(self):
+ # type: () -> str
+ """The name by which this file system is identified."""
+ return self._file_system_id
+
+ @property
+ def base_uri(self):
+ # type: () -> str
+ return self._base_uri
+
+ @property
+ def configuration(self):
+ # type: () -> Dict[str, str]
+ return self._configuration
+
diff --git a/conjure-python-core/src/test/resources/types/expected/package/product_datasets/Dataset.py b/conjure-python-core/src/test/resources/types/expected/package/product_datasets/Dataset.py
new file mode 100644
index 000000000..1b31d4837
--- /dev/null
+++ b/conjure-python-core/src/test/resources/types/expected/package/product_datasets/Dataset.py
@@ -0,0 +1,32 @@
+from conjure import ConjureBeanType
+from conjure import ConjureFieldDefinition
+
+class Dataset(ConjureBeanType):
+
+ @classmethod
+ def _fields(cls):
+ # type: () -> Dict[str, ConjureFieldDefinition]
+ return {
+ 'file_system_id': ConjureFieldDefinition('fileSystemId', str),
+ 'rid': ConjureFieldDefinition('rid', str)
+ }
+
+ _file_system_id = None # type: str
+ _rid = None # type: str
+
+ def __init__(self, file_system_id, rid):
+ # type: (str, str) -> None
+ self._file_system_id = file_system_id
+ self._rid = rid
+
+ @property
+ def file_system_id(self):
+ # type: () -> str
+ return self._file_system_id
+
+ @property
+ def rid(self):
+ # type: () -> str
+ """Uniquely identifies this dataset."""
+ return self._rid
+
diff --git a/conjure-python-core/src/test/resources/types/expected/package/product_datasets/__init__.py b/conjure-python-core/src/test/resources/types/expected/package/product_datasets/__init__.py
new file mode 100644
index 000000000..abadae157
--- /dev/null
+++ b/conjure-python-core/src/test/resources/types/expected/package/product_datasets/__init__.py
@@ -0,0 +1,8 @@
+from .BackingFileSystem import BackingFileSystem
+from .Dataset import Dataset
+
+__all__ = [
+ 'BackingFileSystem',
+ 'Dataset',
+]
+
diff --git a/conjure-python-core/src/test/resources/types/expected/package/with_imports/AliasImportedObject.py b/conjure-python-core/src/test/resources/types/expected/package/with_imports/AliasImportedObject.py
new file mode 100644
index 000000000..0e315eb5f
--- /dev/null
+++ b/conjure-python-core/src/test/resources/types/expected/package/with_imports/AliasImportedObject.py
@@ -0,0 +1,4 @@
+from ..product.ManyFieldExample import ManyFieldExample
+
+AliasImportedObject = ManyFieldExample
+
diff --git a/conjure-python-core/src/test/resources/types/expected/package/with_imports/AliasImportedPrimitiveAlias.py b/conjure-python-core/src/test/resources/types/expected/package/with_imports/AliasImportedPrimitiveAlias.py
new file mode 100644
index 000000000..6e5788ae0
--- /dev/null
+++ b/conjure-python-core/src/test/resources/types/expected/package/with_imports/AliasImportedPrimitiveAlias.py
@@ -0,0 +1,4 @@
+from ..product.StringAliasExample import StringAliasExample
+
+AliasImportedPrimitiveAlias = StringAliasExample
+
diff --git a/conjure-python-core/src/test/resources/types/expected/package/with_imports/AliasImportedReferenceAlias.py b/conjure-python-core/src/test/resources/types/expected/package/with_imports/AliasImportedReferenceAlias.py
new file mode 100644
index 000000000..37fbc023d
--- /dev/null
+++ b/conjure-python-core/src/test/resources/types/expected/package/with_imports/AliasImportedReferenceAlias.py
@@ -0,0 +1,4 @@
+from ..product.ReferenceAliasExample import ReferenceAliasExample
+
+AliasImportedReferenceAlias = ReferenceAliasExample
+
diff --git a/conjure-python-core/src/test/resources/types/expected/package/with_imports/ComplexObjectWithImports.py b/conjure-python-core/src/test/resources/types/expected/package/with_imports/ComplexObjectWithImports.py
new file mode 100644
index 000000000..800cb03a1
--- /dev/null
+++ b/conjure-python-core/src/test/resources/types/expected/package/with_imports/ComplexObjectWithImports.py
@@ -0,0 +1,32 @@
+from ..product.StringExample import StringExample
+from conjure import ConjureBeanType
+from conjure import ConjureFieldDefinition
+
+class ComplexObjectWithImports(ConjureBeanType):
+
+ @classmethod
+ def _fields(cls):
+ # type: () -> Dict[str, ConjureFieldDefinition]
+ return {
+ 'string': ConjureFieldDefinition('string', str),
+ 'imported': ConjureFieldDefinition('imported', StringExample)
+ }
+
+ _string = None # type: str
+ _imported = None # type: StringExample
+
+ def __init__(self, string, imported):
+ # type: (str, StringExample) -> None
+ self._string = string
+ self._imported = imported
+
+ @property
+ def string(self):
+ # type: () -> str
+ return self._string
+
+ @property
+ def imported(self):
+ # type: () -> StringExample
+ return self._imported
+
diff --git a/conjure-python-core/src/test/resources/types/expected/package/with_imports/ImportService.py b/conjure-python-core/src/test/resources/types/expected/package/with_imports/ImportService.py
new file mode 100644
index 000000000..ad9be40e0
--- /dev/null
+++ b/conjure-python-core/src/test/resources/types/expected/package/with_imports/ImportService.py
@@ -0,0 +1,37 @@
+from ..product.StringExample import StringExample
+from ..product_datasets.BackingFileSystem import BackingFileSystem
+from conjure import ConjureDecoder
+from conjure import ConjureEncoder
+from httpremoting import Service
+
+class ImportService(Service):
+
+ def test_endpoint(self, imported_string):
+ # type: (StringExample) -> BackingFileSystem
+
+ _headers = {
+ 'Accept': 'application/json',
+ 'Content-Type': 'application/json',
+ } # type: Dict[str, Any]
+
+ _params = {
+ } # type: Dict[str, Any]
+
+ _path_params = {
+ } # type: Dict[str, Any]
+
+ _json = ConjureEncoder().default(imported_string) # type: Any
+
+ _path = '/catalog/testEndpoint'
+ _path = _path.format(**_path_params)
+
+ _response = self._request( # type: ignore
+ 'POST',
+ self._uri + _path,
+ params=_params,
+ headers=_headers,
+ json=_json)
+
+ _decoder = ConjureDecoder()
+ return _decoder.decode(_response.json(), BackingFileSystem)
+
diff --git a/conjure-python-core/src/test/resources/types/expected/package/with_imports/ImportedAliasInMaps.py b/conjure-python-core/src/test/resources/types/expected/package/with_imports/ImportedAliasInMaps.py
new file mode 100644
index 000000000..c1b2159cf
--- /dev/null
+++ b/conjure-python-core/src/test/resources/types/expected/package/with_imports/ImportedAliasInMaps.py
@@ -0,0 +1,27 @@
+from ..product.DateTimeAliasExample import DateTimeAliasExample
+from ..product.RidAliasExample import RidAliasExample
+from conjure import ConjureBeanType
+from conjure import ConjureFieldDefinition
+from conjure import DictType
+from typing import Dict
+
+class ImportedAliasInMaps(ConjureBeanType):
+
+ @classmethod
+ def _fields(cls):
+ # type: () -> Dict[str, ConjureFieldDefinition]
+ return {
+ 'aliases': ConjureFieldDefinition('aliases', DictType(RidAliasExample, DateTimeAliasExample))
+ }
+
+ _aliases = None # type: Dict[RidAliasExample, DateTimeAliasExample]
+
+ def __init__(self, aliases):
+ # type: (Dict[RidAliasExample, DateTimeAliasExample]) -> None
+ self._aliases = aliases
+
+ @property
+ def aliases(self):
+ # type: () -> Dict[RidAliasExample, DateTimeAliasExample]
+ return self._aliases
+
diff --git a/conjure-python-core/src/test/resources/types/expected/package/with_imports/UnionWithImports.py b/conjure-python-core/src/test/resources/types/expected/package/with_imports/UnionWithImports.py
new file mode 100644
index 000000000..cc254da60
--- /dev/null
+++ b/conjure-python-core/src/test/resources/types/expected/package/with_imports/UnionWithImports.py
@@ -0,0 +1,38 @@
+from ..product.AnyMapExample import AnyMapExample
+from conjure import ConjureFieldDefinition
+from conjure import ConjureUnionType
+
+class UnionWithImports(ConjureUnionType):
+
+ _string = None # type: str
+ _imported = None # type: AnyMapExample
+
+ @classmethod
+ def _options(cls):
+ # type: () -> Dict[str, ConjureFieldDefinition]
+ return {
+ 'string': ConjureFieldDefinition('string', str),
+ 'imported': ConjureFieldDefinition('imported', AnyMapExample)
+ }
+
+ def __init__(self, string=None, imported=None):
+ if (string is not None) + (imported is not None) != 1:
+ raise ValueError('a union must contain a single member')
+
+ if string is not None:
+ self._string = string
+ self._type = 'string'
+ if imported is not None:
+ self._imported = imported
+ self._type = 'imported'
+
+ @property
+ def string(self):
+ # type: () -> str
+ return self._string
+
+ @property
+ def imported(self):
+ # type: () -> AnyMapExample
+ return self._imported
+
diff --git a/conjure-python-core/src/test/resources/types/expected/package/with_imports/__init__.py b/conjure-python-core/src/test/resources/types/expected/package/with_imports/__init__.py
new file mode 100644
index 000000000..5776934a3
--- /dev/null
+++ b/conjure-python-core/src/test/resources/types/expected/package/with_imports/__init__.py
@@ -0,0 +1,18 @@
+from .AliasImportedObject import AliasImportedObject
+from .AliasImportedPrimitiveAlias import AliasImportedPrimitiveAlias
+from .AliasImportedReferenceAlias import AliasImportedReferenceAlias
+from .ComplexObjectWithImports import ComplexObjectWithImports
+from .ImportService import ImportService
+from .ImportedAliasInMaps import ImportedAliasInMaps
+from .UnionWithImports import UnionWithImports
+
+__all__ = [
+ 'AliasImportedObject',
+ 'AliasImportedPrimitiveAlias',
+ 'AliasImportedReferenceAlias',
+ 'ComplexObjectWithImports',
+ 'ImportService',
+ 'ImportedAliasInMaps',
+ 'UnionWithImports',
+]
+
diff --git a/conjure-python-core/src/test/resources/types/expected/setup.py b/conjure-python-core/src/test/resources/types/expected/setup.py
new file mode 100644
index 000000000..77c44de29
--- /dev/null
+++ b/conjure-python-core/src/test/resources/types/expected/setup.py
@@ -0,0 +1,14 @@
+from setuptools import find_packages
+from setuptools import setup
+
+setup(
+ name='package',
+ version='0.0.0',
+ description='project description',
+ packages=find_packages(),
+ install_requires=[
+ 'requests',
+ 'typing',
+ 'conjure-client>=0.0.0,<1',
+ ],
+)
diff --git a/conjure-python-verifier/build.gradle b/conjure-python-verifier/build.gradle
new file mode 100644
index 000000000..478b31fb0
--- /dev/null
+++ b/conjure-python-verifier/build.gradle
@@ -0,0 +1,49 @@
+/*
+ * (c) Copyright 2018 Palantir Technologies Inc. All rights reserved.
+ */
+
+import static org.apache.tools.ant.taskdefs.condition.Os.*
+
+ext {
+ osClassifier = isFamily(FAMILY_MAC) ? "osx" : "linux"
+}
+
+configurations {
+ testCases
+ verificationApi
+ verificationServer
+}
+
+dependencies {
+ testCases 'com.palantir.conjure.verification:test-cases'
+ verificationApi 'com.palantir.conjure.verification:verification-api'
+ verificationServer "com.palantir.conjure.verification:verification-server::${osClassifier}@tgz"
+}
+
+task unpackVerificationServer(type: Sync) {
+ from { tarTree(configurations.verificationServer.singleFile) }
+ into "${buildDir}/verification-server"
+ rename { "server" }
+}
+
+task copyTestCases(type: Sync) {
+ from configurations.testCases
+ into "$buildDir/test-cases"
+ rename { "test-cases.json" }
+}
+
+task generateVerifierBindings(type: JavaExec, dependsOn: [copyTestCases, ':conjure-python:compileJava']) {
+ main = "com.palantir.conjure.python.cli.ConjurePythonCli"
+ classpath = project(':conjure-python').sourceSets.main.runtimeClasspath
+ args 'generate',
+ "${-> configurations.verificationApi.singleFile}",
+ 'python/test',
+ '--packageName', 'generated',
+ '--packageVersion', '0.0.0'
+
+ inputs.file configurations.verificationApi.singleFile
+ doLast {delete "python/test/setup.py"}
+}
+
+tasks.idea.dependsOn unpackVerificationServer, generateVerifierBindings
+tasks.build.dependsOn unpackVerificationServer, generateVerifierBindings
diff --git a/conjure-python-verifier/python/Pipfile b/conjure-python-verifier/python/Pipfile
new file mode 100644
index 000000000..4e987222e
--- /dev/null
+++ b/conjure-python-verifier/python/Pipfile
@@ -0,0 +1,20 @@
+[[source]]
+url = "https://pypi.org/simple"
+verify_ssl = true
+name = "pypi"
+
+[packages]
+# TODO(forozco): inject version of conjure-client
+conjure-client = "==0.2.0"
+
+[dev-packages]
+setuptools = "*"
+tox = "*"
+rope = "*"
+pylint = "*"
+wheel = "*"
+pyyaml = "*"
+black = "==18.3a1"
+pycodestyle = "==2.4.0"
+pytest = "==3.2.5"
+mock = "==2.0.0"
diff --git a/conjure-python-verifier/python/Pipfile.lock b/conjure-python-verifier/python/Pipfile.lock
new file mode 100644
index 000000000..58b7e396c
--- /dev/null
+++ b/conjure-python-verifier/python/Pipfile.lock
@@ -0,0 +1,291 @@
+{
+ "_meta": {
+ "hash": {
+ "sha256": "70444e38d2577f9cce85591d6ee384894b0fda8604206c4990a63facf1b76aee"
+ },
+ "pipfile-spec": 6,
+ "requires": {},
+ "sources": [
+ {
+ "name": "pypi",
+ "url": "https://pypi.org/simple",
+ "verify_ssl": true
+ }
+ ]
+ },
+ "default": {
+ "certifi": {
+ "hashes": [
+ "sha256:13e698f54293db9f89122b0581843a782ad0934a4fe0172d2a980ba77fc61bb7",
+ "sha256:9fa520c1bacfb634fa7af20a76bcbd3d5fb390481724c597da32c719a7dca4b0"
+ ],
+ "version": "==2018.4.16"
+ },
+ "chardet": {
+ "hashes": [
+ "sha256:84ab92ed1c4d4f16916e05906b6b75a6c0fb5db821cc65e70cbd64a3e2a5eaae",
+ "sha256:fc323ffcaeaed0e0a02bf4d117757b98aed530d9ed4531e3e15460124c106691"
+ ],
+ "version": "==3.0.4"
+ },
+ "conjure-client": {
+ "hashes": [
+ "sha256:3e5ef49635e574e48428e38c1914ae18e618a7ab450fd4957c88309f708594f6",
+ "sha256:ceae333fb4d4b9aad62c7af9e472e3a13317969577ba32bded2277907fe10087"
+ ],
+ "index": "pypi",
+ "version": "==0.2.0"
+ },
+ "enum34": {
+ "hashes": [
+ "sha256:2d81cbbe0e73112bdfe6ef8576f2238f2ba27dd0d55752a776c41d38b7da2850",
+ "sha256:644837f692e5f550741432dd3f223bbb9852018674981b1664e5dc339387588a",
+ "sha256:6bd0f6ad48ec2aa117d3d141940d484deccda84d4fcd884f5c3d93c23ecd8c79",
+ "sha256:8ad8c4783bf61ded74527bffb48ed9b54166685e4230386a9ed9b1279e2df5b1"
+ ],
+ "version": "==1.1.6"
+ },
+ "idna": {
+ "hashes": [
+ "sha256:156a6814fb5ac1fc6850fb002e0852d56c0c8d2531923a51032d1b70760e186e",
+ "sha256:684a38a6f903c1d71d6d5fac066b58d7768af4de2b832e426ec79c30daa94a16"
+ ],
+ "version": "==2.7"
+ },
+ "requests": {
+ "hashes": [
+ "sha256:63b52e3c866428a224f97cab011de738c36aec0185aa91cfacd418b5d58911d1",
+ "sha256:ec22d826a36ed72a7358ff3fe56cbd4ba69dd7a6718ffd450ff0e9df7a47ce6a"
+ ],
+ "version": "==2.19.1"
+ },
+ "typing": {
+ "hashes": [
+ "sha256:3a887b021a77b292e151afb75323dea88a7bc1b3dfa92176cff8e44c8b68bddf",
+ "sha256:b2c689d54e1144bbcfd191b0832980a21c2dbcf7b5ff7a66248a60c90e951eb8",
+ "sha256:d400a9344254803a2368533e4533a4200d21eb7b6b729c173bc38201a74db3f2"
+ ],
+ "version": "==3.6.4"
+ },
+ "urllib3": {
+ "hashes": [
+ "sha256:a68ac5e15e76e7e5dd2b8f94007233e01effe3e50e8daddf69acfd81cb686baf",
+ "sha256:b5725a0bd4ba422ab0e66e89e030c806576753ea3ee08554382c14e685d117b5"
+ ],
+ "markers": "python_version != '3.3.*' and python_version != '3.2.*' and python_version != '3.1.*' and python_version >= '2.6' and python_version < '4' and python_version != '3.0.*'",
+ "version": "==1.23"
+ }
+ },
+ "develop": {
+ "astroid": {
+ "hashes": [
+ "sha256:0ef2bf9f07c3150929b25e8e61b5198c27b0dca195e156f0e4d5bdd89185ca1a",
+ "sha256:fc9b582dba0366e63540982c3944a9230cbc6f303641c51483fa547dcc22393a"
+ ],
+ "version": "==1.6.5"
+ },
+ "attrs": {
+ "hashes": [
+ "sha256:4b90b09eeeb9b88c35bc642cbac057e45a5fd85367b985bd2809c62b7b939265",
+ "sha256:e0d0eb91441a3b53dab4d9b743eafc1ac44476296a2053b6ca3af0b139faf87b"
+ ],
+ "version": "==18.1.0"
+ },
+ "black": {
+ "hashes": [
+ "sha256:04edc87b51b34a7aa093f39bf207e2ebc3612b26f2c79fdf8ff13b2d60622c9a",
+ "sha256:c9662827051587b732ecb4994a268f218eda896137b3ea6b8af8510682438b79"
+ ],
+ "index": "pypi",
+ "version": "==18.3a1"
+ },
+ "click": {
+ "hashes": [
+ "sha256:29f99fc6125fbc931b758dc053b3114e55c77a6e4c6c3a2674a2dc986016381d",
+ "sha256:f15516df478d5a56180fbf80e68f206010e6d160fc39fa508b65e035fd75130b"
+ ],
+ "version": "==6.7"
+ },
+ "isort": {
+ "hashes": [
+ "sha256:1153601da39a25b14ddc54955dbbacbb6b2d19135386699e2ad58517953b34af",
+ "sha256:b9c40e9750f3d77e6e4d441d8b0266cf555e7cdabdcff33c4fd06366ca761ef8",
+ "sha256:ec9ef8f4a9bc6f71eec99e1806bfa2de401650d996c59330782b89a5555c1497"
+ ],
+ "markers": "python_version != '3.2.*' and python_version >= '2.7' and python_version != '3.0.*' and python_version != '3.3.*' and python_version != '3.1.*'",
+ "version": "==4.3.4"
+ },
+ "lazy-object-proxy": {
+ "hashes": [
+ "sha256:0ce34342b419bd8f018e6666bfef729aec3edf62345a53b537a4dcc115746a33",
+ "sha256:1b668120716eb7ee21d8a38815e5eb3bb8211117d9a90b0f8e21722c0758cc39",
+ "sha256:209615b0fe4624d79e50220ce3310ca1a9445fd8e6d3572a896e7f9146bbf019",
+ "sha256:27bf62cb2b1a2068d443ff7097ee33393f8483b570b475db8ebf7e1cba64f088",
+ "sha256:27ea6fd1c02dcc78172a82fc37fcc0992a94e4cecf53cb6d73f11749825bd98b",
+ "sha256:2c1b21b44ac9beb0fc848d3993924147ba45c4ebc24be19825e57aabbe74a99e",
+ "sha256:2df72ab12046a3496a92476020a1a0abf78b2a7db9ff4dc2036b8dd980203ae6",
+ "sha256:320ffd3de9699d3892048baee45ebfbbf9388a7d65d832d7e580243ade426d2b",
+ "sha256:50e3b9a464d5d08cc5227413db0d1c4707b6172e4d4d915c1c70e4de0bbff1f5",
+ "sha256:5276db7ff62bb7b52f77f1f51ed58850e315154249aceb42e7f4c611f0f847ff",
+ "sha256:61a6cf00dcb1a7f0c773ed4acc509cb636af2d6337a08f362413c76b2b47a8dd",
+ "sha256:6ae6c4cb59f199d8827c5a07546b2ab7e85d262acaccaacd49b62f53f7c456f7",
+ "sha256:7661d401d60d8bf15bb5da39e4dd72f5d764c5aff5a86ef52a042506e3e970ff",
+ "sha256:7bd527f36a605c914efca5d3d014170b2cb184723e423d26b1fb2fd9108e264d",
+ "sha256:7cb54db3535c8686ea12e9535eb087d32421184eacc6939ef15ef50f83a5e7e2",
+ "sha256:7f3a2d740291f7f2c111d86a1c4851b70fb000a6c8883a59660d95ad57b9df35",
+ "sha256:81304b7d8e9c824d058087dcb89144842c8e0dea6d281c031f59f0acf66963d4",
+ "sha256:933947e8b4fbe617a51528b09851685138b49d511af0b6c0da2539115d6d4514",
+ "sha256:94223d7f060301b3a8c09c9b3bc3294b56b2188e7d8179c762a1cda72c979252",
+ "sha256:ab3ca49afcb47058393b0122428358d2fbe0408cf99f1b58b295cfeb4ed39109",
+ "sha256:bd6292f565ca46dee4e737ebcc20742e3b5be2b01556dafe169f6c65d088875f",
+ "sha256:cb924aa3e4a3fb644d0c463cad5bc2572649a6a3f68a7f8e4fbe44aaa6d77e4c",
+ "sha256:d0fc7a286feac9077ec52a927fc9fe8fe2fabab95426722be4c953c9a8bede92",
+ "sha256:ddc34786490a6e4ec0a855d401034cbd1242ef186c20d79d2166d6a4bd449577",
+ "sha256:e34b155e36fa9da7e1b7c738ed7767fc9491a62ec6af70fe9da4a057759edc2d",
+ "sha256:e5b9e8f6bda48460b7b143c3821b21b452cb3a835e6bbd5dd33aa0c8d3f5137d",
+ "sha256:e81ebf6c5ee9684be8f2c87563880f93eedd56dd2b6146d8a725b50b7e5adb0f",
+ "sha256:eb91be369f945f10d3a49f5f9be8b3d0b93a4c2be8f8a5b83b0571b8123e0a7a",
+ "sha256:f460d1ceb0e4a5dcb2a652db0904224f367c9b3c1470d5a7683c0480e582468b"
+ ],
+ "version": "==1.3.1"
+ },
+ "mccabe": {
+ "hashes": [
+ "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42",
+ "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"
+ ],
+ "version": "==0.6.1"
+ },
+ "mock": {
+ "hashes": [
+ "sha256:5ce3c71c5545b472da17b72268978914d0252980348636840bd34a00b5cc96c1",
+ "sha256:b158b6df76edd239b8208d481dc46b6afd45a846b7812ff0ce58971cf5bc8bba"
+ ],
+ "index": "pypi",
+ "version": "==2.0.0"
+ },
+ "packaging": {
+ "hashes": [
+ "sha256:e9215d2d2535d3ae866c3d6efc77d5b24a0192cce0ff20e42896cc0664f889c0",
+ "sha256:f019b770dd64e585a99714f1fd5e01c7a8f11b45635aa953fd41c689a657375b"
+ ],
+ "version": "==17.1"
+ },
+ "pbr": {
+ "hashes": [
+ "sha256:4f2b11d95917af76e936811be8361b2b19616e5ef3b55956a429ec7864378e0c",
+ "sha256:e0f23b61ec42473723b2fec2f33fb12558ff221ee551962f01dd4de9053c2055"
+ ],
+ "version": "==4.1.0"
+ },
+ "pluggy": {
+ "hashes": [
+ "sha256:7f8ae7f5bdf75671a718d2daf0a64b7885f74510bcd98b1a0bb420eb9a9d0cff",
+ "sha256:d345c8fe681115900d6da8d048ba67c25df42973bda370783cd58826442dcd7c",
+ "sha256:e160a7fcf25762bb60efc7e171d4497ff1d8d2d75a3d0df7a21b76821ecbf5c5"
+ ],
+ "markers": "python_version != '3.2.*' and python_version >= '2.7' and python_version != '3.0.*' and python_version != '3.3.*' and python_version != '3.1.*'",
+ "version": "==0.6.0"
+ },
+ "py": {
+ "hashes": [
+ "sha256:3fd59af7435864e1a243790d322d763925431213b6b8529c6ca71081ace3bbf7",
+ "sha256:e31fb2767eb657cbde86c454f02e99cb846d3cd9d61b318525140214fdc0e98e"
+ ],
+ "markers": "python_version != '3.2.*' and python_version >= '2.7' and python_version != '3.0.*' and python_version != '3.3.*' and python_version != '3.1.*'",
+ "version": "==1.5.4"
+ },
+ "pycodestyle": {
+ "hashes": [
+ "sha256:cbc619d09254895b0d12c2c691e237b2e91e9b2ecf5e84c26b35400f93dcfb83",
+ "sha256:cbfca99bd594a10f674d0cd97a3d802a1fdef635d4361e1a2658de47ed261e3a"
+ ],
+ "index": "pypi",
+ "version": "==2.4.0"
+ },
+ "pylint": {
+ "hashes": [
+ "sha256:a48070545c12430cfc4e865bf62f5ad367784765681b3db442d8230f0960aa3c",
+ "sha256:fff220bcb996b4f7e2b0f6812fd81507b72ca4d8c4d05daf2655c333800cb9b3"
+ ],
+ "index": "pypi",
+ "version": "==1.9.2"
+ },
+ "pyparsing": {
+ "hashes": [
+ "sha256:0832bcf47acd283788593e7a0f542407bd9550a55a8a8435214a1960e04bcb04",
+ "sha256:fee43f17a9c4087e7ed1605bd6df994c6173c1e977d7ade7b651292fab2bd010"
+ ],
+ "version": "==2.2.0"
+ },
+ "pytest": {
+ "hashes": [
+ "sha256:241d7e7798d79192a123ceaf64c602b4d233eacf6d6e42ae27caa97f498b7dc6",
+ "sha256:6d5bd4f7113b444c55a3bbb5c738a3dd80d43563d063fc42dcb0aaefbdd78b81"
+ ],
+ "index": "pypi",
+ "version": "==3.2.5"
+ },
+ "pyyaml": {
+ "hashes": [
+ "sha256:3d7da3009c0f3e783b2c873687652d83b1bbfd5c88e9813fb7e5b03c0dd3108b",
+ "sha256:3ef3092145e9b70e3ddd2c7ad59bdd0252a94dfe3949721633e41344de00a6bf",
+ "sha256:40c71b8e076d0550b2e6380bada1f1cd1017b882f7e16f09a65be98e017f211a",
+ "sha256:558dd60b890ba8fd982e05941927a3911dc409a63dcb8b634feaa0cda69330d3",
+ "sha256:a7c28b45d9f99102fa092bb213aa12e0aaf9a6a1f5e395d36166639c1f96c3a1",
+ "sha256:aa7dd4a6a427aed7df6fb7f08a580d68d9b118d90310374716ae90b710280af1",
+ "sha256:bc558586e6045763782014934bfaf39d48b8ae85a2713117d16c39864085c613",
+ "sha256:d46d7982b62e0729ad0175a9bc7e10a566fc07b224d2c79fafb5e032727eaa04",
+ "sha256:d5eef459e30b09f5a098b9cea68bebfeb268697f78d647bd255a085371ac7f3f",
+ "sha256:e01d3203230e1786cd91ccfdc8f8454c8069c91bee3962ad93b87a4b2860f537",
+ "sha256:e170a9e6fcfd19021dd29845af83bb79236068bf5fd4df3327c1be18182b2531"
+ ],
+ "index": "pypi",
+ "version": "==3.13"
+ },
+ "rope": {
+ "hashes": [
+ "sha256:a09edfd2034fd50099a67822f9bd851fbd0f4e98d3b87519f6267b60e50d80d1"
+ ],
+ "index": "pypi",
+ "version": "==0.10.7"
+ },
+ "six": {
+ "hashes": [
+ "sha256:70e8a77beed4562e7f14fe23a786b54f6296e34344c23bc42f07b15018ff98e9",
+ "sha256:832dc0e10feb1aa2c68dcc57dbb658f1c7e65b9b61af69048abc87a2db00a0eb"
+ ],
+ "version": "==1.11.0"
+ },
+ "tox": {
+ "hashes": [
+ "sha256:8df73fb0eae939692d67a095c49081b1afb948eca51879e5dc1868d9b0ad11de",
+ "sha256:9f09ec569b5019ed030d3ed3d486a9263e8964a9752253a98f5d67b46e954055"
+ ],
+ "index": "pypi",
+ "version": "==3.1.1"
+ },
+ "virtualenv": {
+ "hashes": [
+ "sha256:2ce32cd126117ce2c539f0134eb89de91a8413a29baac49cbab3eb50e2026669",
+ "sha256:ca07b4c0b54e14a91af9f34d0919790b016923d157afda5efdde55c96718f752"
+ ],
+ "markers": "python_version >= '2.7' and python_version != '3.2.*' and python_version != '3.0.*' and python_version != '3.1.*'",
+ "version": "==16.0.0"
+ },
+ "wheel": {
+ "hashes": [
+ "sha256:0a2e54558a0628f2145d2fc822137e322412115173e8a2ddbe1c9024338ae83c",
+ "sha256:80044e51ec5bbf6c894ba0bc48d26a8c20a9ba629f4ca19ea26ecfcf87685f5f"
+ ],
+ "index": "pypi",
+ "version": "==0.31.1"
+ },
+ "wrapt": {
+ "hashes": [
+ "sha256:d4d560d479f2c21e1b5443bbd15fe7ec4b37fe7e53d335d3b9b0a7b1226fe3c6"
+ ],
+ "version": "==1.10.11"
+ }
+ }
+}
diff --git a/conjure-python-verifier/python/mypy.ini b/conjure-python-verifier/python/mypy.ini
new file mode 100644
index 000000000..3856bf227
--- /dev/null
+++ b/conjure-python-verifier/python/mypy.ini
@@ -0,0 +1,4 @@
+[mypy]
+check_untyped_defs = True
+verbosity = 0
+show_column_numbers = True
diff --git a/conjure-python-verifier/python/setup.py b/conjure-python-verifier/python/setup.py
new file mode 100644
index 000000000..f1faf3a1e
--- /dev/null
+++ b/conjure-python-verifier/python/setup.py
@@ -0,0 +1,35 @@
+# (c) Copyright 2018 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.
+
+#!/usr/bin/env python
+
+from setuptools import find_packages, setup
+
+setup(
+ name="conjure-python-verifier",
+ version="0.0.0",
+ description="Test cases to verify wire compatibility of generated clients",
+ # The project's main homepage.
+ url="https://github.com/palantir/conjure-python-client",
+ author="Palantir Technologies, Inc.",
+ # You can just specify the packages manually here if your project is
+ # simple. Or you can use find_packages().
+ packages=find_packages(exclude=["test*", "integration*"]),
+ # List run-time dependencies here. These will be installed by pip when
+ # your project is installed. For an analysis of "install_requires" vs pip's
+ # requirements files see:
+ # https://packaging.python.org/en/latest/requirements.html
+ install_requires=["conjure-client", "pyyaml"],
+ tests_require=["pytest", "pyyaml"],
+)
diff --git a/conjure-python-verifier/python/test/__init__.py b/conjure-python-verifier/python/test/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/conjure-python-verifier/python/test/client/__init__.py b/conjure-python-verifier/python/test/client/__init__.py
new file mode 100644
index 000000000..e69de29bb
diff --git a/conjure-python-verifier/python/test/client/conftest.py b/conjure-python-verifier/python/test/client/conftest.py
new file mode 100644
index 000000000..560126a98
--- /dev/null
+++ b/conjure-python-verifier/python/test/client/conftest.py
@@ -0,0 +1,60 @@
+import pytest
+import subprocess
+import yaml
+from httpremoting import RequestsClient, ServiceConfiguration
+from os import path
+
+from ..generated.conjure_verification import (
+ AutoDeserializeConfirmService,
+ AutoDeserializeService,
+ SingleHeaderService,
+ SinglePathParamService,
+ SingleQueryParamService
+)
+
+TEST_CASES = path.dirname(__file__) + '/../../../build/test-cases/test-cases.json'
+
+
+@pytest.fixture(scope='module')
+def conjure_validation_server():
+ verification_server = subprocess.Popen([
+ path.dirname(__file__) + '/../../../build/verification-server/server', TEST_CASES])
+ yield verification_server
+ verification_server.terminate()
+
+
+@pytest.fixture()
+def config():
+ config = ServiceConfiguration()
+ config.uris = ['http://localhost:8000']
+ return config
+
+
+@pytest.fixture()
+def body_service(config):
+ return RequestsClient.create(AutoDeserializeService, 'conjure-python/0.0.0', config)
+
+
+@pytest.fixture()
+def header_service(config):
+ return RequestsClient.create(SingleHeaderService, 'conjure-python/0.0.0', config)
+
+
+@pytest.fixture()
+def path_service(config):
+ return RequestsClient.create(SinglePathParamService, 'conjure-python/0.0.0', config)
+
+
+@pytest.fixture()
+def query_service(config):
+ return RequestsClient.create(SingleQueryParamService, 'conjure-python/0.0.0', config)
+
+
+@pytest.fixture()
+def confirm_service(config):
+ return RequestsClient.create(AutoDeserializeConfirmService, 'conjure-python/0.0.0', config)
+
+@pytest.fixture(scope='module')
+def test_black_list():
+ with open(path.dirname(__file__) + '/../../../resources/ignored_test_cases.yml') as blacklist_file:
+ return yaml.load(blacklist_file)['client']
diff --git a/conjure-python-verifier/python/test/client/test_verify_spec.py b/conjure-python-verifier/python/test/client/test_verify_spec.py
new file mode 100644
index 000000000..49f01be88
--- /dev/null
+++ b/conjure-python-verifier/python/test/client/test_verify_spec.py
@@ -0,0 +1,84 @@
+import json
+import pytest
+import re
+from os import path
+
+CUR_DIR = path.dirname(__file__)
+TEST_CASES = CUR_DIR + '/../../../build/test-cases/test-cases.json'
+
+
+def convert_to_snake_case(name):
+ s1 = re.sub('(.)([A-Z][a-z]+)', r'\1_\2', name)
+ return re.sub('([a-z0-9])([A-Z])', r'\1_\2', s1).lower()
+
+
+def load_test_cases():
+ return json.load(open(TEST_CASES))['client']
+
+
+def generate_auto_deserialize_tests():
+ test_cases = load_test_cases()
+ all_cases = []
+ for endpoint_name, test_kinds in test_cases['autoDeserialize'].items():
+ method_name = convert_to_snake_case(endpoint_name)
+ positive_count = len(test_kinds['positive'])
+ all_cases.extend([(endpoint_name, method_name, i, case, True)
+ for i, case in enumerate(test_kinds['positive'])])
+ all_cases.extend([(endpoint_name, method_name, i + positive_count, case, False)
+ for i, case in enumerate(test_kinds['negative'])])
+
+ return all_cases
+
+
+def generate_param_tests(test_kind):
+ test_cases = load_test_cases()
+ return [(endpoint_name, convert_to_snake_case(endpoint_name), i, value)
+ for endpoint_name, test_kinds in test_cases[test_kind].items()
+ for i, value in enumerate(test_kinds)]
+
+
+@pytest.mark.parametrize('endpoint_name,method_name,index,case,should_pass', generate_auto_deserialize_tests())
+def test_body(
+ conjure_validation_server,
+ test_black_list,
+ body_service,
+ confirm_service,
+ endpoint_name,
+ method_name,
+ index,
+ case,
+ should_pass):
+ body_black_list = test_black_list['autoDeserialize']
+ if endpoint_name in body_black_list and case in body_black_list[endpoint_name]:
+ pytest.skip("Blacklisted")
+
+ if should_pass:
+ return confirm_service.confirm(endpoint_name, index, getattr(body_service, method_name)(index))
+ else:
+ with pytest.raises(Exception):
+ getattr(body_service, method_name)(index)
+
+
+@pytest.mark.parametrize('endpoint_name,method_name,index,value', generate_param_tests('singleHeaderService'))
+def test_header(conjure_validation_server, test_black_list, header_service, endpoint_name, method_name, index, value):
+ header_black_list = test_black_list['singleHeaderService']
+ if endpoint_name in header_black_list and value in header_black_list[endpoint_name]:
+ pytest.skip("Blacklisted")
+
+ return getattr(header_service, method_name)(index, json.loads(value))
+
+
+@pytest.mark.parametrize('endpoint_name,method_name,index,value', generate_param_tests('singlePathParamService'))
+def test_path(conjure_validation_server, test_black_list, path_service, endpoint_name, method_name, index, value):
+ header_black_list = test_black_list['singlePathParamService']
+ if True or endpoint_name in header_black_list and value in header_black_list[endpoint_name]:
+ pytest.skip("Blacklisted")
+ return getattr(path_service, method_name)(index, json.loads(value))
+
+
+@pytest.mark.parametrize('endpoint_name,method_name,index,value', generate_param_tests('singleQueryParamService'))
+def test_query(conjure_validation_server, test_black_list, query_service, endpoint_name, method_name, index, value):
+ query_black_list = test_black_list['singleQueryService']
+ if endpoint_name in query_black_list and value in query_black_list[endpoint_name]:
+ pytest.skip("Blacklisted")
+ return getattr(query_service, method_name)(index, json.loads(value))
diff --git a/conjure-python-verifier/python/tox.ini b/conjure-python-verifier/python/tox.ini
new file mode 100644
index 000000000..929461032
--- /dev/null
+++ b/conjure-python-verifier/python/tox.ini
@@ -0,0 +1,19 @@
+# Tox (http://tox.testrun.org/) is a tool for running tests
+# in multiple virtualenvs. This configuration file will run the
+# test suite on all supported python versions. To use it, "pip install tox"
+# and then run "tox" from this directory.
+
+[tox]
+envlist = py{27,3}
+
+[testenv]
+commands =
+ pytest -v --capture=no --junitxml=./build/pytest-{envname}.xml --html=./build/pytest-{envname}.html --self-contained-html {posargs:test/}
+deps =
+ mock==2.0.0
+ pytest==3.2.5
+ pytest-pylint==0.9.0
+ pytest-html==1.16.1
+setenv =
+ PYTHONDONTWRITEBYTECODE = 1
+ ROOT_PROJECT_DIR = {toxinidir}
diff --git a/conjure-python-verifier/resources/ignored_test_cases.yml b/conjure-python-verifier/resources/ignored_test_cases.yml
new file mode 100644
index 000000000..b246bcd83
--- /dev/null
+++ b/conjure-python-verifier/resources/ignored_test_cases.yml
@@ -0,0 +1,38 @@
+client:
+ autoDeserialize:
+ receiveBearerTokenAliasExample: ['""']
+ receiveBearerTokenExample: ['{"value":" space"}', '{"value":"space "}', '{"value":"with
+ space"}', '{"value":""}', '{"value":"#"}', '{"value":" "}', '{"value":"("}',
+ '{"value":"="}', '{"value":"=a"}']
+ receiveDateTimeAliasExample: ['""']
+ receiveDateTimeExample: ['{"value":"4/3/2018"}', '{"value":"1523040070"}', '{"value":"2017-01-02T03:04:05.0000000000Z"}',
+ '{"value":"2017-01-02T04:04:05.000000000+01:00[Europe/Berlin][Another/Zone]"}']
+ receiveDoubleAliasExample: ['10']
+ receiveDoubleExample: ['{"value":13}', '{"value":"1.23"}', '{"value":"nan"}']
+ receiveIntegerAliasExample: ['0']
+ receiveIntegerExample: ['{"value":-2147483649}', '{"value":2147483648}']
+ receiveListExample: ['{}']
+ receiveMapExample: ['{}']
+ receiveOptionalExample: ['{}']
+ receiveRidAliasExample: ['""', '"badString"', '"ri.service.CAPLOCK.type.name"',
+ '"ri.service.instance.-123.name"', '"ri..instance.type.noService"', '"ri.service.instance.type."',
+ '"id.bad.id.class.b.name"', '"ri:service::instance:type:name"', '"ri.service.instance.type.name!@#"',
+ '"ri.service(name)..folder.foo"', '""']
+ receiveRidExample: ['{"value":""}', '{"value":"badString"}', '{"value":"ri.service.CAPLOCK.type.name"}',
+ '{"value":"ri.service.instance.-123.name"}', '{"value":"ri..instance.type.noService"}',
+ '{"value":"ri.service.instance.type."}', '{"value":"id.bad.id.class.b.name"}',
+ '{"value":"ri:service::instance:type:name"}', '{"value":"ri.service.instance.type.name!@#"}',
+ '{"value":"ri.service(name)..folder.foo"}']
+ receiveSafeLongAliasExample: ['-9007199254740992', '9007199254740992']
+ receiveSafeLongExample: ['{"value":null}', '{}', '{"value":-9007199254740992}',
+ '{"value":9007199254740992}']
+ receiveSetDoubleExample: ['{}']
+ receiveSetStringExample: ['{}', '{"value":["a","a"]}']
+ receiveUuidAliasExample: ['""', '"80e6dd13-5f42-4e33-ad18"']
+ receiveUuidExample: ['{"value":""}', '{"value":"80e6dd13-5f42-4e33-ad18"}']
+ singleHeaderService:
+ headerDouble: ['10.0']
+ singlePathParamService: {}
+ singleQueryService:
+ queryParamDouble: ['10.0']
+
diff --git a/conjure-python/.gitignore b/conjure-python/.gitignore
new file mode 100644
index 000000000..e8399334d
--- /dev/null
+++ b/conjure-python/.gitignore
@@ -0,0 +1 @@
+src/main/resources/buildConfiguration.yml
diff --git a/conjure-python/build.gradle b/conjure-python/build.gradle
new file mode 100644
index 000000000..1db3f5f60
--- /dev/null
+++ b/conjure-python/build.gradle
@@ -0,0 +1,40 @@
+/*
+ * (c) Copyright 2018 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.
+ */
+
+apply from: "$rootDir/gradle/publish-dist.gradle"
+
+mainClassName = 'com.palantir.conjure.python.cli.ConjurePythonCli'
+
+dependencies {
+ compile project(':conjure-python-core')
+ compile 'commons-cli:commons-cli'
+
+ testCompile 'junit:junit'
+ testCompile 'org.assertj:assertj-core'
+
+ processor 'org.immutables:value'
+}
+
+task generateBuildConfiguration() {
+ doLast {
+ def minConjureClientVersion = dependencyRecommendations.getRecommendedVersion('com.palantir.conjure.python', 'conjure-client')
+ def props = new File("$projectDir/src/main/resources/buildConfiguration.yml")
+ props.getParentFile().mkdirs()
+ props.text = "minConjureClientVersion: $minConjureClientVersion"
+ }
+}
+
+compileJava.dependsOn generateBuildConfiguration
diff --git a/conjure-python/src/main/java/com/palantir/conjure/python/cli/BuildConfiguration.java b/conjure-python/src/main/java/com/palantir/conjure/python/cli/BuildConfiguration.java
new file mode 100644
index 000000000..12539776a
--- /dev/null
+++ b/conjure-python/src/main/java/com/palantir/conjure/python/cli/BuildConfiguration.java
@@ -0,0 +1,32 @@
+/*
+ * (c) Copyright 2018 Palantir Technologies Inc. All rights reserved.
+ */
+
+package com.palantir.conjure.python.cli;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import com.fasterxml.jackson.dataformat.yaml.YAMLFactory;
+import com.palantir.tokens.auth.ImmutablesStyle;
+import java.io.IOException;
+import org.immutables.value.Value;
+
+@Value.Immutable
+@ImmutablesStyle
+@JsonDeserialize(as = ImmutableBuildConfiguration.class)
+public abstract class BuildConfiguration {
+
+ private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(new YAMLFactory());
+
+ abstract String minConjureClientVersion();
+
+ static BuildConfiguration load() {
+ try {
+ return OBJECT_MAPPER.readValue(
+ BuildConfiguration.class.getResourceAsStream("/buildConfiguration.yml"),
+ BuildConfiguration.class);
+ } catch (IOException e) {
+ throw new RuntimeException(e);
+ }
+ }
+}
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
new file mode 100644
index 000000000..566224a27
--- /dev/null
+++ b/conjure-python/src/main/java/com/palantir/conjure/python/cli/CliConfiguration.java
@@ -0,0 +1,99 @@
+/*
+ * (c) Copyright 2018 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.cli;
+
+import com.google.common.base.Preconditions;
+import com.palantir.tokens.auth.ImmutablesStyle;
+import java.io.File;
+import java.util.Optional;
+import org.apache.commons.cli.Option;
+import org.immutables.value.Value;
+
+@Value.Immutable
+@ImmutablesStyle
+public abstract class CliConfiguration {
+ static final String PACKAGE_NAME = "packageName";
+ static final String PACKAGE_VERSION = "packageVersion";
+ static final String PACKAGE_DESCRIPTION = "packageDescription";
+ static final String PACKAGE_URL = "packageUrl";
+ static final String PACKAGE_AUTHOR = "packageAuthor";
+ static final String WRITE_CONDA_RECIPE = "writeCondaRecipe";
+
+ abstract File target();
+
+ abstract File outputDirectory();
+
+ abstract String packageName();
+
+ abstract String packageVersion();
+
+ abstract Optional packageDescription();
+
+ abstract Optional packageUrl();
+
+ abstract Optional packageAuthor();
+
+ @Value.Default
+ @SuppressWarnings("DesignForExtension")
+ boolean shouldWriteCondaRecipe() {
+ return false;
+ }
+
+ @Value.Check
+ final void check() {
+ Preconditions.checkArgument(target().isFile(), "Target must exist and be a file");
+ Preconditions.checkArgument(outputDirectory().isDirectory(), "Output must exist and be a directory");
+ }
+
+ static CliConfiguration of(String target, String outputDirectory, Option[] options) {
+ Builder builder = new Builder()
+ .target(new File(target))
+ .outputDirectory(new File(outputDirectory));
+ for (Option option : options) {
+ switch (option.getLongOpt()) {
+ case PACKAGE_NAME:
+ builder.packageName(option.getValue());
+ break;
+ case PACKAGE_VERSION:
+ String pythonicVersion = option.getValue().replace('-', '_');
+ builder.packageVersion(pythonicVersion);
+ break;
+ case PACKAGE_DESCRIPTION:
+ builder.packageDescription(option.getValue());
+ break;
+ case PACKAGE_URL:
+ builder.packageUrl(option.getValue());
+ break;
+ case PACKAGE_AUTHOR:
+ builder.packageAuthor(option.getValue());
+ break;
+ case WRITE_CONDA_RECIPE:
+ builder.shouldWriteCondaRecipe(true);
+ break;
+ default:
+ break;
+ }
+ }
+ return builder.build();
+ }
+
+ static Builder builder() {
+ return new Builder();
+ }
+
+ public static final class Builder extends ImmutableCliConfiguration.Builder {}
+}
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
new file mode 100644
index 000000000..e05e976ca
--- /dev/null
+++ b/conjure-python/src/main/java/com/palantir/conjure/python/cli/ConjurePythonCli.java
@@ -0,0 +1,132 @@
+/*
+ * (c) Copyright 2018 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.cli;
+
+import com.fasterxml.jackson.annotation.JsonInclude;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.datatype.jdk8.Jdk8Module;
+import com.google.common.base.Preconditions;
+import com.google.common.base.Throwables;
+import com.palantir.conjure.python.ConjurePythonGenerator;
+import com.palantir.conjure.python.DefaultPythonFileWriter;
+import com.palantir.conjure.python.GeneratorConfiguration;
+import com.palantir.conjure.python.service.ServiceGeneratorPython;
+import com.palantir.conjure.python.types.DefaultBeanGeneratorPython;
+import com.palantir.conjure.spec.ConjureDefinition;
+import java.io.File;
+import java.io.IOException;
+import org.apache.commons.cli.CommandLine;
+import org.apache.commons.cli.CommandLineParser;
+import org.apache.commons.cli.DefaultParser;
+import org.apache.commons.cli.HelpFormatter;
+import org.apache.commons.cli.Option;
+import org.apache.commons.cli.Options;
+import org.apache.commons.cli.ParseException;
+
+public final class ConjurePythonCli {
+ public static final String GENERATE_COMMAND = "generate";
+ private static final String CLI_NAME = "conjure-python";
+ private static final String USAGE = String.format("%s %s