Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Python] support multi namespace #5271

Closed
wants to merge 56 commits into from
Closed
Show file tree
Hide file tree
Changes from 48 commits
Commits
Show all changes
56 commits
Select commit Hold shift + click to select a range
feb80b5
update dependency tcgc 0.48.1
msyyc Nov 11, 2024
60e4c15
add clientNamespace property
msyyc Nov 11, 2024
ac2b225
init
msyyc Nov 25, 2024
0a391bd
init2
msyyc Nov 26, 2024
80ea470
init3
msyyc Nov 27, 2024
d7f6e87
init4
msyyc Dec 2, 2024
4146dc4
init5
msyyc Dec 2, 2024
f0d01be
format
msyyc Dec 3, 2024
3613073
format
msyyc Dec 3, 2024
db6402e
merge
msyyc Dec 3, 2024
5ea817c
review
msyyc Dec 3, 2024
ed61e31
review
msyyc Dec 3, 2024
682b381
review
msyyc Dec 4, 2024
32399e8
review
msyyc Dec 4, 2024
ab818e2
review
msyyc Dec 4, 2024
f3f0140
review
msyyc Dec 4, 2024
ea246a9
review
msyyc Dec 4, 2024
08a68ff
review
msyyc Dec 5, 2024
a8dc1d4
review
msyyc Dec 5, 2024
089ebcc
review
msyyc Dec 5, 2024
59d97c2
review
msyyc Dec 6, 2024
bbb5884
Merge branch 'main' of https://github.com/microsoft/typespec into pyt…
msyyc Dec 6, 2024
9c5f209
review
msyyc Dec 6, 2024
d8881d1
review
msyyc Dec 6, 2024
5aeb9cf
review
msyyc Dec 6, 2024
c8cac2c
review
msyyc Dec 6, 2024
b0b1b43
review
msyyc Dec 6, 2024
a21d7ad
review
msyyc Dec 6, 2024
6c37c0c
review
msyyc Dec 6, 2024
d0dcc68
review
msyyc Dec 6, 2024
6121243
merge
msyyc Dec 6, 2024
05309a8
for sample and test
msyyc Dec 6, 2024
f3d25ff
for sample and test
msyyc Dec 6, 2024
42f29c9
review
msyyc Dec 6, 2024
bf9806d
merge
msyyc Dec 9, 2024
ab88321
debug
msyyc Dec 9, 2024
7f88e3e
review
msyyc Dec 9, 2024
adf95fe
fix
msyyc Dec 10, 2024
7c5532c
fix
msyyc Dec 10, 2024
2781b09
new test
msyyc Dec 10, 2024
657176d
review
msyyc Dec 10, 2024
29c30fd
review
msyyc Dec 10, 2024
af457ca
review
msyyc Dec 10, 2024
b58ef98
review
msyyc Dec 10, 2024
5458bb5
merge
msyyc Dec 11, 2024
d5b473a
fix
msyyc Dec 11, 2024
689a06b
fix
msyyc Dec 11, 2024
4128461
fix
msyyc Dec 11, 2024
08ca7c7
Merge branch 'main' of https://github.com/microsoft/typespec into pyt…
msyyc Dec 13, 2024
6aa4203
fix typing
msyyc Dec 13, 2024
5700597
fix typing
msyyc Dec 13, 2024
49cc526
review
msyyc Dec 13, 2024
5b7fe1a
review
msyyc Dec 13, 2024
7290a71
Merge branch 'main' of https://github.com/microsoft/typespec into pyt…
msyyc Dec 16, 2024
c592198
review
msyyc Dec 16, 2024
21d8448
review
msyyc Dec 16, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 16 additions & 6 deletions packages/http-client-python/emitter/src/code-model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,12 @@ import {
simpleTypesMap,
typesMap,
} from "./types.js";
import { emitParamBase, getImplementation, removeUnderscoresFromNamespace } from "./utils.js";
import {
emitParamBase,
getClientNamespace,
getImplementation,
removeUnderscoresFromNamespace,
} from "./utils.js";

function emitBasicMethod<TServiceOperation extends SdkServiceOperation>(
context: PythonSdkContext<TServiceOperation>,
Expand Down Expand Up @@ -193,6 +198,7 @@ function emitOperationGroups<TServiceOperation extends SdkServiceOperation>(
propertyName: operationGroup.name,
operations: operations,
operationGroups: emitOperationGroups(context, operationGroup, rootClient, name),
clientNamespace: getClientNamespace(context, client.clientNamespace),
});
}
}
Expand All @@ -210,10 +216,18 @@ function emitOperationGroups<TServiceOperation extends SdkServiceOperation>(
className: "",
propertyName: "",
operations: operations,
clientNamespace: getClientNamespace(context, client.clientNamespace),
});
}
}

// operation has same clientNamespace as the operation group
for (const og of operationGroups) {
for (const op of og.operations) {
op.clientNamespace = getClientNamespace(context, og.clientNamespace);
}
}

return operationGroups.length > 0 ? operationGroups : undefined;
}

Expand Down Expand Up @@ -247,6 +261,7 @@ function emitClient<TServiceOperation extends SdkServiceOperation>(
url,
apiVersions: client.apiVersions,
arm: context.arm,
clientNamespace: getClientNamespace(context, client.clientNamespace),
};
}

Expand All @@ -268,14 +283,9 @@ export function emitCodeModel<TServiceOperation extends SdkServiceOperation>(
const codeModel: Record<string, any> = {
namespace: removeUnderscoresFromNamespace(sdkPackage.rootNamespace).toLowerCase(),
clients: [],
subnamespaceToClients: {},
};
for (const client of sdkPackage.clients) {
codeModel["clients"].push(emitClient(sdkContext, client));
if (client.nameSpace === sdkPackage.rootNamespace) {
} else {
codeModel["subnamespaceToClients"][client.nameSpace] = emitClient(sdkContext, client);
}
}
// loop through models and enums since there may be some orphaned models needs to be generated
for (const model of sdkPackage.models) {
Expand Down
2 changes: 2 additions & 0 deletions packages/http-client-python/emitter/src/lib.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ export interface PythonEmitterOptions {
debug?: boolean;
flavor?: "azure";
"examples-dir"?: string;
"enable-typespec-namespace"?: boolean;
}

export interface PythonSdkContext<TServiceOperation extends SdkServiceOperation>
Expand All @@ -43,6 +44,7 @@ const EmitterOptionsSchema: JSONSchemaType<PythonEmitterOptions> = {
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 },
},
required: [],
};
Expand Down
20 changes: 16 additions & 4 deletions packages/http-client-python/emitter/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,13 @@ import { Type } from "@typespec/compiler";
import { HttpAuth, Visibility } from "@typespec/http";
import { dump } from "js-yaml";
import { PythonSdkContext } from "./lib.js";
import { camelToSnakeCase, emitParamBase, getAddedOn, getImplementation } from "./utils.js";
import {
camelToSnakeCase,
emitParamBase,
getAddedOn,
getClientNamespace,
getImplementation,
} from "./utils.js";

export const typesMap = new Map<SdkType, Record<string, any>>();
export const simpleTypesMap = new Map<string | null, Record<string, any>>();
Expand Down Expand Up @@ -75,7 +81,7 @@ export function getType<TServiceOperation extends SdkServiceOperation>(
case "union":
return emitUnion(context, type);
case "enum":
return emitEnum(type);
return emitEnum(context, type);
case "constant":
return emitConstant(type)!;
case "array":
Expand All @@ -86,7 +92,7 @@ export function getType<TServiceOperation extends SdkServiceOperation>(
case "duration":
return emitDurationOrDateType(type);
case "enumvalue":
return emitEnumMember(type, emitEnum(type.enumType));
return emitEnumMember(type, emitEnum(context, type.enumType));
case "credential":
return emitCredential(type);
case "bytes":
Expand Down Expand Up @@ -289,6 +295,7 @@ function emitModel<TServiceOperation extends SdkServiceOperation>(
usage: type.usage,
isXml: type.usage & UsageFlags.Xml ? true : false,
xmlMetadata: type.usage & UsageFlags.Xml ? getXmlMetadata(type) : undefined,
clientNamespace: getClientNamespace(context, type.clientNamespace),
};

typesMap.set(type, newValue);
Expand All @@ -314,7 +321,10 @@ function emitModel<TServiceOperation extends SdkServiceOperation>(
return newValue;
}

function emitEnum(type: SdkEnumType): Record<string, any> {
function emitEnum<TServiceOperation extends SdkServiceOperation>(
context: PythonSdkContext<TServiceOperation>,
type: SdkEnumType,
): Record<string, any> {
if (typesMap.has(type)) {
return typesMap.get(type)!;
}
Expand Down Expand Up @@ -352,6 +362,7 @@ function emitEnum(type: SdkEnumType): Record<string, any> {
values,
xmlMetadata: {},
crossLanguageDefinitionId: type.crossLanguageDefinitionId,
clientNamespace: getClientNamespace(context, type.clientNamespace),
};
for (const value of type.values) {
newValue.values.push(emitEnumMember(value, newValue));
Expand Down Expand Up @@ -469,6 +480,7 @@ function emitUnion<TServiceOperation extends SdkServiceOperation>(
type: "combined",
types: type.variantTypes.map((x) => getType(context, x)),
xmlMetadata: {},
clientNamespace: getClientNamespace(context, type.clientNamespace),
});
}

Expand Down
21 changes: 21 additions & 0 deletions packages/http-client-python/emitter/src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -199,3 +199,24 @@ export function isAzureCoreErrorResponse(t: SdkType | undefined): boolean {
export function capitalize(name: string): string {
return name[0].toUpperCase() + name.slice(1);
}

export function getClientNamespace<TServiceOperation extends SdkServiceOperation>(
context: PythonSdkContext<TServiceOperation>,
clientNamespace: string,
) {
const rootNamespace = removeUnderscoresFromNamespace(
context.sdkPackage.rootNamespace,
).toLowerCase();
const options = context.emitContext.options;
if ([undefined, false].includes(options["enable-typespec-namespace"])) {
return rootNamespace;
}
if (
["azure.core", "azure.resourcemanager"].some((item) =>
clientNamespace.toLowerCase().startsWith(item),
)
) {
return rootNamespace;
}
return removeUnderscoresFromNamespace(clientNamespace).toLowerCase();
}
3 changes: 3 additions & 0 deletions packages/http-client-python/eng/scripts/ci/regenerate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,9 @@ const EMITTER_OPTIONS: Record<string, Record<string, string> | Record<string, st
"client/structure/two-operation-group": {
"package-name": "client-structure-twooperationgroup",
},
"client/namespace": {
"enable-typespec-namespace": "true",
},
};

function toPosix(dir: string): string {
Expand Down
2 changes: 1 addition & 1 deletion packages/http-client-python/generator/pygen/black.py
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ def process(self) -> bool:
"venv",
"env",
)
and not Path(f).parts[0].startswith(".")
and (not Path(f).parts[0].startswith(".") or Path(f).parts[0] == "..")
and Path(f).suffix == ".py"
],
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ class OptionsRetriever:
"generate-test": False,
"from-typespec": False,
"emit-cross-language-definition-file": False,
"enable-typespec-namespace": False,
}

@property
Expand Down Expand Up @@ -316,6 +317,7 @@ 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}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,3 +15,7 @@
TYPESPEC_PACKAGE_MODE = ["azure-mgmt", "azure-dataplane", "generic"]
VALID_PACKAGE_MODE = SWAGGER_PACKAGE_MODE + TYPESPEC_PACKAGE_MODE
NAME_LENGTH_LIMIT = 40


def get_parent_namespace(namespace: str) -> str:
return namespace.rsplit(".", 1)[0]
Original file line number Diff line number Diff line change
Expand Up @@ -87,8 +87,7 @@ def xml_serialization_ctxt(self) -> Optional[str]:
attrs_list.append("'text': True")
return ", ".join(attrs_list)

@property
def serialization_type(self) -> str:
def serialization_type(self, **kwargs: Any) -> str:
"""The tag recognized by 'msrest' as a serialization/deserialization.

'str', 'int', 'float', 'bool' or
Expand All @@ -103,7 +102,7 @@ def serialization_type(self) -> str:

@property
def msrest_deserialization_key(self) -> str:
return self.serialization_type
return self.serialization_type()

@property
def client_default_value(self) -> Any:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ def __init__(
self.api_versions: List[str] = yaml_data["apiVersions"]
self.added_on: Optional[str] = yaml_data.get("addedOn")
self.external_docs: Optional[Dict[str, Any]] = yaml_data.get("externalDocs")
self.client_namespace: str = yaml_data.get("clientNamespace", code_model.namespace)

if code_model.options["version_tolerant"] and yaml_data.get("abstract"):
_LOGGER.warning(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from .base import BaseModel
from .parameter_list import ClientGlobalParameterList, ConfigGlobalParameterList
from .imports import FileImport, ImportType, TypingSection, MsrestImportType
from .utils import add_to_pylint_disable
from .utils import add_to_pylint_disable, NamespaceType
from .operation_group import OperationGroup
from .request_builder import (
RequestBuilder,
Expand Down Expand Up @@ -43,6 +43,7 @@ def __init__(
self.parameters = parameters
self.url: str = self.yaml_data["url"] # the base endpoint of the client. Can be parameterized or not
self.legacy_filename: str = self.yaml_data.get("legacyFilename", "client")
self.client_namespace: str = self.yaml_data.get("clientNamespace", code_model.namespace)

@property
def description(self) -> str:
Expand Down Expand Up @@ -188,7 +189,7 @@ def lookup_operation(self, operation_id: int) -> "OperationType":
except StopIteration as exc:
raise KeyError(f"No operation with id {operation_id} found.") from exc

def _imports_shared(self, async_mode: bool) -> FileImport:
def _imports_shared(self, async_mode: bool, **kwargs) -> FileImport:
file_import = FileImport(self.code_model)
file_import.add_submodule_import("typing", "Any", ImportType.STDLIB, TypingSection.CONDITIONAL)
if self.code_model.options["azure_arm"]:
Expand All @@ -206,17 +207,18 @@ def _imports_shared(self, async_mode: bool) -> FileImport:
file_import.merge(
gp.imports(
async_mode,
relative_path=".." if async_mode else ".",
operation=True,
is_operation_file=True,
**kwargs,
)
)
file_import.add_submodule_import(
"._configuration",
f"{self.name}Configuration",
ImportType.LOCAL,
)
serialize_namespace = kwargs.get("serialize_namespace", self.code_model.namespace)
file_import.add_msrest_import(
relative_path=".." if async_mode else ".",
serialize_namespace=serialize_namespace,
msrest_import_type=MsrestImportType.SerializerDeserializer,
typing_section=TypingSection.REGULAR,
)
Expand Down Expand Up @@ -277,8 +279,8 @@ def has_non_abstract_operations(self) -> bool:
"""Whether there is non-abstract operation in any operation group."""
return any(og.has_non_abstract_operations for og in self.operation_groups)

def imports(self, async_mode: bool) -> FileImport:
file_import = self._imports_shared(async_mode)
def imports(self, async_mode: bool, **kwargs) -> FileImport:
file_import = self._imports_shared(async_mode, **kwargs)
if async_mode:
file_import.add_submodule_import("typing", "Awaitable", ImportType.STDLIB)
file_import.add_submodule_import(
Expand All @@ -300,9 +302,15 @@ def imports(self, async_mode: bool) -> FileImport:
ImportType.SDKCORE,
TypingSection.CONDITIONAL,
)
serialize_namespace = kwargs.get("serialize_namespace", self.code_model.namespace)
for og in self.operation_groups:
file_import.add_submodule_import(
f".{self.code_model.operations_folder_name}",
self.code_model.get_relative_import_path(
serialize_namespace,
og.client_namespace,
async_mode=async_mode,
imported_namespace_type=NamespaceType.OPERATION,
),
og.class_name,
ImportType.LOCAL,
)
Expand All @@ -317,8 +325,8 @@ def imports(self, async_mode: bool) -> FileImport:
file_import.add_submodule_import("copy", "deepcopy", ImportType.STDLIB)
return file_import

def imports_for_multiapi(self, async_mode: bool) -> FileImport:
file_import = self._imports_shared(async_mode)
def imports_for_multiapi(self, async_mode: bool, **kwargs) -> FileImport:
file_import = self._imports_shared(async_mode, **kwargs)
file_import.add_submodule_import("typing", "Optional", ImportType.STDLIB, TypingSection.CONDITIONAL)
try:
mixin_operation = next(og for og in self.operation_groups if og.is_mixin)
Expand Down Expand Up @@ -377,7 +385,7 @@ def sdk_moniker(self) -> str:
def name(self) -> str:
return f"{super().name}Configuration"

def _imports_shared(self, async_mode: bool) -> FileImport:
def _imports_shared(self, async_mode: bool, **kwargs: Any) -> FileImport:
file_import = FileImport(self.code_model)
file_import.add_submodule_import(
"pipeline" if self.code_model.is_azure_flavor else "runtime",
Expand All @@ -386,30 +394,34 @@ def _imports_shared(self, async_mode: bool) -> FileImport:
)
file_import.add_submodule_import("typing", "Any", ImportType.STDLIB, TypingSection.CONDITIONAL)
if self.code_model.options["package_version"]:
file_import.add_submodule_import(".._version" if async_mode else "._version", "VERSION", ImportType.LOCAL)
serialize_namespace = kwargs.get("serialize_namespace", self.code_model.namespace)
file_import.add_submodule_import(
self.code_model.get_relative_import_path(serialize_namespace, module_name="_version"),
"VERSION",
ImportType.LOCAL,
)
if self.code_model.options["azure_arm"]:
policy = "AsyncARMChallengeAuthenticationPolicy" if async_mode else "ARMChallengeAuthenticationPolicy"
file_import.add_submodule_import("azure.mgmt.core.policies", "ARMHttpLoggingPolicy", ImportType.SDKCORE)
file_import.add_submodule_import("azure.mgmt.core.policies", policy, ImportType.SDKCORE)

return file_import

def imports(self, async_mode: bool) -> FileImport:
file_import = self._imports_shared(async_mode)
def imports(self, async_mode: bool, **kwargs) -> FileImport:
file_import = self._imports_shared(async_mode, **kwargs)
for gp in self.parameters:
if gp.method_location == ParameterMethodLocation.KWARG and gp not in self.parameters.kwargs_to_pop:
continue
file_import.merge(
gp.imports(
async_mode=async_mode,
relative_path=".." if async_mode else ".",
operation=True,
**kwargs,
)
)
return file_import

def imports_for_multiapi(self, async_mode: bool) -> FileImport:
file_import = self._imports_shared(async_mode)
def imports_for_multiapi(self, async_mode: bool, **kwargs: Any) -> FileImport:
file_import = self._imports_shared(async_mode, **kwargs)
for gp in self.parameters:
if (
gp.method_location == ParameterMethodLocation.KWARG
Expand All @@ -420,8 +432,7 @@ def imports_for_multiapi(self, async_mode: bool) -> FileImport:
file_import.merge(
gp.imports_for_multiapi(
async_mode=async_mode,
relative_path=".." if async_mode else ".",
operation=True,
**kwargs,
)
)
return file_import
Expand Down
Loading
Loading