diff --git a/.chronus/changes/update-root-namespace-2025-2-12-6-11-5.md b/.chronus/changes/update-root-namespace-2025-2-12-6-11-5.md new file mode 100644 index 00000000000..970830b700f --- /dev/null +++ b/.chronus/changes/update-root-namespace-2025-2-12-6-11-5.md @@ -0,0 +1,8 @@ +--- +# Change versionKind to one of: internal, fix, dependencies, feature, deprecation, breaking +changeKind: fix +packages: + - "@typespec/http-client-python" +--- + +Always respect namespace from TCGC diff --git a/packages/http-client-python/emitter/src/code-model.ts b/packages/http-client-python/emitter/src/code-model.ts index 1094f0ef2fb..52efc1e7398 100644 --- a/packages/http-client-python/emitter/src/code-model.ts +++ b/packages/http-client-python/emitter/src/code-model.ts @@ -1,5 +1,4 @@ import { - SdkApiVersionParameter, SdkBasicServiceMethod, SdkClientType, SdkCredentialParameter, @@ -33,12 +32,7 @@ import { simpleTypesMap, typesMap, } from "./types.js"; -import { - emitParamBase, - getClientNamespace, - getImplementation, - removeUnderscoresFromNamespace, -} from "./utils.js"; +import { emitParamBase, getClientNamespace, getImplementation, getRootNamespace } from "./utils.js"; function emitBasicMethod( context: PythonSdkContext, @@ -106,11 +100,7 @@ function emitLroPagingMethod( function emitMethodParameter( context: PythonSdkContext, - parameter: - | SdkEndpointParameter - | SdkCredentialParameter - | SdkMethodParameter - | SdkApiVersionParameter, + parameter: SdkEndpointParameter | SdkCredentialParameter | SdkMethodParameter, ): Record[] { if (parameter.kind === "endpoint") { if (parameter.type.kind === "union") { @@ -286,7 +276,7 @@ export function emitCodeModel( // Get types const sdkPackage = sdkContext.sdkPackage; const codeModel: Record = { - namespace: removeUnderscoresFromNamespace(sdkPackage.rootNamespace).toLowerCase(), + namespace: getRootNamespace(sdkContext), clients: [], }; for (const client of sdkPackage.clients) { diff --git a/packages/http-client-python/emitter/src/emitter.ts b/packages/http-client-python/emitter/src/emitter.ts index dfcb3658f08..ad010ae4d51 100644 --- a/packages/http-client-python/emitter/src/emitter.ts +++ b/packages/http-client-python/emitter/src/emitter.ts @@ -16,7 +16,7 @@ import { saveCodeModelAsYaml } from "./external-process.js"; import { PythonEmitterOptions, PythonSdkContext, reportDiagnostic } from "./lib.js"; import { runPython3 } from "./run-python3.js"; import { disableGenerationMap, simpleTypesMap, typesMap } from "./types.js"; -import { md2Rst, removeUnderscoresFromNamespace } from "./utils.js"; +import { getRootNamespace, md2Rst } from "./utils.js"; export function getModelsMode(context: SdkContext): "dpg" | "none" { const specifiedModelsMode = context.emitContext.options["models-mode"]; @@ -50,9 +50,14 @@ function addDefaultOptions(sdkContext: SdkContext) { options["package-mode"] = sdkContext.arm ? "azure-mgmt" : "azure-dataplane"; } if (!options["package-name"]) { - options["package-name"] = removeUnderscoresFromNamespace( - (sdkContext.sdkPackage.rootNamespace ?? "").toLowerCase(), - ).replace(/\./g, "-"); + const namespace = getRootNamespace(sdkContext as PythonSdkContext); + const packageName = namespace.replace(/\./g, "-"); + reportDiagnostic(sdkContext.program, { + code: "no-package-name", + target: NoTarget, + format: { namespace, packageName }, + }); + options["package-name"] = packageName; } if (options.flavor !== "azure") { // if they pass in a flavor other than azure, we want to ignore the value @@ -61,9 +66,6 @@ function addDefaultOptions(sdkContext: SdkContext) { if (!options.flavor && sdkContext.emitContext.emitterOutputDir.includes("azure")) { options.flavor = "azure"; } - if (options["enable-typespec-namespace"] === undefined) { - options["enable-typespec-namespace"] = options.flavor !== "azure"; - } } async function createPythonSdkContext( diff --git a/packages/http-client-python/emitter/src/lib.ts b/packages/http-client-python/emitter/src/lib.ts index d0f3bdfb762..63372c4d17b 100644 --- a/packages/http-client-python/emitter/src/lib.ts +++ b/packages/http-client-python/emitter/src/lib.ts @@ -17,9 +17,7 @@ export interface PythonEmitterOptions { debug?: boolean; flavor?: "azure"; "examples-dir"?: string; - // If true, package namespace will respect the typespec namespace. Otherwise, - // package namespace is always aligned with package name. - "enable-typespec-namespace"?: boolean; + namespace?: string; "use-pyodide"?: boolean; } @@ -47,7 +45,7 @@ const EmitterOptionsSchema: JSONSchemaType = { debug: { type: "boolean", nullable: true }, flavor: { type: "string", nullable: true }, "examples-dir": { type: "string", nullable: true, format: "absolute-path" }, - "enable-typespec-namespace": { type: "boolean", nullable: true }, + namespace: { type: "string", nullable: true }, "use-pyodide": { type: "boolean", nullable: true }, }, required: [], @@ -77,6 +75,12 @@ const libDef = { }, }, // warning + "no-package-name": { + severity: "warning", + messages: { + default: paramMessage`No package-name configured in tspconfig.yaml and will infer package-name '${"packageName"}' from namespace '${"namespace"}'.`, + }, + }, "no-valid-client": { severity: "warning", messages: { diff --git a/packages/http-client-python/emitter/src/utils.ts b/packages/http-client-python/emitter/src/utils.ts index b583f8e4246..deb6233238a 100644 --- a/packages/http-client-python/emitter/src/utils.ts +++ b/packages/http-client-python/emitter/src/utils.ts @@ -242,31 +242,45 @@ export function capitalize(name: string): string { return name[0].toUpperCase() + name.slice(1); } +const LIB_NAMESPACE = [ + "azure.core", + "azure.resourcemanager", + "azure.clientgenerator.core", + "typespec.rest", + "typespec.http", + "typespec.versioning", +]; + +export function getRootNamespace(context: PythonSdkContext): string { + let rootNamespace = ""; + if (context.sdkPackage.clients.length > 0) { + rootNamespace = context.sdkPackage.clients[0].namespace; + } else if (context.sdkPackage.models.length > 0) { + const result = context.sdkPackage.models + .map((model) => model.namespace) + .filter((namespace) => !LIB_NAMESPACE.includes(namespace)); + if (result.length > 0) { + result.sort(); + rootNamespace = result[0]; + } + } else if (context.sdkPackage.namespaces.length > 0) { + rootNamespace = context.sdkPackage.namespaces[0].fullName; + } + + return removeUnderscoresFromNamespace(rootNamespace).toLowerCase(); +} + export function getClientNamespace( context: PythonSdkContext, clientNamespace: string, ) { - const rootNamespace = removeUnderscoresFromNamespace( - context.sdkPackage.rootNamespace, - ).toLowerCase(); - if (!context.emitContext.options["enable-typespec-namespace"]) { - return rootNamespace; - } if ( - [ - "azure.core", - "azure.resourcemanager", - "azure.clientgenerator.core", - "typespec.rest", - "typespec.http", - "typespec.versioning", - ].some((item) => clientNamespace.toLowerCase().startsWith(item)) + clientNamespace === "" || + LIB_NAMESPACE.some((item) => clientNamespace.toLowerCase().startsWith(item)) ) { - return rootNamespace; + return getRootNamespace(context); } - return clientNamespace === "" - ? rootNamespace - : removeUnderscoresFromNamespace(clientNamespace).toLowerCase(); + return removeUnderscoresFromNamespace(clientNamespace).toLowerCase(); } function parseToken(token: Token): string { diff --git a/packages/http-client-python/eng/scripts/ci/regenerate.ts b/packages/http-client-python/eng/scripts/ci/regenerate.ts index c20d0392c60..4995f6a8d9d 100644 --- a/packages/http-client-python/eng/scripts/ci/regenerate.ts +++ b/packages/http-client-python/eng/scripts/ci/regenerate.ts @@ -38,96 +38,171 @@ interface TspCommand { command: string; } +const AZURE_EMITTER_OPTIONS: Record | Record[]> = { + "azure/example/basic": { + namespace: "specs.azure.example.basic", + }, + "azure/client-generator-core/access": { + namespace: "specs.azure.clientgenerator.core.access", + }, + "azure/client-generator-core/usage": { + namespace: "specs.azure.clientgenerator.core.usage", + }, + "azure/core/lro/rpc": { + "package-name": "azurecore-lro-rpc", + namespace: "azurecore.lro.rpc", + }, + "client/structure/default": { + namespace: "client.structure.service", + }, + "client/structure/multi-client": { + "package-name": "client-structure-multiclient", + namespace: "client.structure.multiclient", + }, + "client/structure/renamed-operation": { + "package-name": "client-structure-renamedoperation", + namespace: "client.structure.renamedoperation", + }, + "client/structure/two-operation-group": { + "package-name": "client-structure-twooperationgroup", + namespace: "client.structure.twooperationgroup", + }, + "client/naming": { + namespace: "client.naming", + }, + "encode/duration": { + namespace: "encode.duration", + }, + "encode/numeric": { + namespace: "encode.numeric", + }, + "parameters/basic": { + namespace: "parameters.basic", + }, + "parameters/spread": { + namespace: "parameters.spread", + }, + "payload/content-negotiation": { + namespace: "payload.contentnegotiation", + }, + "payload/multipart": { + namespace: "payload.multipart", + }, + "serialization/encoded-name/json": { + namespace: "serialization.encodedname.json", + }, + "special-words": { + namespace: "specialwords", + }, +}; + const EMITTER_OPTIONS: Record | Record[]> = { "resiliency/srv-driven/old.tsp": { "package-name": "resiliency-srv-driven1", + namespace: "resiliency.srv.driven1", "package-mode": "azure-dataplane", "package-pprint-name": "ResiliencySrvDriven1", }, "resiliency/srv-driven": { "package-name": "resiliency-srv-driven2", + namespace: "resiliency.srv.driven2", "package-mode": "azure-dataplane", "package-pprint-name": "ResiliencySrvDriven2", }, "authentication/http/custom": { "package-name": "authentication-http-custom", + namespace: "authentication.http.custom", "package-pprint-name": "Authentication Http Custom", }, "authentication/union": { "package-name": "authentication-union", + namespace: "authentication.union", }, "type/array": { "package-name": "typetest-array", + namespace: "typetest.array", "use-pyodide": "true", }, "type/dictionary": { "package-name": "typetest-dictionary", + namespace: "typetest.dictionary", }, "type/enum/extensible": { "package-name": "typetest-enum-extensible", + namespace: "typetest.enum.extensible", }, "type/enum/fixed": { "package-name": "typetest-enum-fixed", + namespace: "typetest.enum.fixed", }, "type/model/empty": { "package-name": "typetest-model-empty", + namespace: "typetest.model.empty", }, "type/model/inheritance/enum-discriminator": { "package-name": "typetest-model-enumdiscriminator", + namespace: "typetest.model.enumdiscriminator", }, "type/model/inheritance/nested-discriminator": { "package-name": "typetest-model-nesteddiscriminator", + namespace: "typetest.model.nesteddiscriminator", }, "type/model/inheritance/not-discriminated": { "package-name": "typetest-model-notdiscriminated", + namespace: "typetest.model.notdiscriminated", }, "type/model/inheritance/single-discriminator": { "package-name": "typetest-model-singlediscriminator", + namespace: "typetest.model.singlediscriminator", }, "type/model/inheritance/recursive": { "package-name": "typetest-model-recursive", + namespace: "typetest.model.recursive", "use-pyodide": "true", }, "type/model/usage": { "package-name": "typetest-model-usage", + namespace: "typetest.model.usage", }, "type/model/visibility": [ - { "package-name": "typetest-model-visibility" }, - { "package-name": "headasbooleantrue", "head-as-boolean": "true" }, - { "package-name": "headasbooleanfalse", "head-as-boolean": "false" }, + { + "package-name": "typetest-model-visibility", + namespace: "typetest.model.visibility", + }, + { + "package-name": "headasbooleantrue", + namespace: "headasbooleantrue", + "head-as-boolean": "true", + }, + { + "package-name": "headasbooleanfalse", + namespace: "headasbooleanfalse", + "head-as-boolean": "false", + }, ], "type/property/nullable": { "package-name": "typetest-property-nullable", + namespace: "typetest.property.nullable", }, "type/property/optionality": { "package-name": "typetest-property-optional", + namespace: "typetest.property.optional", }, "type/property/additional-properties": { "package-name": "typetest-property-additionalproperties", + namespace: "typetest.property.additionalproperties", }, "type/scalar": { "package-name": "typetest-scalar", + namespace: "typetest.scalar", }, "type/property/value-types": { "package-name": "typetest-property-valuetypes", + namespace: "typetest.property.valuetypes", }, "type/union": { "package-name": "typetest-union", - }, - "azure/core/lro/rpc": { - "package-name": "azurecore-lro-rpc", - }, - "client/structure/multi-client": { - "package-name": "client-structure-multiclient", - }, - "client/structure/renamed-operation": { - "package-name": "client-structure-renamedoperation", - }, - "client/structure/two-operation-group": { - "package-name": "client-structure-twooperationgroup", - }, - "client/namespace": { - "enable-typespec-namespace": "true", + namespace: "typetest.union", }, }; @@ -141,29 +216,9 @@ function getEmitterOption(spec: string, flavor: string): Record[ const key = relativeSpec.includes("resiliency/srv-driven/old.tsp") ? relativeSpec : dirname(relativeSpec); - const emitter_options = EMITTER_OPTIONS[key] || [{}]; - const result = Array.isArray(emitter_options) ? emitter_options : [emitter_options]; - - function updateOptions(options: Record): void { - if (options["package-name"] && options["enable-typespec-namespace"] === undefined) { - options["enable-typespec-namespace"] = "false"; - } - } - - // when package name is different with typespec namespace, disable typespec namespace - if (flavor !== "azure") { - for (const options of result) { - if (Array.isArray(options)) { - for (const option of options) { - updateOptions(option); - } - } else { - updateOptions(options); - } - } - } - - return result; + const emitter_options = EMITTER_OPTIONS[key] || + (flavor === "azure" ? AZURE_EMITTER_OPTIONS[key] : [{}]) || [{}]; + return Array.isArray(emitter_options) ? emitter_options : [emitter_options]; } // Function to execute CLI commands asynchronously diff --git a/packages/http-client-python/generator/pygen/codegen/__init__.py b/packages/http-client-python/generator/pygen/codegen/__init__.py index c48ba50ee21..1a2882fba9c 100644 --- a/packages/http-client-python/generator/pygen/codegen/__init__.py +++ b/packages/http-client-python/generator/pygen/codegen/__init__.py @@ -40,7 +40,6 @@ class OptionsRetriever: "generate-test": False, "from-typespec": False, "emit-cross-language-definition-file": False, - "enable-typespec-namespace": False, } @property @@ -317,7 +316,6 @@ def _build_code_model_options(self) -> Dict[str, Any]: "flavor", "company_name", "emit_cross_language_definition_file", - "enable_typespec_namespace", ] return {f: getattr(self.options_retriever, f) for f in flags} diff --git a/packages/http-client-python/generator/pygen/codegen/models/code_model.py b/packages/http-client-python/generator/pygen/codegen/models/code_model.py index 4d0f1da89b1..2153cbcbf55 100644 --- a/packages/http-client-python/generator/pygen/codegen/models/code_model.py +++ b/packages/http-client-python/generator/pygen/codegen/models/code_model.py @@ -145,12 +145,8 @@ def get_relative_import_path( return result return f"{result}{module_name}" if result.endswith(".") else f"{result}.{module_name}" - @property - def need_unique_model_alias(self) -> bool: - return self.has_subnamespace and self.options["enable_typespec_namespace"] - def get_unique_models_alias(self, serialize_namespace: str, imported_namespace: str) -> str: - if not self.need_unique_model_alias: + if not self.has_subnamespace: return "_models" relative_path = self.get_relative_import_path( serialize_namespace, self.get_imported_namespace_for_model(imported_namespace) @@ -406,3 +402,7 @@ def is_legacy(self) -> bool: @staticmethod def has_non_json_models(models: List[ModelType]) -> bool: return any(m for m in models if m.base != "json") + + @property + def is_tsp(self) -> bool: + return self.options.get("tsp_file") is not None diff --git a/packages/http-client-python/generator/pygen/codegen/serializers/__init__.py b/packages/http-client-python/generator/pygen/codegen/serializers/__init__.py index 25531abe3cf..305655d8680 100644 --- a/packages/http-client-python/generator/pygen/codegen/serializers/__init__.py +++ b/packages/http-client-python/generator/pygen/codegen/serializers/__init__.py @@ -197,6 +197,8 @@ def _serialize_and_write_package_files(self, client_namespace: str) -> None: env = Environment( loader=PackageLoader("pygen.codegen", "templates/packaging_templates"), undefined=StrictUndefined, + trim_blocks=True, + lstrip_blocks=True, ) package_files = _PACKAGE_FILES @@ -460,21 +462,11 @@ def _serialize_and_write_metadata(self, env: Environment, namespace: str) -> Non metadata_serializer = MetadataSerializer(self.code_model, env, client_namespace=namespace) self.write_file(self.exec_path(namespace) / Path("_metadata.json"), metadata_serializer.serialize()) - @property - def _namespace_from_package_name(self) -> str: - return get_namespace_from_package_name(self.code_model.options["package_name"]) - - def _name_space(self) -> str: - if self.code_model.namespace.count(".") >= self._namespace_from_package_name.count("."): - return self.code_model.namespace - - return self._namespace_from_package_name - @property def exec_path_compensation(self) -> Path: """Assume the process is running in the root folder of the package. If not, we need the path compensation.""" return ( - Path("../" * (self._name_space().count(".") + 1)) + Path("../" * (self.code_model.namespace.count(".") + 1)) if self.code_model.options["no_namespace_folders"] else Path(".") ) @@ -482,16 +474,28 @@ def exec_path_compensation(self) -> Path: def exec_path_for_test_sample(self, namespace: str) -> Path: return self.exec_path_compensation / Path(*namespace.split(".")) + # pylint: disable=line-too-long def exec_path(self, namespace: str) -> Path: if self.code_model.options["no_namespace_folders"] and not self.code_model.options["multiapi"]: + # when output folder contains parts different from the namespace, we fall back to current folder directly. + # (e.g. https://github.com/Azure/azure-sdk-for-python/blob/main/sdk/communication/azure-communication-callautomation/swagger/SWAGGER.md) return Path(".") return self.exec_path_compensation / Path(*namespace.split(".")) + # pylint: disable=line-too-long @property - def _additional_folder(self) -> Path: + def sample_additional_folder(self) -> Path: + # For special package, we need to additional folder when generate samples. + # For example, azure-mgmt-resource is combined by multiple modules, and each module is multiapi package. + # one of namespace is "azure.mgmt.resource.resources.v2020_01_01", then additional folder is "resources" + # so that we could avoid conflict when generate samples. + # python config: https://github.com/Azure/azure-rest-api-specs/blob/main/specification/resources/resource-manager/readme.python.md + # generated SDK: https://github.com/Azure/azure-sdk-for-python/tree/main/sdk/resources/azure-mgmt-resource/generated_samples namespace_config = get_namespace_config(self.code_model.namespace, self.code_model.options["multiapi"]) num_of_namespace = namespace_config.count(".") + 1 - num_of_package_namespace = self._namespace_from_package_name.count(".") + 1 + num_of_package_namespace = ( + get_namespace_from_package_name(self.code_model.options["package_name"]).count(".") + 1 + ) if num_of_namespace > num_of_package_namespace: return Path("/".join(namespace_config.split(".")[num_of_package_namespace:])) return Path("") @@ -514,7 +518,7 @@ def _serialize_and_write_sample(self, env: Environment, namespace: str): file_name = to_snake_case(extract_sample_name(file)) + ".py" try: self.write_file( - out_path / self._additional_folder / _sample_output_path(file) / file_name, + out_path / self.sample_additional_folder / _sample_output_path(file) / file_name, SampleSerializer( code_model=self.code_model, env=env, diff --git a/packages/http-client-python/generator/pygen/codegen/serializers/general_serializer.py b/packages/http-client-python/generator/pygen/codegen/serializers/general_serializer.py index 57e774d99b0..ef6b029af8e 100644 --- a/packages/http-client-python/generator/pygen/codegen/serializers/general_serializer.py +++ b/packages/http-client-python/generator/pygen/codegen/serializers/general_serializer.py @@ -28,7 +28,11 @@ def serialize_setup_file(self) -> str: def serialize_package_file(self, template_name: str, **kwargs: Any) -> str: template = self.env.get_template(template_name) - package_parts = (self.code_model.options["package_name"] or "").split("-")[:-1] + package_parts = ( + self.code_model.namespace.split(".")[:-1] + if self.code_model.is_tsp + else (self.code_model.options["package_name"] or "").split("-")[:-1] + ) token_credential = any( c for c in self.code_model.clients if isinstance(getattr(c.credential, "type", None), TokenCredentialType) ) @@ -44,7 +48,6 @@ def serialize_package_file(self, template_name: str, **kwargs: Any) -> str: "pkgutil_names": [".".join(package_parts[: i + 1]) for i in range(len(package_parts))], "init_names": ["/".join(package_parts[: i + 1]) + "/__init__.py" for i in range(len(package_parts))], "client_name": self.code_model.clients[0].name, - "namespace": self.code_model.namespace, } params.update(self.code_model.options) params.update(kwargs) diff --git a/packages/http-client-python/generator/pygen/codegen/serializers/test_serializer.py b/packages/http-client-python/generator/pygen/codegen/serializers/test_serializer.py index 3925f341178..444b67455bc 100644 --- a/packages/http-client-python/generator/pygen/codegen/serializers/test_serializer.py +++ b/packages/http-client-python/generator/pygen/codegen/serializers/test_serializer.py @@ -19,7 +19,7 @@ CombinedType, FileImport, ) -from .utils import get_namespace_from_package_name, json_dumps_template +from .utils import json_dumps_template def is_lro(operation_type: str) -> bool: diff --git a/packages/http-client-python/generator/pygen/codegen/templates/packaging_templates/MANIFEST.in.jinja2 b/packages/http-client-python/generator/pygen/codegen/templates/packaging_templates/MANIFEST.in.jinja2 index 454a9ad2717..3cb8e23e6a4 100644 --- a/packages/http-client-python/generator/pygen/codegen/templates/packaging_templates/MANIFEST.in.jinja2 +++ b/packages/http-client-python/generator/pygen/codegen/templates/packaging_templates/MANIFEST.in.jinja2 @@ -1,8 +1,12 @@ include *.md include LICENSE +{% if code_model.is_tsp %} +include {{ code_model.namespace.replace('.', '/') }}/py.typed +{% else %} include {{ package_name.replace('-', '/') }}/py.typed +{% endif %} recursive-include tests *.py recursive-include samples *.py *.md -{%- for init_name in init_names %} +{% for init_name in init_names %} include {{ init_name }} -{%- endfor %} +{% endfor %} diff --git a/packages/http-client-python/generator/pygen/codegen/templates/packaging_templates/README.md.jinja2 b/packages/http-client-python/generator/pygen/codegen/templates/packaging_templates/README.md.jinja2 index fbea5913604..f7dc4f8de52 100644 --- a/packages/http-client-python/generator/pygen/codegen/templates/packaging_templates/README.md.jinja2 +++ b/packages/http-client-python/generator/pygen/codegen/templates/packaging_templates/README.md.jinja2 @@ -1,5 +1,5 @@ {% if code_model.is_azure_flavor %} -{% if package_mode == "mgmtplane" -%} +{% if package_mode == "mgmtplane" %} # Microsoft Azure SDK for Python This is the Microsoft {{package_pprint_name}} Client Library. @@ -40,7 +40,7 @@ python -m pip install {{ package_name }} - You need an [Azure subscription][azure_sub] to use this package. - An existing {{ package_pprint_name }} instance. -{%- if token_credential %} +{% if token_credential %} #### Create with an Azure Active Directory Credential To use an [Azure Active Directory (AAD) token credential][authenticate_with_token], provide an instance of the desired credential type obtained from the @@ -57,7 +57,7 @@ Set the values of the client ID, tenant ID, and client secret of the AAD applica Use the returned token credential to authenticate the client: ```python ->>> from {{ namespace }} import {{ client_name }} +>>> from {{ code_model.namespace }} import {{ client_name }} >>> from azure.identity import DefaultAzureCredential >>> client = {{ client_name }}(endpoint='', credential=DefaultAzureCredential()) ``` @@ -65,7 +65,7 @@ Use the returned token credential to authenticate the client: ## Examples ```python ->>> from {{ namespace }} import {{ client_name }} +>>> from {{ code_model.namespace }} import {{ client_name }} >>> from azure.identity import DefaultAzureCredential >>> from {{ code_model.core_library }}.exceptions import HttpResponseError @@ -76,7 +76,7 @@ Use the returned token credential to authenticate the client: print('service responds error: {}'.format(e.response.json())) ``` -{%- endif %} +{% endif %} ## Contributing diff --git a/packages/http-client-python/generator/pygen/codegen/templates/packaging_templates/dev_requirements.txt.jinja2 b/packages/http-client-python/generator/pygen/codegen/templates/packaging_templates/dev_requirements.txt.jinja2 index a9782cabd58..2e01201bf96 100644 --- a/packages/http-client-python/generator/pygen/codegen/templates/packaging_templates/dev_requirements.txt.jinja2 +++ b/packages/http-client-python/generator/pygen/codegen/templates/packaging_templates/dev_requirements.txt.jinja2 @@ -1,9 +1,9 @@ -e ../../../tools/azure-sdk-tools ../../core/azure-core -{% if token_credential -%} +{% if token_credential %} ../../identity/azure-identity {% endif -%} -{% if azure_arm -%} +{% if azure_arm %} ../../core/azure-mgmt-core -{% endif -%} +{% endif %} aiohttp diff --git a/packages/http-client-python/generator/pygen/codegen/templates/packaging_templates/setup.py.jinja2 b/packages/http-client-python/generator/pygen/codegen/templates/packaging_templates/setup.py.jinja2 index 45239f0dac5..6b3af5b10ef 100644 --- a/packages/http-client-python/generator/pygen/codegen/templates/packaging_templates/setup.py.jinja2 +++ b/packages/http-client-python/generator/pygen/codegen/templates/packaging_templates/setup.py.jinja2 @@ -2,19 +2,27 @@ {{ license_header }} # coding: utf-8 {% if package_mode %} + import os import re -{% endif -%} +{% endif %} from setuptools import setup, find_packages {% set package_name = package_name or code_model.clients[0].name %} PACKAGE_NAME = "{{ package_name|lower }}" -{% if package_mode -%} +{% if package_mode %} PACKAGE_PPRINT_NAME = "{{ package_pprint_name }}" +{% if code_model.is_tsp %} +PACKAGE_NAMESPACE = "{{ code_model.namespace|lower }}" + +# a.b.c => a/b/c +package_folder_path = PACKAGE_NAMESPACE.replace(".", "/") +{% else %} # a-b-c => a/b/c package_folder_path = PACKAGE_NAME.replace("-", "/") +{% endif %} # Version extraction inspired from 'requests' with open(os.path.join(package_folder_path, "_version.py"), "r") as fd: @@ -33,7 +41,8 @@ version = "{{ package_version }}" {% set long_description = code_model.description %} {% set author_email = "" %} {% set url = "" %} -{% endif -%} +{% endif %} + setup( name=PACKAGE_NAME, @@ -70,9 +79,9 @@ setup( {% if pkgutil_names %} # Exclude packages that will be covered by PEP420 or nspkg {% endif %} - {%- for pkgutil_name in pkgutil_names %} + {% for pkgutil_name in pkgutil_names %} "{{ pkgutil_name }}", - {%- endfor %} + {% endfor %} ] ), include_package_data=True, diff --git a/packages/http-client-python/package-lock.json b/packages/http-client-python/package-lock.json index 8f30393e372..b435cbafc5d 100644 --- a/packages/http-client-python/package-lock.json +++ b/packages/http-client-python/package-lock.json @@ -22,7 +22,7 @@ "@azure-tools/typespec-azure-core": "~0.53.0", "@azure-tools/typespec-azure-resource-manager": "~0.53.0", "@azure-tools/typespec-azure-rulesets": "~0.53.0", - "@azure-tools/typespec-client-generator-core": "~0.53.0", + "@azure-tools/typespec-client-generator-core": "~0.53.1", "@types/js-yaml": "~4.0.5", "@types/node": "~22.5.4", "@types/semver": "7.5.8", @@ -47,7 +47,7 @@ "@azure-tools/typespec-azure-core": ">=0.53.0 <1.0.0", "@azure-tools/typespec-azure-resource-manager": ">=0.53.0 <1.0.0", "@azure-tools/typespec-azure-rulesets": ">=0.53.0 <3.0.0", - "@azure-tools/typespec-client-generator-core": ">=0.53.0 <1.0.0", + "@azure-tools/typespec-client-generator-core": ">=0.53.1 <1.0.0", "@typespec/compiler": ">=0.67.0 <1.0.0", "@typespec/http": ">=0.67.0 <1.0.0", "@typespec/openapi": ">=0.67.0 <1.0.0", @@ -151,9 +151,9 @@ } }, "node_modules/@azure-tools/typespec-client-generator-core": { - "version": "0.53.0", - "resolved": "https://registry.npmjs.org/@azure-tools/typespec-client-generator-core/-/typespec-client-generator-core-0.53.0.tgz", - "integrity": "sha512-EXdkC76HmAdaH0kzET38pPpG7Phoyvp9AFYsAXnQdhRQssfI+x9nxEADix8GZ2n2P1P50ZysvzWeciqgqUbRVw==", + "version": "0.53.1", + "resolved": "https://registry.npmjs.org/@azure-tools/typespec-client-generator-core/-/typespec-client-generator-core-0.53.1.tgz", + "integrity": "sha512-BWHQQ9Kjsk23Rb0eZ6V6HI2Gr20n/LhxAKEuBChCFWLjrFMYyXrHtlUBK6j/9D2VqwjaurRQA2SVXx/wzGyvAg==", "dev": true, "license": "MIT", "dependencies": { diff --git a/packages/http-client-python/package.json b/packages/http-client-python/package.json index 8ec9f492442..7fda2e5c9f1 100644 --- a/packages/http-client-python/package.json +++ b/packages/http-client-python/package.json @@ -57,7 +57,7 @@ "@azure-tools/typespec-azure-core": ">=0.53.0 <1.0.0", "@azure-tools/typespec-azure-resource-manager": ">=0.53.0 <1.0.0", "@azure-tools/typespec-azure-rulesets": ">=0.53.0 <3.0.0", - "@azure-tools/typespec-client-generator-core": ">=0.53.0 <1.0.0", + "@azure-tools/typespec-client-generator-core": ">=0.53.1 <1.0.0", "@typespec/compiler": ">=0.67.0 <1.0.0", "@typespec/http": ">=0.67.0 <1.0.0", "@typespec/openapi": ">=0.67.0 <1.0.0", @@ -77,7 +77,7 @@ "@azure-tools/typespec-azure-core": "~0.53.0", "@azure-tools/typespec-azure-resource-manager": "~0.53.0", "@azure-tools/typespec-azure-rulesets": "~0.53.0", - "@azure-tools/typespec-client-generator-core": "~0.53.0", + "@azure-tools/typespec-client-generator-core": "~0.53.1", "@types/js-yaml": "~4.0.5", "@types/node": "~22.5.4", "@types/semver": "7.5.8",