diff --git a/.chronus/changes/python-update-eng-2026-4-8-17-7-57.md b/.chronus/changes/python-update-eng-2026-4-8-17-7-57.md new file mode 100644 index 0000000000..3efbe497cf --- /dev/null +++ b/.chronus/changes/python-update-eng-2026-4-8-17-7-57.md @@ -0,0 +1,7 @@ +--- +changeKind: internal +packages: + - "@azure-tools/typespec-python" +--- + +Simplify eng system for typespec-python \ No newline at end of file diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index 05bf6e066a..fec17276c3 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,6 +1,8 @@ -* @bterlson @markcowl @allenjzhang @timotheeguerin +* @bterlson @markcowl @timotheeguerin /packages/typespec-client-generator-core @lmazuel @m-nash @iscai-msft @srnagar @joheredi /packages/azure-http-specs/specs/ @iscai-msft @lmazuel @m-nash @joheredi @srnagar @weidongxu-microsoft @tadelesh @jhendrixMSFT /docs/howtos/DataPlane*/ @lmazuel @m-nash @iscai-msft @srnagar @joheredi + +/packages/typespec-python @iscai-msft @msyyc @tadelesh diff --git a/.gitignore b/.gitignore index bb5e091993..c08fc96280 100644 --- a/.gitignore +++ b/.gitignore @@ -224,3 +224,4 @@ packages/samples/test/output/azure/templates-contoso/setup.* # python emitter packages/typespec-python/tests/generated/** +packages/typespec-python/alpha diff --git a/.vscode/launch.json b/.vscode/launch.json index 306dd0c704..331a42d0e0 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -219,6 +219,21 @@ "request": "launch", "args": ["--extensionDevelopmentPath=${workspaceFolder}/packages/typespec-azure-vscode"], "sourceMaps": true + }, + { + "name": "typespec-python", + "request": "launch", + "args": [ + "compile", + "${workspaceFolder}/packages/typespec-python/alpha/client.tsp", + "--emit=${workspaceFolder}/packages/typespec-python", + "--option=@azure-tools/typespec-python.flavor=azure" + ], + "program": "${workspaceRoot}/core/packages/compiler/entrypoints/cli.js", + "skipFiles": ["/**"], + "type": "node", + "smartStep": true, + "sourceMaps": true } ], "compounds": [ diff --git a/packages/typespec-python/dev_requirements.txt b/packages/typespec-python/dev_requirements.txt index 3327859bfd..57e26e4363 100644 --- a/packages/typespec-python/dev_requirements.txt +++ b/packages/typespec-python/dev_requirements.txt @@ -1,16 +1,16 @@ # shall keep aligned with dev_requirements.txt of @typspec/http-client-python -pyright==1.1.391 -pylint==3.2.7 -tox==4.23.2 +pyright==1.1.407 +pylint==4.0.4 +tox==4.16.0 tox-uv -mypy==1.13.0 +mypy==1.19.1 colorama==0.4.6 -debugpy==1.8.12 -pytest==8.3.4 +debugpy==1.8.2 +pytest==8.3.2 coverage==7.6.1 -black==24.8.0 +black==26.3.1 ptvsd==4.3.2 -types-PyYAML==6.0.12.20241230 +types-PyYAML==6.0.12.8 # additional dependency needed for development setuptools diff --git a/packages/typespec-python/eng/scripts/ci/config/mypy.ini b/packages/typespec-python/eng/scripts/ci/config/mypy.ini index cfd4a49719..6bd11f1f11 100644 --- a/packages/typespec-python/eng/scripts/ci/config/mypy.ini +++ b/packages/typespec-python/eng/scripts/ci/config/mypy.ini @@ -1,9 +1,8 @@ # global configurations [mypy] -python_version = 3.9 +python_version = 3.10 # Exclude mypy check for sub client tests -exclude = (.*azure/clientgenerator/core/clientinitialization/operations/_operations\.py|.*azure/clientgenerator/core/clientinitialization/aio/operations/_operations\.py) - +exclude = .*/clientinitialization/.*\.py # module level configurations [mypy-jsonrpc.*] @@ -38,3 +37,5 @@ ignore_missing_imports = True [mypy-yaml.*] ignore_missing_imports = True + + diff --git a/packages/typespec-python/eng/scripts/ci/config/pylintrc b/packages/typespec-python/eng/scripts/ci/config/pylintrc index ad23a561c6..387021335f 100644 --- a/packages/typespec-python/eng/scripts/ci/config/pylintrc +++ b/packages/typespec-python/eng/scripts/ci/config/pylintrc @@ -1,5 +1,5 @@ [MASTER] -py-version=3.9 +py-version=3.10 ignore-patterns=test_*,conftest,setup reports=no @@ -17,7 +17,7 @@ enable=useless-suppression # too-many-arguments: Due to the nature of the CLI many commands have large arguments set which reflect in large arguments set in corresponding methods. # too-many-lines: Due to code generation many files end up with too many lines. # Let's black deal with bad-continuation -disable=missing-docstring,locally-disabled,fixme,cyclic-import,too-many-arguments,invalid-name,duplicate-code,too-few-public-methods,consider-using-f-string,super-with-arguments,redefined-builtin,import-outside-toplevel,client-suffix-needed,unnecessary-dunder-call,unnecessary-ellipsis,disallowed-name,consider-using-max-builtin,unknown-option-value,file-needs-copyright-header +disable=missing-docstring,locally-disabled,fixme,cyclic-import,too-many-arguments,invalid-name,duplicate-code,too-few-public-methods,consider-using-f-string,super-with-arguments,redefined-builtin,import-outside-toplevel,client-suffix-needed,unnecessary-dunder-call,unnecessary-ellipsis,disallowed-name,consider-using-max-builtin,unknown-option-value,file-needs-copyright-header,too-many-positional-arguments [FORMAT] max-line-length=120 diff --git a/packages/typespec-python/eng/scripts/ci/config/pyrightconfig.json b/packages/typespec-python/eng/scripts/ci/config/pyrightconfig.json index bebfe77f91..ef8340726d 100644 --- a/packages/typespec-python/eng/scripts/ci/config/pyrightconfig.json +++ b/packages/typespec-python/eng/scripts/ci/config/pyrightconfig.json @@ -2,7 +2,5 @@ "reportUnnecessaryCast": "warning", "reportTypeCommentUsage": true, "reportMissingImports": false, - "reportAttributeAccessIssue": false, - "pythonVersion": "3.9", - "exclude": ["**/build/**"] + "pythonVersion": "3.10" } diff --git a/packages/typespec-python/eng/scripts/regenerate.ts b/packages/typespec-python/eng/scripts/ci/regenerate-common.ts similarity index 51% rename from packages/typespec-python/eng/scripts/regenerate.ts rename to packages/typespec-python/eng/scripts/ci/regenerate-common.ts index 9fd3ab95f7..5e6b2b9ed8 100644 --- a/packages/typespec-python/eng/scripts/regenerate.ts +++ b/packages/typespec-python/eng/scripts/ci/regenerate-common.ts @@ -1,149 +1,94 @@ /* eslint-disable no-console */ /** - * Regenerates Python SDK code from TypeSpec definitions. + * Shared helpers, types, constants, and data tables used by `regenerate.ts`. * - * Uses in-process TypeSpec compilation to avoid subprocess spawning overhead. - * This is significantly faster than spawning `tsp compile` for each spec. + * This file is meant to be **byte-identical** between this package and the + * upstream `@typespec/http-client-python`. typespec-python syncs it from + * /core/packages/http-client-python/eng/scripts/ci/regenerate-common.ts + * via `pnpm sync`. + * + * Per-repo divergence (paths, emitter name, single-phase vs two-phase + * orchestration, argv/help text) lives in each repo's own `regenerate.ts`, + * which builds a `RegenerateContext` and feeds it into the helpers exported + * from this module. */ import { compile, NodeHost } from "@typespec/compiler"; -import { promises, rmSync } from "fs"; -import { platform } from "os"; +import { execSync } from "child_process"; +import { existsSync, rmSync } from "fs"; +import { access, cp, mkdir, mkdtemp, readdir, writeFile } from "fs/promises"; +import { tmpdir } from "os"; import { dirname, join, relative, resolve } from "path"; import pc from "picocolors"; -import { fileURLToPath } from "url"; -import { parseArgs } from "util"; - -// ---- Shared constants ---- -const SKIP_SPECS: string[] = ["type/file"]; +// ---- Public types ---- -const SpecialFlags: Record> = { - azure: { - "generate-test": true, - "generate-sample": true, - }, -}; - -function toPosix(dir: string): string { - return dir.replace(/\\/g, "/"); -} - -interface RegenerateFlags { +export interface RegenerateFlags { flavor: string; debug: boolean; name?: string; } -async function getSubdirectories(baseDir: string, flags: RegenerateFlags): Promise { - const subdirectories: string[] = []; - - async function searchDir(currentDir: string) { - const items = await promises.readdir(currentDir, { withFileTypes: true }); - - const promisesArray = items.map(async (item) => { - const subDirPath = join(currentDir, item.name); - if (item.isDirectory()) { - const mainTspPath = join(subDirPath, "main.tsp"); - const clientTspPath = join(subDirPath, "client.tsp"); - - const mainTspRelativePath = toPosix(relative(baseDir, mainTspPath)); - - if (SKIP_SPECS.some((skipSpec) => mainTspRelativePath.includes(skipSpec))) return; - - const hasMainTsp = await promises - .access(mainTspPath) - .then(() => true) - .catch(() => false); - const hasClientTsp = await promises - .access(clientTspPath) - .then(() => true) - .catch(() => false); - - if (mainTspRelativePath.toLowerCase().includes(flags.name || "")) { - if (mainTspRelativePath.includes("resiliency/srv-driven")) { - subdirectories.push(resolve(subDirPath, "old.tsp")); - } - if (hasClientTsp) { - subdirectories.push(resolve(subDirPath, "client.tsp")); - } else if (hasMainTsp) { - subdirectories.push(resolve(subDirPath, "main.tsp")); - } - } - - await searchDir(subDirPath); - } - }); - - await Promise.all(promisesArray); - } - - await searchDir(baseDir); - return subdirectories; +export interface CompileTask { + spec: string; + outputDir: string; + options: Record; } -// Parse arguments -const argv = parseArgs({ - args: process.argv.slice(2), - options: { - flavor: { type: "string", short: "f" }, - name: { type: "string", short: "n" }, - debug: { type: "boolean", short: "d" }, - jobs: { type: "string", short: "j" }, - help: { type: "boolean", short: "h" }, - }, -}); - -if (argv.values.help) { - console.log(` -${pc.bold("Usage:")} tsx regenerate.ts [options] - -${pc.bold("Description:")} - Regenerates Python SDK code from TypeSpec definitions using in-process compilation. - This avoids spawning a new Node.js process for each spec, making it significantly faster. - -${pc.bold("Options:")} - ${pc.cyan("-f, --flavor ")} - SDK flavor to regenerate. If not specified, regenerates both flavors. - - ${pc.cyan("-n, --name ")} - Filter packages by name pattern (case-insensitive substring match). - - ${pc.cyan("-d, --debug")} - Enable debug output during regeneration. - - ${pc.cyan("-j, --jobs ")} - Number of parallel compilation tasks (default: 30 on Linux/Mac, 10 on Windows). +// Group of tasks for the same spec that must run sequentially +export interface TaskGroup { + spec: string; + tasks: CompileTask[]; +} - ${pc.cyan("-h, --help")} - Show this help message. +/** + * Per-repo context injected into the helpers below. Every value is repo + * specific and must be supplied by the caller's `regenerate.ts`. + */ +export interface RegenerateContext { + /** Absolute path to the package root (the dir containing `package.json`). */ + pluginDir: string; + /** Absolute path to the azure-http-specs `specs/` dir. */ + azureHttpSpecs: string; + /** Absolute path to the http-specs `specs/` dir. */ + httpSpecs: string; + /** Absolute path to where generated SDKs should go (e.g. `/generator`). */ + generatedFolder: string; + /** Emitter name to invoke (e.g. `@azure-tools/typespec-python`). */ + emitterName: string; +} -${pc.bold("Examples:")} - ${pc.dim("# Regenerate all packages for both flavors")} - tsx regenerate.ts +/** + * Optional knobs for `buildTaskGroups`. Kept here so the call site in each + * repo's `regenerate.ts` can opt into the upstream two-phase pipeline + * (`emitYamlOnly: true`) or the single-phase pipeline (default). + */ +export interface BuildTaskGroupsOptions { + /** If true, ask the emitter to write YAML only and skip Python codegen. */ + emitYamlOnly?: boolean; +} - ${pc.dim("# Regenerate only Azure packages")} - tsx regenerate.ts --flavor azure +// ---- Public constants ---- - ${pc.dim("# Regenerate a specific package by name")} - tsx regenerate.ts --flavor azure --name authentication-api-key -`); - process.exit(0); -} +export const SKIP_SPECS: string[] = [ + "type/file", + "service/multiple-services", + "azure/client-generator-core/response-as-bool", +]; -// Get paths -const SCRIPT_DIR = dirname(fileURLToPath(import.meta.url)); -const PLUGIN_DIR = resolve(SCRIPT_DIR, "../../"); -const AZURE_HTTP_SPECS = resolve(PLUGIN_DIR, "node_modules/@azure-tools/azure-http-specs/specs"); -const HTTP_SPECS = resolve(PLUGIN_DIR, "node_modules/@typespec/http-specs/specs"); -const EMITTER_NAME = "@azure-tools/typespec-python"; +export const SpecialFlags: Record> = { + azure: { + "generate-test": true, + "generate-sample": true, + }, +}; -function isAzureSpec(specPath: string): boolean { - return specPath.startsWith(AZURE_HTTP_SPECS); -} +// ---- Spec-specific emitter option overrides ---- -// Emitter options -const AZURE_EMITTER_OPTIONS: Record | Record[]> = { +export const AZURE_EMITTER_OPTIONS: Record< + string, + Record | Record[] +> = { "azure/client-generator-core/access": { namespace: "specs.azure.clientgenerator.core.access", }, @@ -162,12 +107,6 @@ const AZURE_EMITTER_OPTIONS: Record | Record | Record | Record | Record[]> = { +export const EMITTER_OPTIONS: Record | Record[]> = { "resiliency/srv-driven/old.tsp": { "package-name": "resiliency-srv-driven1", namespace: "resiliency.srv.driven1", @@ -352,6 +292,7 @@ const EMITTER_OPTIONS: Record | Record | Record; +// ---- Public helpers ---- + +export function toPosix(p: string): string { + return p.replace(/\\/g, "/"); } -// Group of tasks for the same spec that must run sequentially -interface TaskGroup { - spec: string; - tasks: CompileTask[]; +/** + * Whether a spec path belongs to azure-http-specs (vs standard http-specs). + * Uses the `azure-http-specs` substring rather than `azure` to avoid false + * positives when the working-dir path itself contains "azure" (e.g. + * azure-sdk-for-python). + */ +export function isAzureSpec(spec: string): boolean { + return spec.includes("azure-http-specs"); } -function defaultPackageName(spec: string): string { - const specDir = isAzureSpec(spec) ? AZURE_HTTP_SPECS : HTTP_SPECS; +export function defaultPackageName(spec: string, ctx: RegenerateContext): string { + const specDir = isAzureSpec(spec) ? ctx.azureHttpSpecs : ctx.httpSpecs; return toPosix(relative(specDir, dirname(spec))) .replace(/\//g, "-") .toLowerCase(); } -function getEmitterOptions(spec: string, flavor: string): Record[] { - const specDir = isAzureSpec(spec) ? AZURE_HTTP_SPECS : HTTP_SPECS; +export function getEmitterOptions( + spec: string, + flavor: string, + ctx: RegenerateContext, +): Record[] { + const specDir = isAzureSpec(spec) ? ctx.azureHttpSpecs : ctx.httpSpecs; const relativeSpec = toPosix(relative(specDir, spec)); const key = relativeSpec.includes("resiliency/srv-driven/old.tsp") ? relativeSpec @@ -448,37 +404,102 @@ function getEmitterOptions(spec: string, flavor: string): Record return Array.isArray(emitterOpts) ? emitterOpts : [emitterOpts]; } -function buildTaskGroups(specs: string[], flags: RegenerateFlags): TaskGroup[] { +/** + * Walk `baseDir` and collect every TypeSpec entry-point file that the + * regenerator should compile (handles `client.tsp`, `main.tsp`, and the + * special `resiliency/srv-driven/old.tsp` case). + */ +export async function getSubdirectories( + baseDir: string, + flags: RegenerateFlags, +): Promise { + const subdirectories: string[] = []; + + async function searchDir(currentDir: string) { + const items = await readdir(currentDir, { withFileTypes: true }); + + const promisesArray = items.map(async (item) => { + const subDirPath = join(currentDir, item.name); + if (item.isDirectory()) { + const mainTspPath = join(subDirPath, "main.tsp"); + const clientTspPath = join(subDirPath, "client.tsp"); + + const mainTspRelativePath = toPosix(relative(baseDir, mainTspPath)); + + if (SKIP_SPECS.some((skipSpec) => mainTspRelativePath.includes(skipSpec))) return; + + const hasMainTsp = await access(mainTspPath) + .then(() => true) + .catch(() => false); + const hasClientTsp = await access(clientTspPath) + .then(() => true) + .catch(() => false); + + if (mainTspRelativePath.toLowerCase().includes(flags.name || "")) { + if (mainTspRelativePath.includes("resiliency/srv-driven")) { + subdirectories.push(resolve(subDirPath, "old.tsp")); + } + if (hasClientTsp) { + subdirectories.push(resolve(subDirPath, "client.tsp")); + } else if (hasMainTsp) { + subdirectories.push(resolve(subDirPath, "main.tsp")); + } + } + + await searchDir(subDirPath); + } + }); + + await Promise.all(promisesArray); + } + + await searchDir(baseDir); + return subdirectories; +} + +export function buildTaskGroups( + specs: string[], + flags: RegenerateFlags, + ctx: RegenerateContext, + options: BuildTaskGroupsOptions = {}, +): TaskGroup[] { const groups: TaskGroup[] = []; for (const spec of specs) { const tasks: CompileTask[] = []; - for (const emitterConfig of getEmitterOptions(spec, flags.flavor)) { - const options: Record = { ...emitterConfig }; - - // Add flavor-specific options - options["flavor"] = flags.flavor; + for (const emitterConfig of getEmitterOptions(spec, flags.flavor, ctx)) { + // Apply flavor defaults first, then per-spec options so they can override + // (e.g. "generate-test": "false") + const opts: Record = {}; for (const [k, v] of Object.entries(SpecialFlags[flags.flavor] ?? {})) { - options[k] = v; + opts[k] = v; } + Object.assign(opts, emitterConfig); - // Set output directory - use tests/generated// structure - const packageName = (options["package-name"] as string) || defaultPackageName(spec); + opts["flavor"] = flags.flavor; + + // Set output directory - tests/generated// structure. + // Always anchored at /../tests/generated regardless of + // pluginDir, so generator-only checkouts work too. + const packageName = (opts["package-name"] as string) || defaultPackageName(spec, ctx); const outputDir = - (options["emitter-output-dir"] as string) || - toPosix(`${PLUGIN_DIR}/tests/generated/${flags.flavor}/${packageName}`); - options["emitter-output-dir"] = outputDir; + (opts["emitter-output-dir"] as string) || + toPosix(`${ctx.generatedFolder}/../tests/generated/${flags.flavor}/${packageName}`); + opts["emitter-output-dir"] = outputDir; - // Debug mode if (flags.debug) { - options["debug"] = true; + opts["debug"] = true; } - // Examples directory - options["examples-dir"] = toPosix(join(dirname(spec), "examples")); + opts["examples-dir"] = toPosix(join(dirname(spec), "examples")); + + if (options.emitYamlOnly) { + // Emit YAML only - Python processing is batched after all specs compile. + opts["emit-yaml-only"] = true; + } - tasks.push({ spec, outputDir, options }); + tasks.push({ spec, outputDir, options: opts }); } groups.push({ spec, tasks }); @@ -487,19 +508,20 @@ function buildTaskGroups(specs: string[], flags: RegenerateFlags): TaskGroup[] { return groups; } -async function compileSpec(task: CompileTask): Promise<{ success: boolean; error?: string }> { +export async function compileSpec( + task: CompileTask, + ctx: RegenerateContext, +): Promise<{ success: boolean; error?: string }> { const { spec, outputDir, options } = task; try { - // Build compiler options const compilerOptions = { - emit: [PLUGIN_DIR], + emit: [ctx.pluginDir], options: { - [EMITTER_NAME]: options, + [ctx.emitterName]: options, }, }; - // Compile using TypeSpec compiler directly (no subprocess) const program = await compile(NodeHost, spec, compilerOptions); if (program.hasError()) { @@ -512,44 +534,74 @@ async function compileSpec(task: CompileTask): Promise<{ success: boolean; error return { success: true }; } catch (err) { - // Clean up on error rmSync(outputDir, { recursive: true, force: true }); return { success: false, error: String(err) }; } } -async function runParallel(groups: TaskGroup[], maxJobs: number): Promise> { +export function renderProgressBar( + completed: number, + failed: number, + total: number, + width: number = 40, +): string { + const successCount = completed - failed; + const successWidth = Math.round((successCount / total) * width); + const failWidth = Math.round((failed / total) * width); + const emptyWidth = width - successWidth - failWidth; + + const successBar = pc.bgGreen(" ".repeat(successWidth)); + const failBar = failed > 0 ? pc.bgRed(" ".repeat(failWidth)) : ""; + const emptyBar = pc.dim("░".repeat(Math.max(0, emptyWidth))); + + const percent = Math.round((completed / total) * 100); + return `${successBar}${failBar}${emptyBar} ${pc.cyan(`${percent}%`)} (${completed}/${total})`; +} + +export async function runParallel( + groups: TaskGroup[], + maxJobs: number, + ctx: RegenerateContext, +): Promise> { const results = new Map(); const executing: Set> = new Set(); - // Count total tasks for progress const totalTasks = groups.reduce((sum, g) => sum + g.tasks.length, 0); let completed = 0; + let failed = 0; + const failedSpecs: string[] = []; + + const isTTY = process.stdout.isTTY; + + const updateProgress = () => { + if (isTTY) { + process.stdout.write(`\r${renderProgressBar(completed, failed, totalTasks)}`); + } + }; + + updateProgress(); for (const group of groups) { // Each group runs as a unit - tasks within a group run sequentially - // But different groups can run in parallel + // to avoid state pollution. Different groups run in parallel. const runGroup = async () => { - const specDir = isAzureSpec(group.spec) ? AZURE_HTTP_SPECS : HTTP_SPECS; + const specDir = isAzureSpec(group.spec) ? ctx.azureHttpSpecs : ctx.httpSpecs; const shortName = toPosix(relative(specDir, dirname(group.spec))); - // Run all tasks in this group sequentially to avoid state pollution let groupSuccess = true; for (const task of group.tasks) { const packageName = (task.options["package-name"] as string) || shortName; - console.log(pc.blue(`[${completed + 1}/${totalTasks}] Compiling ${packageName}...`)); - const result = await compileSpec(task); + const result = await compileSpec(task, ctx); completed++; - if (result.success) { - console.log(pc.green(`[${completed}/${totalTasks}] ${packageName} succeeded`)); - } else { - console.log( - pc.red(`[${completed}/${totalTasks}] ${packageName} failed: ${result.error}`), - ); + if (!result.success) { + failed++; + failedSpecs.push(`${packageName}: ${result.error}`); groupSuccess = false; } + + updateProgress(); } results.set(group.spec, groupSuccess); @@ -564,125 +616,140 @@ async function runParallel(groups: TaskGroup[], maxJobs: number): Promise { - if (flavor === "azure") { - const generalParts = [PLUGIN_DIR, "tests", "generated", "azure"]; - const authFile = join( - ...generalParts, - "authentication-api-key", - "authentication", - "apikey", - "_operations", - "to_be_deleted.py", - ); - await promises.mkdir(dirname(authFile), { recursive: true }); - await promises.writeFile(authFile, "# This file is to be deleted after regeneration"); - - const folderParts = [...generalParts, "generation-subdir"]; - const genFile = join(...folderParts, "generation", "subdir", "_generated", "to_be_deleted.py"); - await promises.mkdir(dirname(genFile), { recursive: true }); - await promises.writeFile(genFile, "# This file is to be deleted after regeneration"); - - const testFile = join(...folderParts, "generated_tests", "to_be_deleted.py"); - await promises.mkdir(dirname(testFile), { recursive: true }); - await promises.writeFile(testFile, "# This file is to be kept after regeneration"); - - const keptFile = join(...folderParts, "generation", "subdir", "to_be_kept.py"); - await promises.mkdir(dirname(keptFile), { recursive: true }); - await promises.writeFile(keptFile, "# This file is to be kept after regeneration"); + if (isTTY) { + process.stdout.write("\r" + " ".repeat(60) + "\r"); } -} -async function regenerateFlavor( - flavor: string, - name: string | undefined, - debug: boolean, - jobs: number, -): Promise { - console.log(pc.cyan(`\n${"=".repeat(60)}`)); - console.log(pc.cyan(`Regenerating ${flavor} flavor`)); - console.log(pc.cyan(`${"=".repeat(60)}\n`)); - - const flags: RegenerateFlags = { flavor, debug, name }; - - // Preprocess - await preprocess(flavor); + if (failedSpecs.length > 0) { + console.log(pc.red(`\nFailed specs:`)); + for (const spec of failedSpecs) { + console.log(pc.red(` • ${spec}`)); + } + } - // Collect specs - const azureSpecs = flavor === "azure" ? await getSubdirectories(AZURE_HTTP_SPECS, flags) : []; - const standardSpecs = await getSubdirectories(HTTP_SPECS, flags); - const allSpecs = [...azureSpecs, ...standardSpecs]; + return results; +} - // Build task groups (tasks for same spec run sequentially to avoid state pollution) - const groups = buildTaskGroups(allSpecs, flags); - const totalTasks = groups.reduce((sum, g) => sum + g.tasks.length, 0); +/** + * Pre-create the marker files that the test harness expects to find before + * regeneration so it can verify they're cleared/preserved correctly. + */ +export async function preprocess(flavor: string, generatedFolder: string): Promise { + if (flavor !== "azure") return; - console.log(pc.cyan(`Found ${allSpecs.length} specs (${totalTasks} total tasks) to compile`)); - console.log(pc.cyan(`Using ${jobs} parallel jobs\n`)); + const testsGeneratedDir = resolve(generatedFolder, "../tests/generated/azure"); - // Run compilation - const startTime = performance.now(); - const results = await runParallel(groups, jobs); - const duration = (performance.now() - startTime) / 1000; + const DELETE_CONTENT = "# This file is to be deleted after regeneration"; + const KEEP_CONTENT = "# This file is to be kept after regeneration"; + const DELETE_FILE = "to_be_deleted.py"; + const entries: { folder: string[]; file: string; content: string }[] = [ + { + folder: ["authentication-api-key", "authentication", "apikey", "_operations"], + file: DELETE_FILE, + content: DELETE_CONTENT, + }, + { + folder: ["generation-subdir", "generation", "subdir", "_generated"], + file: DELETE_FILE, + content: DELETE_CONTENT, + }, + { + folder: ["generation-subdir", "generated_tests"], + file: DELETE_FILE, + content: DELETE_CONTENT, + }, + { + folder: ["generation-subdir", "generation", "subdir"], + file: "to_be_kept.py", + content: KEEP_CONTENT, + }, + ]; + + await Promise.all( + entries.map(async ({ folder, file, content }) => { + const targetFolder = join(testsGeneratedDir, ...folder); + await mkdir(targetFolder, { recursive: true }); + await writeFile(join(targetFolder, file), content); + }), + ); +} - // Summary - const succeeded = Array.from(results.values()).filter((v) => v).length; - const failed = results.size - succeeded; +/** + * Resets the `tests/generated/{azure,unbranded}` baseline by sparse-checking-out + * `eng/tools/azure-sdk-tools/emitter/generated` from the Azure/azure-sdk-for-python repo, then + * deleting a couple of fully-generated package folders so regeneration has to + * recreate them from scratch (smoke test of full-emit path). + * + * `generatedFolder` is the per-repo `generator/` directory; baseline lands at + * `/../tests/generated`. + */ +export async function prepareBaselineOfGeneratedCode(generatedFolder: string): Promise { + const repoUrl = "https://github.com/Azure/azure-sdk-for-python.git"; + const branch = "typespec-python-generated-tests"; + const sourceSubdir = "eng/tools/azure-sdk-tools/emitter/generated"; + const testsGeneratedDir = resolve(generatedFolder, "../tests/generated"); console.log(pc.cyan(`\n${"=".repeat(60)}`)); - console.log(pc.cyan(`Results: ${succeeded} succeeded, ${failed} failed`)); - console.log(pc.cyan(`Time: ${duration.toFixed(1)}s`)); + console.log(pc.cyan(`Resetting baseline from ${repoUrl} (${branch}/${sourceSubdir})`)); console.log(pc.cyan(`${"=".repeat(60)}\n`)); - return failed === 0; -} - -async function main() { - const isWindows = platform() === "win32"; - const flavor = argv.values.flavor; - const name = argv.values.name; - const debug = argv.values.debug ?? false; - // Windows has slower file system operations and process spawning, - // so use fewer parallel jobs to avoid I/O contention and memory pressure - const defaultJobs = isWindows ? 10 : 30; - const jobs = argv.values.jobs ? parseInt(argv.values.jobs, 10) : defaultJobs; - - console.log(pc.cyan(`\nRegeneration config:`)); - console.log(pc.cyan(` Platform: ${isWindows ? "Windows" : "Unix"}`)); - console.log(pc.cyan(` Mode: in-process compilation`)); - console.log(pc.cyan(` Jobs: ${jobs}`)); - if (name) { - console.log(pc.cyan(` Filter: ${name}`)); - } - console.log(); - - const startTime = performance.now(); - let success: boolean; - - if (flavor) { - success = await regenerateFlavor(flavor, name, debug, jobs); - } else { - // Both flavors - const azureSuccess = await regenerateFlavor("azure", name, debug, jobs); - const unbrandedSuccess = await regenerateFlavor("unbranded", name, debug, jobs); - success = azureSuccess && unbrandedSuccess; + // Wipe tests/generated + if (existsSync(testsGeneratedDir)) { + console.log(pc.dim(`Removing ${testsGeneratedDir}`)); + rmSync(testsGeneratedDir, { recursive: true, force: true }); } - const totalDuration = (performance.now() - startTime) / 1000; - console.log( - success - ? pc.green(`\nRegeneration completed successfully in ${totalDuration.toFixed(1)}s`) - : pc.red(`\nRegeneration failed after ${totalDuration.toFixed(1)}s`), - ); + // Sparse-checkout the baseline folder into a temp directory + const tempDir = await mkdtemp(join(tmpdir(), "azsdk-baseline-")); + try { + console.log(pc.dim(`Cloning into ${tempDir}`)); + const run = (cmd: string) => + execSync(cmd, { cwd: tempDir, stdio: ["ignore", "ignore", "inherit"] }); + + run(`git init`); + run(`git config core.longpaths true`); + run(`git remote add origin ${repoUrl}`); + run(`git config core.sparseCheckout true`); + run(`git sparse-checkout init --cone`); + run(`git sparse-checkout set ${sourceSubdir}`); + run(`git fetch --depth 1 origin ${branch}`); + run(`git checkout FETCH_HEAD`); + + const sourceRoot = join(tempDir, ...sourceSubdir.split("/")); + for (const flavor of ["azure", "unbranded"]) { + const src = join(sourceRoot, flavor); + const dest = join(testsGeneratedDir, flavor); + if (!existsSync(src)) { + console.warn(pc.yellow(`Baseline folder not found: ${src}`)); + continue; + } + console.log(pc.dim(`Copying ${flavor}/ -> ${dest}`)); + await cp(src, dest, { recursive: true }); + } - process.exit(success ? 0 : 1); -} + console.log(pc.green(`Baseline reset complete.\n`)); + } finally { + rmSync(tempDir, { recursive: true, force: true }); + } -main().catch((err) => { - console.error(pc.red(`Fatal error: ${err}`)); - process.exit(1); -}); + // Smoke test the full-emit path: delete a couple of fully-generated package + // folders and every README.md so regeneration has to recreate them. + const deleteIfExists = (path: string) => { + if (!existsSync(path)) return; + console.log(pc.dim(`Deleting ${path}`)); + rmSync(path, { recursive: true, force: true }); + }; + + deleteIfExists(join(testsGeneratedDir, "azure", "authentication-http-custom")); + deleteIfExists(join(testsGeneratedDir, "unbranded", "encode-array")); + + if (existsSync(testsGeneratedDir)) { + const entries = await readdir(testsGeneratedDir, { recursive: true, withFileTypes: true }); + for (const entry of entries) { + if (entry.isFile() && entry.name === "README.md") { + deleteIfExists(join(entry.parentPath, entry.name)); + } + } + } +} diff --git a/packages/typespec-python/eng/scripts/ci/regenerate.ts b/packages/typespec-python/eng/scripts/ci/regenerate.ts new file mode 100644 index 0000000000..f35bc24a48 --- /dev/null +++ b/packages/typespec-python/eng/scripts/ci/regenerate.ts @@ -0,0 +1,195 @@ +/* eslint-disable no-console */ +/** + * Regenerates Python SDK code from TypeSpec definitions. + * + * Uses in-process TypeSpec compilation to avoid subprocess spawning overhead. + * Specs are compiled in parallel and the emitter writes the final `.py` files + * directly inside the same process — no YAML-only intermediate, no batched + * Python subprocess. This single-phase pipeline is faster for the wrapper + * use-case (azure flavor only, smaller spec set, Windows). + * + * Shared helpers/data live in `regenerate-common.ts` and are kept identical + * with the upstream `@typespec/http-client-python` copy via `pnpm sync`. + */ + +import { platform } from "os"; +import { dirname, resolve } from "path"; +import pc from "picocolors"; +import { fileURLToPath } from "url"; +import { parseArgs } from "util"; + +import { + buildTaskGroups, + getSubdirectories, + prepareBaselineOfGeneratedCode, + preprocess, + RegenerateContext, + RegenerateFlags, + runParallel, +} from "./regenerate-common.js"; + +const argv = parseArgs({ + args: process.argv.slice(2), + options: { + flavor: { type: "string", short: "f" }, + name: { type: "string", short: "n" }, + debug: { type: "boolean", short: "d" }, + pluginDir: { type: "string" }, + emitterName: { type: "string" }, + generatedFolder: { type: "string" }, + jobs: { type: "string", short: "j" }, + help: { type: "boolean", short: "h" }, + }, +}); + +if (argv.values.help) { + console.log(` +${pc.bold("Usage:")} tsx regenerate.ts [options] + +${pc.bold("Description:")} + Regenerates Python SDK code from TypeSpec definitions using in-process + compilation. This avoids spawning a new Node.js process for each spec, + making it significantly faster. + +${pc.bold("Options:")} + ${pc.cyan("-f, --flavor ")} + SDK flavor to regenerate. If not specified, regenerates both flavors. + + ${pc.cyan("-n, --name ")} + Filter packages by name pattern (case-insensitive substring match). + Examples: + --name xml Regenerate packages containing "xml" + --name authentication Regenerate authentication packages + --name type/array Regenerate the type/array package + + ${pc.cyan("-d, --debug")} + Enable debug output during regeneration. + + ${pc.cyan("-j, --jobs ")} + Number of parallel compilation tasks (default: 30 on Linux/Mac, 10 on Windows). + + ${pc.cyan("-h, --help")} + Show this help message. + +${pc.bold("Examples:")} + ${pc.dim("# Regenerate all packages for both flavors")} + tsx regenerate.ts + + ${pc.dim("# Regenerate only Azure packages")} + tsx regenerate.ts --flavor azure + + ${pc.dim("# Regenerate a specific package by name")} + tsx regenerate.ts --flavor azure --name authentication-api-key + + ${pc.dim("# Regenerate with more parallelism")} + tsx regenerate.ts --jobs 50 +`); + process.exit(0); +} + +// Resolve repo-specific paths. PLUGIN_DIR defaults to two levels above this +// file (eng/scripts/ci -> eng/scripts -> eng -> packageRoot). +const SCRIPT_DIR = dirname(fileURLToPath(import.meta.url)); +const PLUGIN_DIR = argv.values.pluginDir + ? resolve(argv.values.pluginDir) + : resolve(SCRIPT_DIR, "../../../"); +const AZURE_HTTP_SPECS = resolve(PLUGIN_DIR, "node_modules/@azure-tools/azure-http-specs/specs"); +const HTTP_SPECS = resolve(PLUGIN_DIR, "node_modules/@typespec/http-specs/specs"); +const GENERATED_FOLDER = argv.values.generatedFolder + ? resolve(argv.values.generatedFolder) + : resolve(PLUGIN_DIR, "generator"); +const EMITTER_NAME = argv.values.emitterName || "@azure-tools/typespec-python"; + +const ctx: RegenerateContext = { + pluginDir: PLUGIN_DIR, + azureHttpSpecs: AZURE_HTTP_SPECS, + httpSpecs: HTTP_SPECS, + generatedFolder: GENERATED_FOLDER, + emitterName: EMITTER_NAME, +}; + +async function regenerateFlavor( + flavor: string, + name: string | undefined, + debug: boolean, + jobs: number, +): Promise { + console.log(pc.cyan(`\n${"=".repeat(60)}`)); + console.log(pc.cyan(`Regenerating ${flavor} flavor`)); + console.log(pc.cyan(`${"=".repeat(60)}\n`)); + + const flags: RegenerateFlags = { flavor, debug, name }; + + await preprocess(flavor, GENERATED_FOLDER); + + const azureSpecs = flavor === "azure" ? await getSubdirectories(AZURE_HTTP_SPECS, flags) : []; + const standardSpecs = await getSubdirectories(HTTP_SPECS, flags); + const allSpecs = [...azureSpecs, ...standardSpecs]; + + const groups = buildTaskGroups(allSpecs, flags, ctx); + const totalTasks = groups.reduce((sum, g) => sum + g.tasks.length, 0); + + console.log(pc.cyan(`Found ${allSpecs.length} specs (${totalTasks} total tasks) to compile`)); + console.log(pc.cyan(`Using ${jobs} parallel jobs\n`)); + + const startTime = performance.now(); + const results = await runParallel(groups, jobs, ctx); + const duration = (performance.now() - startTime) / 1000; + + const succeeded = Array.from(results.values()).filter((v) => v).length; + const failed = results.size - succeeded; + + console.log(pc.cyan(`\n${"=".repeat(60)}`)); + console.log(pc.cyan(`Results: ${succeeded} succeeded, ${failed} failed`)); + console.log(pc.cyan(`Time: ${duration.toFixed(1)}s`)); + console.log(pc.cyan(`${"=".repeat(60)}\n`)); + + return failed === 0; +} + +async function main(): Promise { + const isWindows = platform() === "win32"; + const flavor = argv.values.flavor; + const name = argv.values.name; + const debug = argv.values.debug ?? false; + // Windows has slower file system operations and process spawning, so use + // fewer parallel jobs to avoid I/O contention and memory pressure. + const defaultJobs = isWindows ? 10 : 30; + const jobs = argv.values.jobs ? parseInt(argv.values.jobs, 10) : defaultJobs; + + console.log(pc.cyan(`\nRegeneration config:`)); + console.log(pc.cyan(` Platform: ${isWindows ? "Windows" : "Unix"}`)); + console.log(pc.cyan(` Mode: in-process compilation (single-phase)`)); + console.log(pc.cyan(` Jobs: ${jobs}`)); + if (name) { + console.log(pc.cyan(` Filter: ${name}`)); + } + console.log(); + + const startTime = performance.now(); + let success: boolean; + + await prepareBaselineOfGeneratedCode(GENERATED_FOLDER); + + if (flavor) { + success = await regenerateFlavor(flavor, name, debug, jobs); + } else { + const azureSuccess = await regenerateFlavor("azure", name, debug, jobs); + const unbrandedSuccess = await regenerateFlavor("unbranded", name, debug, jobs); + success = azureSuccess && unbrandedSuccess; + } + + const totalDuration = (performance.now() - startTime) / 1000; + console.log( + success + ? pc.green(`\nRegeneration completed successfully in ${totalDuration.toFixed(1)}s`) + : pc.red(`\nRegeneration failed after ${totalDuration.toFixed(1)}s`), + ); + + process.exit(success ? 0 : 1); +} + +main().catch((err) => { + console.error(pc.red(`\nUnexpected error: ${String(err)}`)); + process.exit(1); +}); diff --git a/packages/typespec-python/eng/scripts/ci/run_apiview.py b/packages/typespec-python/eng/scripts/ci/run_apiview.py index 5345f694ef..48d6f890a3 100644 --- a/packages/typespec-python/eng/scripts/ci/run_apiview.py +++ b/packages/typespec-python/eng/scripts/ci/run_apiview.py @@ -10,32 +10,38 @@ import os import sys -from subprocess import check_call, CalledProcessError +from subprocess import run, TimeoutExpired import logging from util import run_check logging.getLogger().setLevel(logging.INFO) +# Timeout for each apiview generation (seconds) +APIVIEW_TIMEOUT = 30 + def _single_dir_apiview(mod): - loop = 0 - while True: + for attempt in range(2): try: - check_call( - [ - "apistubgen", - "--pkg-path", - str(mod.absolute()), - ] + result = run( + ["apistubgen", "--pkg-path", str(mod.absolute())], + capture_output=True, + timeout=APIVIEW_TIMEOUT, ) - except CalledProcessError as e: - if loop >= 2: # retry for maximum 3 times because sometimes the apistubgen has transient failure. - logging.error("{} exited with apiview generation error {}".format(mod.stem, e.returncode)) + if result.returncode == 0: + return True + if attempt == 1: + logging.error(f"{mod.stem} failed: {result.stderr.decode()[:200]}") + return False + except TimeoutExpired: + if attempt == 1: + logging.error(f"{mod.stem} timed out after {APIVIEW_TIMEOUT}s") + return False + except Exception as e: + if attempt == 1: + logging.error(f"{mod.stem} error: {e}") return False - else: - loop += 1 - continue - return True + return False if __name__ == "__main__": diff --git a/packages/typespec-python/eng/scripts/ci/run_mypy.py b/packages/typespec-python/eng/scripts/ci/run_mypy.py index c452ea0e54..ef99a32638 100644 --- a/packages/typespec-python/eng/scripts/ci/run_mypy.py +++ b/packages/typespec-python/eng/scripts/ci/run_mypy.py @@ -12,7 +12,7 @@ import os import logging import sys -from util import run_check +from util import run_check, get_package_namespace_dir logging.getLogger().setLevel(logging.INFO) @@ -26,40 +26,34 @@ def get_config_file_location(): return os.path.join(os.path.dirname(__file__), "config/mypy.ini") -def _has_python_files(directory): - """Check if a directory contains any .py files recursively.""" - return any(directory.rglob("*.py")) - - -def _single_dir_mypy(mod): - try: - inner_class = next( - (d for d in mod.iterdir() if d.is_dir() and d.name not in ("build", "generated_tests", "specs", "generated_samples") and not str(d).endswith("egg-info") and _has_python_files(d)), - None - ) - if inner_class is None: - logging.warning("No valid source directory found in %s, skipping", mod) - return True - check_call( - [ - sys.executable, - "-m", - "mypy", - "--config-file", - get_config_file_location(), - "--ignore-missing", - "--exclude", - "build", - str(inner_class.absolute()), - ] - ) +def _single_dir_mypy(mod, retries=2): + inner_class = get_package_namespace_dir(mod) + if not inner_class: + logging.info(f"No package directory found in {mod}, skipping") return True - except CalledProcessError as e: - logging.error("{} exited with mypy error {}".format(mod.stem, e.returncode)) - return False - except Exception as e: - logging.error("Unexpected error processing %s: %s", mod, e) - return False + for attempt in range(1, retries + 2): + try: + check_call( + [ + sys.executable, + "-m", + "mypy", + "--config-file", + get_config_file_location(), + "--ignore-missing", + str(inner_class.absolute()), + ] + ) + return True + except CalledProcessError as e: + if attempt <= retries: + logging.warning( + "{} mypy attempt {} failed (exit {}), retrying...".format(inner_class.stem, attempt, e.returncode) + ) + else: + logging.error("{} exited with mypy error {}".format(inner_class.stem, e.returncode)) + return False + return False if __name__ == "__main__": diff --git a/packages/typespec-python/eng/scripts/ci/run_pylint.py b/packages/typespec-python/eng/scripts/ci/run_pylint.py index 3e8bbf220e..c184c56a3d 100644 --- a/packages/typespec-python/eng/scripts/ci/run_pylint.py +++ b/packages/typespec-python/eng/scripts/ci/run_pylint.py @@ -12,7 +12,7 @@ import os import logging import sys -from util import run_check +from util import run_check, get_package_namespace_dir logging.getLogger().setLevel(logging.INFO) @@ -26,41 +26,33 @@ def get_rfc_file_location(): return os.path.join(os.path.dirname(__file__), "config/pylintrc") -def _has_python_files(directory): - """Check if a directory contains any .py files recursively.""" - return any(directory.rglob("*.py")) - - def _single_dir_pylint(mod): + inner_class = get_package_namespace_dir(mod) + if not inner_class: + logging.info(f"No package directory found in {mod}, skipping") + return True + # Only load the Azure pylint guidelines checker plugin for azure packages. + # The plugin (azure-pylint-guidelines-checker) is only installed in the + # lint-azure tox environment and is not available for unbranded packages. + is_azure = "azure" in mod.parts + pylint_args = [ + sys.executable, + "-m", + "pylint", + "--rcfile={}".format(get_rfc_file_location()), + "--evaluation=(max(0, 0 if fatal else 10.0 - ((float(5 * error + warning + refactor + convention + info)/ statement) * 10)))", + "--output-format=parseable", + "--recursive=y", + "--py-version=3.10", + ] + if is_azure: + pylint_args.append("--load-plugins=pylint_guidelines_checker") + pylint_args.append(str(inner_class.absolute())) try: - inner_class = next( - (d for d in mod.iterdir() if d.is_dir() and d.name not in ("build", "generated_tests", "specs", "generated_samples") and not str(d).endswith("egg-info") and _has_python_files(d)), - None - ) - if inner_class is None: - logging.warning("No valid source directory found in %s, skipping", mod) - return True - check_call( - [ - sys.executable, - "-m", - "pylint", - "--rcfile={}".format(get_rfc_file_location()), - "--evaluation=(max(0, 0 if fatal else 10.0 - ((float(5 * error + warning + refactor + convention + info)/ statement) * 10)))", - "--load-plugins=pylint_guidelines_checker", - "--output-format=parseable", - "--recursive=y", - "--ignore=build", - "--py-version=3.9", - str(inner_class.absolute()), - ] - ) + check_call(pylint_args) return True except CalledProcessError as e: - logging.error("{} exited with linting error {}".format(mod.stem, e.returncode)) - return False - except Exception as e: - logging.error("Unexpected error processing %s: %s", mod, e) + logging.error("{} exited with linting error {}".format(str(inner_class.absolute()), e.returncode)) return False diff --git a/packages/typespec-python/eng/scripts/ci/run_pyright.py b/packages/typespec-python/eng/scripts/ci/run_pyright.py index fef8c468ea..b998b515d2 100644 --- a/packages/typespec-python/eng/scripts/ci/run_pyright.py +++ b/packages/typespec-python/eng/scripts/ci/run_pyright.py @@ -13,7 +13,7 @@ import logging import sys import time -from util import run_check +from util import run_check, get_package_namespace_dir logging.getLogger().setLevel(logging.INFO) @@ -27,51 +27,39 @@ def get_pyright_config_file_location(): return os.path.join(os.path.dirname(__file__), "config/pyrightconfig.json") -def _has_python_files(directory): - """Check if a directory contains any .py files recursively.""" - return any(directory.rglob("*.py")) - - def _single_dir_pyright(mod): - try: - inner_class = next( - (d for d in mod.iterdir() if d.is_dir() and d.name not in ("build", "generated_tests", "specs", "generated_samples") and not str(d).endswith("egg-info") and _has_python_files(d)), - None - ) - if inner_class is None: - logging.warning("No valid source directory found in %s, skipping", mod) - return True - retries = 3 - while retries: - try: - # After fully support client hierarchy, we can remove this check - if "azure-client-generator-core-client-initialization" in str(inner_class.absolute()): - return True - - check_output( - [ - sys.executable, - "-m", - "pyright", - "-p", - get_pyright_config_file_location(), - str(inner_class.absolute()), - ], - text=True, - ) + inner_class = get_package_namespace_dir(mod) + if not inner_class: + logging.info(f"No package directory found in {mod}, skipping") + return True + retries = 3 + while retries: + try: + # After fully support client hierarchy, we can remove this check + if "azure-client-generator-core-client-initialization" in str(inner_class.absolute()): return True - except CalledProcessError as e: - logging.exception("{} exited with pyright error {}".format(inner_class.stem, e.returncode)) - logging.error(f"PyRight stdout:\n{e.stdout}\n===========") - logging.error(f"PyRight stderr:\n{e.stderr}\n===========") - # PyRight has shown to randomly failed with a 217, retry the same folder 3 times should help - retries -= 1 - time.sleep(5) - return False - except Exception as e: - logging.error("Unexpected error processing %s: %s", mod, e) - return False + check_output( + [ + sys.executable, + "-m", + "pyright", + "-p", + get_pyright_config_file_location(), + str(inner_class.absolute()), + ], + text=True, + ) + return True + except CalledProcessError as e: + logging.exception("{} exited with pyright error {}".format(inner_class.stem, e.returncode)) + logging.error(f"PyRight stdout:\n{e.stdout}\n===========") + logging.error(f"PyRight stderr:\n{e.stderr}\n===========") + # PyRight has shown to randomly failed with a 217, retry the same folder 3 times should help + retries -= 1 + time.sleep(5) + + return False if __name__ == "__main__": diff --git a/packages/typespec-python/eng/scripts/ci/run_sphinx_build.py b/packages/typespec-python/eng/scripts/ci/run_sphinx_build.py index e97476aed9..0dba25b8f6 100644 --- a/packages/typespec-python/eng/scripts/ci/run_sphinx_build.py +++ b/packages/typespec-python/eng/scripts/ci/run_sphinx_build.py @@ -8,18 +8,21 @@ # This script is used to execute sphinx documentation build within a tox environment. # It uses a central sphinx configuration and validates docstrings by running sphinx-build. -from subprocess import check_call, CalledProcessError +from subprocess import run, TimeoutExpired import os import logging import sys from pathlib import Path -from util import run_check +from util import run_check, SKIP_PACKAGE_DIRS logging.getLogger().setLevel(logging.INFO) # Get the central Sphinx config directory SPHINX_CONF_DIR = os.path.abspath(os.path.dirname(__file__)) +# Timeout for each sphinx build (seconds) +SPHINX_TIMEOUT = 120 + def _create_minimal_index_rst(docs_dir, package_name, module_names): """Create a minimal index.rst file for sphinx to process.""" @@ -50,7 +53,12 @@ def _single_dir_sphinx(mod): # Find the actual Python package directories package_dirs = [ - d for d in mod.iterdir() if d.is_dir() and not d.name.startswith("_") and (d / "__init__.py").exists() + d + for d in mod.iterdir() + if d.is_dir() + and not d.name.startswith("_") + and d.name not in SKIP_PACKAGE_DIRS + and (d / "__init__.py").exists() ] if not package_dirs: @@ -85,7 +93,7 @@ def _single_dir_sphinx(mod): sys.path.insert(0, str(mod.absolute())) try: - result = check_call( + result = run( [ sys.executable, "-m", @@ -100,12 +108,19 @@ def _single_dir_sphinx(mod): "-q", # Quiet mode (only show warnings/errors) str(docs_dir.absolute()), # Source directory str(output_dir.absolute()), # Output directory - ] + ], + capture_output=True, + timeout=SPHINX_TIMEOUT, ) - logging.info(f"Sphinx build completed successfully for {mod.stem}") - return True - except CalledProcessError as e: - logging.error(f"{mod.stem} exited with sphinx build error {e.returncode}") + if result.returncode == 0: + return True + logging.error(f"{mod.stem} sphinx error: {result.stderr.decode()[:500]}") + return False + except TimeoutExpired: + logging.error(f"{mod.stem} timed out after {SPHINX_TIMEOUT}s") + return False + except Exception as e: + logging.error(f"{mod.stem} sphinx error: {e}") return False finally: # Remove from sys.path diff --git a/packages/typespec-python/eng/scripts/ci/util.py b/packages/typespec-python/eng/scripts/ci/util.py index 80fa1a3408..07aba0f3f4 100644 --- a/packages/typespec-python/eng/scripts/ci/util.py +++ b/packages/typespec-python/eng/scripts/ci/util.py @@ -8,15 +8,32 @@ import logging from pathlib import Path import argparse -from multiprocessing import Pool +from concurrent.futures import ProcessPoolExecutor, as_completed logging.getLogger().setLevel(logging.INFO) -# Root folder is the typespec-python package root -ROOT_FOLDER = os.path.abspath(os.path.join(os.path.abspath(__file__), "..", "..", "..", "..")) +# Root is the tests directory (4 levels up from this file: ci -> scripts -> eng -> package_root, then into tests) +ROOT_FOLDER = os.path.abspath(os.path.join(os.path.abspath(__file__), "..", "..", "..", "..", "tests")) IGNORE_FOLDER = [] +# Directories inside each generated package that should be skipped by all CI checks. +# These are auto-generated test/sample scaffolding, not the actual SDK code. +SKIP_PACKAGE_DIRS = {"generated_tests", "generated_samples", "build", "__pycache__", ".pytest_cache"} + + +def get_package_namespace_dir(mod): + """Find the actual namespace directory inside a generated package, skipping non-SDK dirs.""" + for d in mod.iterdir(): + if ( + d.is_dir() + and not d.name.startswith("_") + and not d.name.endswith("egg-info") + and d.name not in SKIP_PACKAGE_DIRS + ): + return d + return None + def run_check(name, call_back, log_info): parser = argparse.ArgumentParser( @@ -29,13 +46,6 @@ def run_check(name, call_back, log_info): help="The test folder we're in. Can be 'azure' or 'unbranded'", required=True, ) - parser.add_argument( - "-g", - "--generator", - dest="generator", - help="The generator we're using. Optional.", - required=False, - ) parser.add_argument( "-f", "--file-name", @@ -47,26 +57,52 @@ def run_check(name, call_back, log_info): "-s", "--subfolder", dest="subfolder", - help="The specific sub folder to validate, default to 'generated'. Optional.", + help="The subfolder containing generated code, default to 'generated'.", required=False, default="generated", ) + parser.add_argument( + "-j", + "--jobs", + dest="jobs", + help="Number of parallel jobs (default: CPU count)", + type=int, + required=False, + default=max(1, os.cpu_count()), + ) args = parser.parse_args() - # Build path: tests/generated/{flavor}/ - pkg_dir = Path(ROOT_FOLDER) / Path("tests") / Path(args.subfolder) / Path(args.test_folder) - if args.generator: - pkg_dir /= Path(args.generator) + # Path structure: tests/generated/{test_folder}/ + pkg_dir = Path(ROOT_FOLDER) / Path(args.subfolder) / Path(args.test_folder) dirs = [d for d in pkg_dir.iterdir() if d.is_dir() and not d.stem.startswith("_") and d.stem not in IGNORE_FOLDER] if args.file_name: dirs = [d for d in dirs if args.file_name.lower() in d.stem.lower()] - if len(dirs) > 1: - with Pool() as pool: - result = pool.map(call_back, dirs) - response = all(result) - else: - response = call_back(dirs[0]) - if not response: - logging.error("%s fails", log_info) + + if not dirs: + logging.info("No directories to process") + return + + logging.info(f"Processing {len(dirs)} packages with {args.jobs} parallel jobs...") + + failed = [] + succeeded = 0 + + with ProcessPoolExecutor(max_workers=args.jobs) as executor: + futures = {executor.submit(call_back, d): d for d in dirs} + for future in as_completed(futures): + pkg = futures[future] + try: + if future.result(): + succeeded += 1 + else: + failed.append(pkg.stem) + except Exception as e: + logging.error(f"{pkg.stem} raised exception: {e}") + failed.append(pkg.stem) + + logging.info(f"{log_info}: {succeeded} succeeded, {len(failed)} failed") + + if failed: + logging.error(f"{log_info} failed for: {', '.join(failed)}") exit(1) diff --git a/packages/typespec-python/eng/scripts/setup/venvtools.py b/packages/typespec-python/eng/scripts/setup/venvtools.py index f15abdd525..c733957dc9 100644 --- a/packages/typespec-python/eng/scripts/setup/venvtools.py +++ b/packages/typespec-python/eng/scripts/setup/venvtools.py @@ -8,7 +8,6 @@ import sys from pathlib import Path - _ROOT_DIR = Path(__file__).parent.parent diff --git a/packages/typespec-python/eng/scripts/sync.ts b/packages/typespec-python/eng/scripts/sync.ts new file mode 100644 index 0000000000..ee9bbfba4a --- /dev/null +++ b/packages/typespec-python/eng/scripts/sync.ts @@ -0,0 +1,417 @@ +/* eslint-disable no-console */ +/** + * Sync files from the upstream http-client-python checkout (sibling `core/` + * repo) into this package. + * + * typespec-python is mostly a wrapper around http-client-python, so most of + * the build / setup / CI helper scripts and shared test assets should be kept + * identical between the two packages. This script copies the files (and + * directories) listed in INCLUDES from + * /core/packages/http-client-python/ + * into + * / + * so we don't have to maintain duplicate copies by hand. If the core/ checkout + * isn't present the script exits with an informational message and does + * nothing. + * + * Usage: + * tsx eng/scripts/sync.ts # write mode: overwrite local files + * tsx eng/scripts/sync.ts --check # check mode: exit non-zero on drift (CI) + */ +import fs from "fs"; +import { dirname, join, relative, sep } from "path"; +import pc from "picocolors"; +import { fileURLToPath } from "url"; +import { parseArgs } from "util"; + +const here = dirname(fileURLToPath(import.meta.url)); +// eng/scripts/sync.ts -> package root is two levels up +const packageRoot = join(here, "..", ".."); + +// Source lives in the sibling `core/` checkout at the repo root: +// /core/packages/http-client-python +// packageRoot == /packages/typespec-python, so the repo root is two +// levels up from packageRoot. +const repoRoot = join(packageRoot, "..", ".."); +const sourceRoot = join(repoRoot, "core", "packages", "http-client-python"); + +/** + * Paths (POSIX, relative to the package root on both sides) that should be + * synced from the upstream @typespec/http-client-python package. + * + * - Entries ending in `/` are directories — every file underneath is copied + * recursively (additive: local-only files in the directory are NOT removed). + * - Other entries are individual files. + * + * Anything not listed here is ignored — keeping this an allow-list makes it + * explicit which files this package is willing to inherit from upstream. + * + * To pull in a new upstream file, add it here and run `pnpm sync`. + */ +const INCLUDES: readonly string[] = [ + // Shared CI config + "eng/scripts/ci/config/mypy.ini", + "eng/scripts/ci/config/pylintrc", + "eng/scripts/ci/config/pyrightconfig.json", + + // Shared CI runners (invoked by run-tests.ts via tox) + "eng/scripts/ci/run_apiview.py", + "eng/scripts/ci/run_mypy.py", + "eng/scripts/ci/run_pylint.py", + "eng/scripts/ci/run_pyright.py", + "eng/scripts/ci/run_sphinx_build.py", + "eng/scripts/ci/util.py", + + // Shared regenerate helpers/data tables. Per-repo orchestration (paths, + // emitter name, single-phase vs two-phase pipeline, argv/help) lives in + // each repo's own `regenerate.ts` and is intentionally *not* synced. + "eng/scripts/ci/regenerate-common.ts", + + // NOTE: eng/scripts/setup/* is intentionally NOT synced. Those scripts + // (install.py, prepare.py, venvtools.py, etc.) diverged from upstream's + // http-client-python build/test pipeline; the typespec-python wrapper + // owns its own copies. + // + // Likewise NOT synced (committed and owned by this wrapper): + // - generator/ : pygen lives upstream as the source of + // truth, but the wrapper checks in a + // pinned snapshot rather than re-fetching + // on every CI run. + // - tests/tox.ini : tox envs differ from upstream (paths, + // emitter name, extra envs). + // - tests/install_packages.py : upstream's version calls `uv pip wheel`, + // which is not a real uv subcommand, so + // it fails in CI. The wrapper keeps the + // working `uv build --wheel` flow. Once + // the upstream script is fixed (see + // microsoft/typespec#10636) it can be + // re-added to this list. + + // Shared test assets. Directory entries (trailing `/`) are recursive and + // **mirror** the upstream layout: files matching upstream are overwritten, + // and local-only files are deleted. `tests/data/` and `tests/mock_api/` + // are gitignored so a fresh CI checkout starts empty and is populated by + // `pnpm sync`; `tests/requirements/` is small and stable enough to commit + // as a snapshot that `pnpm sync` refreshes in place. Upstream tests are + // treated as the source of truth; any wrapper-specific test must live + // OUTSIDE these directories (e.g. tests/wrapper/) so it isn't pruned by + // the sync. + "tests/data/", + "tests/mock_api/", + "tests/requirements/", + + // Shared test driver/helper file. Treated as the upstream source of truth + // (DATA_FOLDER, server-launch logic, etc.) and kept aligned with + // http-client-python. + "tests/conftest.py", +]; + +/** + * Directory/file names to skip when walking INCLUDES directory entries. Any + * path that has a segment matching one of these names is ignored on both + * sides: not copied from source, and not pruned in dest. This is how we keep + * build artifacts (egg-info, __pycache__, build/, dist/) from polluting the + * sync. + */ +const EXCLUDED_SEGMENTS: ReadonlySet = new Set([ + "__pycache__", + "build", + "dist", + "pygen.egg-info", + ".pytest_cache", + ".mypy_cache", + ".tox", + ".wheels", +]); + +/** + * Specific file paths (POSIX, relative to the package root) that must NOT be + * present in the destination after a sync, even if upstream has them. They are + * skipped during the copy phase and actively deleted from the destination if + * they exist. In --check mode their presence is reported as drift. + * + * Use this for individual upstream files that don't apply to the wrapper but + * live inside an otherwise-mirrored directory. Whole directories should be + * handled via EXCLUDED_SEGMENTS instead. + */ +const EXCLUDED_FILES: ReadonlySet = new Set([ + // Unbranded-only mock test that doesn't apply to the Azure wrapper. + "tests/mock_api/unbranded/test_unbranded.py", + "tests/mock_api/unbranded/asynctests/test_unbranded_async.py", +]); + +const argv = parseArgs({ + args: process.argv.slice(2), + options: { + check: { type: "boolean", short: "c", default: false }, + help: { type: "boolean", short: "h", default: false }, + }, +}); + +if (argv.values.help) { + console.log(` +${pc.bold("Usage:")} tsx eng/scripts/sync.ts [options] + +${pc.bold("Description:")} + Copy the files (and recursive directories) listed in INCLUDES from + /core/packages/http-client-python/ into this package, preserving + the same path on both sides. Anything not in INCLUDES is left untouched. + Directory entries are additive — local-only files are not deleted. If the + core/ checkout is not present, the script exits with an informational + message and does nothing. + +${pc.bold("Options:")} + ${pc.cyan("-c, --check")} Compare only; exit non-zero on any drift (for CI). + ${pc.cyan("-h, --help")} Show this help. +`); + process.exit(0); +} + +const check = argv.values.check ?? false; + +function toPosix(p: string): string { + return p.split(sep).join("/"); +} + +function readBytes(p: string): Buffer | null { + try { + return fs.readFileSync(p); + } catch { + return null; + } +} + +interface SyncStats { + copied: string[]; + unchanged: string[]; + drifted: string[]; // only populated in --check mode + missing: string[]; // listed in INCLUDES but not present in the source package + removed: string[]; // local-only files inside a synced directory (mirror) +} + +/** + * Merge upstream dependencies into this package's top-level + * dev_requirements.txt + * + * This is NOT a full copy. The wrapper file is split on the marker line + * `# additional dependency needed for development` + * Everything BEFORE the marker is replaced verbatim with the contents of + * /eng/scripts/ci/dev_requirements.txt + * Everything from the marker onwards (wrapper-specific deps such as + * `setuptools`) is preserved untouched. If the wrapper file lacks the + * marker, the upstream content simply replaces the whole file. + */ +function syncDevRequirements(srcAbs: string, destAbs: string, stats: SyncStats): void { + const relPath = "dev_requirements.txt"; + + if (!fs.existsSync(srcAbs)) { + stats.missing.push("eng/scripts/ci/dev_requirements.txt"); + return; + } + if (!fs.existsSync(destAbs)) { + stats.missing.push(relPath); + return; + } + + const srcText = fs.readFileSync(srcAbs, "utf8"); + const destText = fs.readFileSync(destAbs, "utf8"); + + // Preserve any leading comment block ("title") of the wrapper file -- the + // run of `#`-prefixed lines at the very top, before the first non-comment + // line. + const destLines = destText.split(/\r?\n/); + const titleLines: string[] = []; + for (const line of destLines) { + if (line.trim().startsWith("#")) titleLines.push(line); + else break; + } + const title = titleLines.length ? titleLines.join("\n") + "\n" : ""; + + const MARKER_RE = /^#\s*additional dependency/im; + const match = MARKER_RE.exec(destText); + // If marker is missing, the upstream content (with title) becomes the whole + // file. Otherwise, keep the marker line and everything after it as-is, with + // a single blank line between the upstream block and the wrapper-only tail. + const tail = match ? destText.slice(match.index) : ""; + const upstream = srcText.endsWith("\n") ? srcText : srcText + "\n"; + const newText = tail ? title + upstream + "\n" + tail : title + upstream; + + if (newText === destText) { + stats.unchanged.push(relPath); + return; + } + if (check) { + stats.drifted.push(relPath); + return; + } + fs.writeFileSync(destAbs, newText, "utf8"); + stats.copied.push(relPath); +} + +function syncFile(srcAbs: string, destAbs: string, relPath: string, stats: SyncStats): void { + const srcBuf = fs.readFileSync(srcAbs); + const destBuf = readBytes(destAbs); + + if (destBuf && destBuf.equals(srcBuf)) { + stats.unchanged.push(relPath); + return; + } + + if (check) { + stats.drifted.push(relPath); + return; + } + + fs.mkdirSync(dirname(destAbs), { recursive: true }); + fs.writeFileSync(destAbs, srcBuf); + stats.copied.push(relPath); +} + +function listFilesRecursive(dir: string): string[] { + if (!fs.existsSync(dir)) return []; + const out: string[] = []; + const stack: string[] = [dir]; + while (stack.length) { + const cur = stack.pop()!; + for (const dirent of fs.readdirSync(cur, { withFileTypes: true })) { + if (EXCLUDED_SEGMENTS.has(dirent.name)) continue; + const child = join(cur, dirent.name); + if (dirent.isDirectory()) { + stack.push(child); + } else if (dirent.isFile()) { + out.push(child); + } + } + } + return out; +} + +function removeEmptyDirsUpTo(startDir: string, stopDir: string): void { + let cur = startDir; + while (cur !== stopDir) { + const rel = relative(stopDir, cur); + // Bail if we've walked outside stopDir (rel starts with "..") or reached it (rel === ""). + if (rel === "" || rel.startsWith("..")) return; + try { + if (fs.readdirSync(cur).length === 0) { + fs.rmdirSync(cur); + } else { + return; + } + } catch { + return; + } + cur = dirname(cur); + } +} + +function main(): void { + if (!fs.existsSync(sourceRoot)) { + console.log( + pc.yellow( + `core/ checkout not found at:\n ${sourceRoot}\n` + + `Skipping sync. Clone the typespec repo into /core (or run\n` + + `the appropriate submodule init command) and re-run this script.`, + ), + ); + process.exit(0); + } + + console.log(pc.bold("Source:") + " " + sourceRoot); + console.log(pc.bold("Destination:") + " " + packageRoot); + console.log(pc.bold("Mode:") + " " + (check ? "check (read-only)" : "write")); + console.log(""); + + const stats: SyncStats = { copied: [], unchanged: [], drifted: [], missing: [], removed: [] }; + + for (const entry of INCLUDES) { + const isDir = entry.endsWith("/"); + const rel = isDir ? entry.slice(0, -1) : entry; + const srcAbs = join(sourceRoot, ...rel.split("/")); + + if (!fs.existsSync(srcAbs)) { + stats.missing.push(entry); + continue; + } + + if (isDir) { + // Mirror the source directory: copy everything underneath, AND delete + // any local-only files so the destination becomes a faithful copy of + // the source. These directories are .gitignored, so deletions never + // touch tracked files. + const destDirAbs = join(packageRoot, ...rel.split("/")); + const sourceFiles = listFilesRecursive(srcAbs); + const sourceRelSet = new Set(sourceFiles.map((f) => toPosix(relative(srcAbs, f)))); + + // Step 1: copy source -> dest (skipping excluded files) + for (const srcFile of sourceFiles) { + const relFromDir = toPosix(relative(srcAbs, srcFile)); + const destAbs = join(destDirAbs, ...relFromDir.split("/")); + const relFromPkg = toPosix(relative(packageRoot, destAbs)); + if (EXCLUDED_FILES.has(relFromPkg)) continue; + syncFile(srcFile, destAbs, relFromPkg, stats); + } + + // Step 2: prune local-only files (and any explicitly excluded files + // that may still be sitting in the destination from a previous sync). + const destFiles = listFilesRecursive(destDirAbs); + for (const destFile of destFiles) { + const relFromDir = toPosix(relative(destDirAbs, destFile)); + const relFromPkg = toPosix(relative(packageRoot, destFile)); + const isExcluded = EXCLUDED_FILES.has(relFromPkg); + if (sourceRelSet.has(relFromDir) && !isExcluded) continue; + if (check) { + stats.drifted.push(relFromPkg + (isExcluded ? " (excluded)" : " (local-only)")); + } else { + fs.unlinkSync(destFile); + removeEmptyDirsUpTo(dirname(destFile), destDirAbs); + stats.removed.push(relFromPkg); + } + } + } else { + const destAbs = join(packageRoot, ...rel.split("/")); + syncFile(srcAbs, destAbs, toPosix(rel), stats); + } + } + + // Special-case merge (NOT a full copy): pull dependency lines from upstream's + // eng/scripts/ci/dev_requirements.txt into this package's top-level + // dev_requirements.txt while preserving wrapper-only deps and comments. + syncDevRequirements( + join(sourceRoot, "eng", "scripts", "ci", "dev_requirements.txt"), + join(packageRoot, "dev_requirements.txt"), + stats, + ); + + if (stats.copied.length) { + console.log(pc.green(pc.bold(`Copied (${stats.copied.length}):`))); + for (const f of stats.copied) console.log(" " + f); + } + if (stats.removed.length) { + console.log(pc.magenta(pc.bold(`Removed local-only (${stats.removed.length}):`))); + for (const f of stats.removed) console.log(" " + f); + } + if (stats.drifted.length) { + console.log(pc.red(pc.bold(`Drifted (${stats.drifted.length}):`))); + for (const f of stats.drifted) console.log(" " + f); + } + if (stats.missing.length) { + console.log(pc.yellow(pc.bold(`Missing in source (${stats.missing.length}):`))); + for (const f of stats.missing) console.log(" " + f); + } + console.log(pc.dim(`Unchanged: ${stats.unchanged.length}`)); + + if (check && (stats.drifted.length > 0 || stats.missing.length > 0)) { + console.error( + pc.red( + `\nSynced files have drifted from core/packages/http-client-python.\n` + + `Run 'pnpm sync' (or 'tsx eng/scripts/sync.ts') and commit the result.`, + ), + ); + process.exit(1); + } + + console.log(pc.green(pc.bold(check ? "\nNo drift detected." : "\nSync complete."))); +} + +main(); diff --git a/packages/typespec-python/package.json b/packages/typespec-python/package.json index b157cea6ab..1ffe5480bb 100644 --- a/packages/typespec-python/package.json +++ b/packages/typespec-python/package.json @@ -37,15 +37,16 @@ "lint:extra": "tsx ./eng/scripts/ci/lint.ts", "format:extra": "tsx ./eng/scripts/ci/format.ts --python", "format:extra:check": "tsx ./eng/scripts/ci/format.ts --python --check", - "regenerate": "tsx ./eng/scripts/regenerate.ts", + "regenerate": "tsx ./eng/scripts/ci/regenerate.ts", + "sync": "tsx ./eng/scripts/sync.ts", + "sync:check": "tsx ./eng/scripts/sync.ts --check", "test:python:e2e": "tsx ./eng/scripts/ci/run-tests.ts", "regen-docs": "pnpm run build && tspd doc . --enable-experimental --output-dir ./website/src/content/docs/docs/emitters/clients/typespec-python/reference --skip-js" }, "files": [ "dist/**", "!dist/tests/**", - "eng/scripts/setup/**", - "generator/**" + "eng/scripts/setup/**" ], "peerDependencies": { "@azure-tools/typespec-autorest": "workspace:^", diff --git a/packages/typespec-python/tests/conftest.py b/packages/typespec-python/tests/conftest.py index 2779f7b673..a0badda9ef 100644 --- a/packages/typespec-python/tests/conftest.py +++ b/packages/typespec-python/tests/conftest.py @@ -15,9 +15,9 @@ from pathlib import Path from filelock import FileLock -# Root of the typespec-python package +# Root of the http-client-python package ROOT = Path(__file__).parent.parent -DATA_FOLDER = Path(__file__).parent / "data" +DATA_FOLDER = Path(__file__).parent / "mock_api" / "shared" # Server configuration SERVER_HOST = "localhost" @@ -25,12 +25,8 @@ SERVER_URL = f"http://{SERVER_HOST}:{SERVER_PORT}" # Lock file for coordinating server startup across xdist workers -LOCK_FILE = Path(tempfile.gettempdir()) / "typespec_python_test_server.lock" -PID_FILE = Path(tempfile.gettempdir()) / "typespec_python_test_server.pid" - -# Global server process reference (used by hooks) -_server_process = None -_owns_server = False # Track if this process started the server +LOCK_FILE = Path(tempfile.gettempdir()) / "http_client_python_test_server.lock" +PID_FILE = Path(tempfile.gettempdir()) / "http_client_python_test_server.pid" def wait_for_server(url: str, timeout: int = 60, interval: float = 0.5) -> bool: @@ -50,23 +46,20 @@ def wait_for_server(url: str, timeout: int = 60, interval: float = 0.5) -> bool: def start_server_process(): - """Start the tsp-spector mock API server.""" + """Start the tsp-spector mock API server. + + Always serves both azure-http-specs and http-specs regardless of flavor. + This allows azure and unbranded tests to run in parallel using the same server. + """ azure_http_path = ROOT / "node_modules/@azure-tools/azure-http-specs" http_path = ROOT / "node_modules/@typespec/http-specs" - # Determine flavor from environment or current directory - flavor = os.environ.get("FLAVOR", "azure") - + # Always serve both spec sets so azure and unbranded tests can run in parallel # Use absolute paths with forward slashes (works on all platforms including Windows) - if flavor == "unbranded": - cwd = http_path.resolve() - specs_path = str(cwd / "specs").replace("\\", "/") - cmd = f"npx tsp-spector serve {specs_path}" - else: - cwd = azure_http_path.resolve() - azure_specs = str(cwd / "specs").replace("\\", "/") - http_specs = str((http_path / "specs").resolve()).replace("\\", "/") - cmd = f"npx tsp-spector serve {azure_specs} {http_specs}" + cwd = azure_http_path.resolve() + azure_specs = str(cwd / "specs").replace("\\", "/") + http_specs = str((http_path / "specs").resolve()).replace("\\", "/") + cmd = f"npx tsp-spector serve {azure_specs} {http_specs}" # Add node_modules/.bin to PATH env = os.environ.copy() @@ -103,91 +96,35 @@ def terminate_server_process(process): pass -def pytest_configure(config): - """Start the mock server before any tests run. - - Uses file locking to ensure only one process starts the server, - even when running with pytest-xdist. The controller process starts - the server and workers wait for it to be ready. - """ - global _server_process, _owns_server - - # Check if server is already running (e.g., from a previous run or external process) - if wait_for_server(SERVER_URL, timeout=1, interval=0.1): - print(f"Mock API server already running at {SERVER_URL}") - return - - # Use file lock to ensure only one process starts the server - # This handles both xdist workers and multiple test runs - lock = FileLock(str(LOCK_FILE), timeout=120) - - try: - with lock: - # Double-check after acquiring lock (another process may have started it) - if wait_for_server(SERVER_URL, timeout=1, interval=0.1): - print(f"Mock API server already running at {SERVER_URL}") - return - - # We're the first process - start the server - print(f"Starting mock API server...") - _server_process = start_server_process() - _owns_server = True - - # Check if process started successfully - if _server_process.poll() is not None: - pytest.exit(f"Mock API server process exited immediately with code {_server_process.returncode}") - - # Write PID file so other processes know who owns the server - PID_FILE.write_text(str(_server_process.pid)) - - # Wait for server to be ready - if not wait_for_server(SERVER_URL, timeout=60): - if _server_process.poll() is not None: - pytest.exit(f"Mock API server process died with code {_server_process.returncode}") - terminate_server_process(_server_process) - _server_process = None - _owns_server = False - pytest.exit(f"Mock API server failed to start within 60 seconds at {SERVER_URL}") - - print(f"Mock API server ready at {SERVER_URL}") - - except TimeoutError: - # Another process is holding the lock for too long - # Check if server is available anyway - if wait_for_server(SERVER_URL, timeout=5): - print(f"Mock API server available at {SERVER_URL} (started by another process)") - else: - pytest.exit("Timeout waiting for server lock - another process may be stuck") - - -def pytest_unconfigure(config): - """Stop the mock server after all tests complete.""" - global _server_process, _owns_server - - # Only stop the server if this process started it - if not _owns_server: - return - - terminate_server_process(_server_process) - _server_process = None - _owns_server = False - - # Clean up PID file - try: - PID_FILE.unlink(missing_ok=True) - except Exception: - pass - - @pytest.fixture(scope="session", autouse=True) -def testserver(request): - """Ensure the mock server is ready before tests run. +def testserver(): + """Start the mock API server, coordinated across xdist workers via file lock. - The server is started in pytest_configure (controller process). - This fixture just verifies the server is accessible from workers. + The first process to acquire the lock starts the server; others wait for it. + The server is intentionally NOT killed in teardown — with xdist, the owning + worker may finish before others, killing the server prematurely. The server + is cleaned up when the tox/parent process exits. """ + # Check if server is already running + if not wait_for_server(SERVER_URL, timeout=1, interval=0.1): + lock = FileLock(str(LOCK_FILE), timeout=120) + try: + with lock: + # Double-check after acquiring lock + if not wait_for_server(SERVER_URL, timeout=1, interval=0.1): + server = start_server_process() + PID_FILE.write_text(str(server.pid)) + if not wait_for_server(SERVER_URL, timeout=60): + terminate_server_process(server) + pytest.fail(f"Mock API server failed to start at {SERVER_URL}") + except TimeoutError: + if not wait_for_server(SERVER_URL, timeout=5): + pytest.fail("Timeout waiting for server lock") + + # Final check that server is reachable if not wait_for_server(SERVER_URL, timeout=30): pytest.fail(f"Mock API server not available at {SERVER_URL}") + yield @@ -212,12 +149,12 @@ def key_credential(core_library): @pytest.fixture def png_data() -> bytes: """Load PNG test data.""" - with open(str(DATA_FOLDER / "image.png"), "rb") as file_in: + with open(str(DATA_FOLDER / "data/image.png"), "rb") as file_in: return file_in.read() @pytest.fixture def jpg_data() -> bytes: """Load JPG test data.""" - with open(str(DATA_FOLDER / "image.jpg"), "rb") as file_in: + with open(str(DATA_FOLDER / "data/image.jpg"), "rb") as file_in: return file_in.read() diff --git a/packages/typespec-python/tests/data/image.png b/packages/typespec-python/tests/data/image.png deleted file mode 100644 index 42fe8dc145..0000000000 Binary files a/packages/typespec-python/tests/data/image.png and /dev/null differ diff --git a/packages/typespec-python/tests/generated/azure/authentication-api-key/authentication/apikey/_operations/_patch.py b/packages/typespec-python/tests/generated/azure/authentication-api-key/authentication/apikey/_operations/_patch.py deleted file mode 100644 index fc4fac79b3..0000000000 --- a/packages/typespec-python/tests/generated/azure/authentication-api-key/authentication/apikey/_operations/_patch.py +++ /dev/null @@ -1,36 +0,0 @@ -# coding=utf-8 - -"""Customize generated code here. - -Follow our quickstart for examples: https://aka.ms/azsdk/python/dpcodegen/python/customize -""" -from typing import List - -from ._operations import _ApiKeyClientOperationsMixin as Generated - - -class _ApiKeyClientOperationsMixin(Generated): - - def patch_added_operation(self) -> bool: - """This is a placeholder for the operation that was added in the patch. - - This method is a no-op and does not perform any action. - - :return: Always returns True. - :rtype: bool - """ - return True - - -__all__: List[str] = [ - "_ApiKeyClientOperationsMixin" -] # Add all objects you want publicly available to users at this package level - - -def patch_sdk(): - """Do not remove from this file. - - `patch_sdk` is a last resort escape hatch that allows you to do customizations - you can't accomplish using the techniques described in - https://aka.ms/azsdk/python/dpcodegen/python/customize - """ diff --git a/packages/typespec-python/tests/generated/azure/authentication-api-key/authentication/apikey/aio/_operations/_patch.py b/packages/typespec-python/tests/generated/azure/authentication-api-key/authentication/apikey/aio/_operations/_patch.py deleted file mode 100644 index fc4fac79b3..0000000000 --- a/packages/typespec-python/tests/generated/azure/authentication-api-key/authentication/apikey/aio/_operations/_patch.py +++ /dev/null @@ -1,36 +0,0 @@ -# coding=utf-8 - -"""Customize generated code here. - -Follow our quickstart for examples: https://aka.ms/azsdk/python/dpcodegen/python/customize -""" -from typing import List - -from ._operations import _ApiKeyClientOperationsMixin as Generated - - -class _ApiKeyClientOperationsMixin(Generated): - - def patch_added_operation(self) -> bool: - """This is a placeholder for the operation that was added in the patch. - - This method is a no-op and does not perform any action. - - :return: Always returns True. - :rtype: bool - """ - return True - - -__all__: List[str] = [ - "_ApiKeyClientOperationsMixin" -] # Add all objects you want publicly available to users at this package level - - -def patch_sdk(): - """Do not remove from this file. - - `patch_sdk` is a last resort escape hatch that allows you to do customizations - you can't accomplish using the techniques described in - https://aka.ms/azsdk/python/dpcodegen/python/customize - """ diff --git a/packages/typespec-python/tests/generated/azure/azure-client-generator-core-alternate-type/specs/azure/clientgenerator/core/alternatetype/models/_patch.py b/packages/typespec-python/tests/generated/azure/azure-client-generator-core-alternate-type/specs/azure/clientgenerator/core/alternatetype/models/_patch.py deleted file mode 100644 index fa513da4c0..0000000000 --- a/packages/typespec-python/tests/generated/azure/azure-client-generator-core-alternate-type/specs/azure/clientgenerator/core/alternatetype/models/_patch.py +++ /dev/null @@ -1,60 +0,0 @@ -# coding=utf-8 -# -------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License.txt in the project root for license information. -# -------------------------------------------------------------------------- -"""Customize generated code here. - -Follow our quickstart for examples: https://aka.ms/azsdk/python/dpcodegen/python/customize -""" -from typing import Type -import geojson -from .._utils.model_base import TYPE_HANDLER_REGISTRY - - -@TYPE_HANDLER_REGISTRY.register_serializer(geojson.Feature) -def feature_serializer(obj: geojson.Feature) -> dict: - """Serialize a geojson.Feature to a dict. - - :param obj: The geojson.Feature object to serialize. - :type obj: geojson.Feature - :return: The serialized feature as a dictionary. - :rtype: dict - """ - return { - "type": obj.type, - "geometry": {"type": obj.geometry.type, "coordinates": obj.geometry.coordinates}, - "properties": obj.properties, - "id": obj.id, - } - - -@TYPE_HANDLER_REGISTRY.register_deserializer(geojson.Feature) -def feature_deserializer(cls: Type[geojson.Feature], data: dict) -> geojson.Feature: - """Deserialize a dict to a geojson.Feature. - - :param data: The dictionary data to deserialize. - :type data: dict - :return: The deserialized geojson.Feature object. - :rtype: geojson.Feature - """ - return cls( - type=data.get("type"), - geometry=geojson.geometry.Geometry( - type=data["geometry"].get("type"), coordinates=data["geometry"].get("coordinates") - ), - properties=data.get("properties"), - id=data.get("id"), - ) - - -__all__: list[str] = [] # Add all objects you want publicly available to users at this package level - - -def patch_sdk(): - """Do not remove from this file. - - `patch_sdk` is a last resort escape hatch that allows you to do customizations - you can't accomplish using the techniques described in - https://aka.ms/azsdk/python/dpcodegen/python/customize - """ diff --git a/packages/typespec-python/tests/generated/unbranded/authentication-api-key/authentication/apikey/_operations/_patch.py b/packages/typespec-python/tests/generated/unbranded/authentication-api-key/authentication/apikey/_operations/_patch.py deleted file mode 100644 index fc4fac79b3..0000000000 --- a/packages/typespec-python/tests/generated/unbranded/authentication-api-key/authentication/apikey/_operations/_patch.py +++ /dev/null @@ -1,36 +0,0 @@ -# coding=utf-8 - -"""Customize generated code here. - -Follow our quickstart for examples: https://aka.ms/azsdk/python/dpcodegen/python/customize -""" -from typing import List - -from ._operations import _ApiKeyClientOperationsMixin as Generated - - -class _ApiKeyClientOperationsMixin(Generated): - - def patch_added_operation(self) -> bool: - """This is a placeholder for the operation that was added in the patch. - - This method is a no-op and does not perform any action. - - :return: Always returns True. - :rtype: bool - """ - return True - - -__all__: List[str] = [ - "_ApiKeyClientOperationsMixin" -] # Add all objects you want publicly available to users at this package level - - -def patch_sdk(): - """Do not remove from this file. - - `patch_sdk` is a last resort escape hatch that allows you to do customizations - you can't accomplish using the techniques described in - https://aka.ms/azsdk/python/dpcodegen/python/customize - """ diff --git a/packages/typespec-python/tests/generated/unbranded/authentication-api-key/authentication/apikey/aio/_operations/_patch.py b/packages/typespec-python/tests/generated/unbranded/authentication-api-key/authentication/apikey/aio/_operations/_patch.py deleted file mode 100644 index fc4fac79b3..0000000000 --- a/packages/typespec-python/tests/generated/unbranded/authentication-api-key/authentication/apikey/aio/_operations/_patch.py +++ /dev/null @@ -1,36 +0,0 @@ -# coding=utf-8 - -"""Customize generated code here. - -Follow our quickstart for examples: https://aka.ms/azsdk/python/dpcodegen/python/customize -""" -from typing import List - -from ._operations import _ApiKeyClientOperationsMixin as Generated - - -class _ApiKeyClientOperationsMixin(Generated): - - def patch_added_operation(self) -> bool: - """This is a placeholder for the operation that was added in the patch. - - This method is a no-op and does not perform any action. - - :return: Always returns True. - :rtype: bool - """ - return True - - -__all__: List[str] = [ - "_ApiKeyClientOperationsMixin" -] # Add all objects you want publicly available to users at this package level - - -def patch_sdk(): - """Do not remove from this file. - - `patch_sdk` is a last resort escape hatch that allows you to do customizations - you can't accomplish using the techniques described in - https://aka.ms/azsdk/python/dpcodegen/python/customize - """ diff --git a/packages/typespec-python/tests/mock_api/azure/asynctests/test_azure_arm_commonproperties_async.py b/packages/typespec-python/tests/mock_api/azure/asynctests/test_azure_arm_commonproperties_async.py index f5a62b6a86..15b8038fef 100644 --- a/packages/typespec-python/tests/mock_api/azure/asynctests/test_azure_arm_commonproperties_async.py +++ b/packages/typespec-python/tests/mock_api/azure/asynctests/test_azure_arm_commonproperties_async.py @@ -4,6 +4,7 @@ # license information. # -------------------------------------------------------------------------- import pytest +import pytest_asyncio from azure.resourcemanager.commonproperties.aio import CommonPropertiesClient from azure.resourcemanager.commonproperties import models from azure.core import exceptions @@ -12,7 +13,7 @@ RESOURCE_GROUP_NAME = "test-rg" -@pytest.fixture +@pytest_asyncio.fixture async def client(credential, authentication_policy): async with CommonPropertiesClient( credential, SUBSCRIPTION_ID, "http://localhost:3000", authentication_policy=authentication_policy diff --git a/packages/typespec-python/tests/mock_api/azure/asynctests/test_azure_arm_largeheader_async.py b/packages/typespec-python/tests/mock_api/azure/asynctests/test_azure_arm_largeheader_async.py index deac2360db..e696553a2f 100644 --- a/packages/typespec-python/tests/mock_api/azure/asynctests/test_azure_arm_largeheader_async.py +++ b/packages/typespec-python/tests/mock_api/azure/asynctests/test_azure_arm_largeheader_async.py @@ -4,6 +4,7 @@ # license information. # -------------------------------------------------------------------------- import pytest +import pytest_asyncio from azure.resourcemanager.largeheader.aio import LargeHeaderClient from azure.resourcemanager.largeheader import models @@ -11,7 +12,7 @@ RESOURCE_GROUP_NAME = "test-rg" -@pytest.fixture +@pytest_asyncio.fixture async def client(credential, authentication_policy): async with LargeHeaderClient( credential, SUBSCRIPTION_ID, "http://localhost:3000", authentication_policy=authentication_policy diff --git a/packages/typespec-python/tests/mock_api/azure/asynctests/test_azure_arm_nonresource_async.py b/packages/typespec-python/tests/mock_api/azure/asynctests/test_azure_arm_nonresource_async.py index 7cbbc763f3..926dcb129a 100644 --- a/packages/typespec-python/tests/mock_api/azure/asynctests/test_azure_arm_nonresource_async.py +++ b/packages/typespec-python/tests/mock_api/azure/asynctests/test_azure_arm_nonresource_async.py @@ -4,6 +4,7 @@ # license information. # -------------------------------------------------------------------------- import pytest +import pytest_asyncio from azure.resourcemanager.nonresource.aio import NonResourceClient from azure.resourcemanager.nonresource import models @@ -11,7 +12,7 @@ RESOURCE_GROUP_NAME = "test-rg" -@pytest.fixture +@pytest_asyncio.fixture async def client(credential, authentication_policy): async with NonResourceClient( credential, SUBSCRIPTION_ID, "http://localhost:3000", authentication_policy=authentication_policy diff --git a/packages/typespec-python/tests/mock_api/azure/asynctests/test_azure_arm_operationtemplates_async.py b/packages/typespec-python/tests/mock_api/azure/asynctests/test_azure_arm_operationtemplates_async.py index 2c1bc0fc4b..d28064a1bc 100644 --- a/packages/typespec-python/tests/mock_api/azure/asynctests/test_azure_arm_operationtemplates_async.py +++ b/packages/typespec-python/tests/mock_api/azure/asynctests/test_azure_arm_operationtemplates_async.py @@ -4,6 +4,7 @@ # license information. # -------------------------------------------------------------------------- import pytest +import pytest_asyncio from azure.resourcemanager.operationtemplates.aio import OperationTemplatesClient from azure.resourcemanager.operationtemplates import models @@ -11,7 +12,7 @@ RESOURCE_GROUP_NAME = "test-rg" -@pytest.fixture +@pytest_asyncio.fixture async def client(credential, authentication_policy): async with OperationTemplatesClient( credential, diff --git a/packages/typespec-python/tests/mock_api/azure/asynctests/test_azure_arm_resource_async.py b/packages/typespec-python/tests/mock_api/azure/asynctests/test_azure_arm_resource_async.py index 8da75bfe24..eb20ed43ee 100644 --- a/packages/typespec-python/tests/mock_api/azure/asynctests/test_azure_arm_resource_async.py +++ b/packages/typespec-python/tests/mock_api/azure/asynctests/test_azure_arm_resource_async.py @@ -4,6 +4,7 @@ # license information. # -------------------------------------------------------------------------- import pytest +import pytest_asyncio from azure.resourcemanager.resources.aio import ResourcesClient from azure.resourcemanager.resources import models @@ -11,7 +12,7 @@ RESOURCE_GROUP_NAME = "test-rg" -@pytest.fixture +@pytest_asyncio.fixture async def client(credential, authentication_policy): async with ResourcesClient( credential, diff --git a/packages/typespec-python/tests/mock_api/azure/asynctests/test_azure_client_generator_core_access_async.py b/packages/typespec-python/tests/mock_api/azure/asynctests/test_azure_client_generator_core_access_async.py index d1b6728a30..2091b74337 100644 --- a/packages/typespec-python/tests/mock_api/azure/asynctests/test_azure_client_generator_core_access_async.py +++ b/packages/typespec-python/tests/mock_api/azure/asynctests/test_azure_client_generator_core_access_async.py @@ -4,11 +4,12 @@ # license information. # -------------------------------------------------------------------------- import pytest +import pytest_asyncio from specs.azure.clientgenerator.core.access.aio import AccessClient from specs.azure.clientgenerator.core.access import models -@pytest.fixture +@pytest_asyncio.fixture async def client(): async with AccessClient() as client: yield client diff --git a/packages/typespec-python/tests/mock_api/azure/asynctests/test_azure_client_generator_core_alternate_type_async.py b/packages/typespec-python/tests/mock_api/azure/asynctests/test_azure_client_generator_core_alternate_type_async.py deleted file mode 100644 index 589aca7960..0000000000 --- a/packages/typespec-python/tests/mock_api/azure/asynctests/test_azure_client_generator_core_alternate_type_async.py +++ /dev/null @@ -1,73 +0,0 @@ -# ------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License.txt in the project root for -# license information. -# -------------------------------------------------------------------------- -import pytest -import geojson -from specs.azure.clientgenerator.core.alternatetype.aio import AlternateTypeClient -from specs.azure.clientgenerator.core.alternatetype import models - -# Shared test data -PROPERTIES = {"name": "A single point of interest", "category": "landmark", "elevation": 100} - -GEOMETRY = geojson.Point((-122.25, 37.87)) - -FEATURE_ID = "feature-1" - - -@pytest.fixture -async def client(): - async with AlternateTypeClient(endpoint="http://localhost:3000") as client: - yield client - - -@pytest.fixture -def feature_geojson(): - """Shared GeoJSON Feature for tests.""" - return geojson.Feature(type="Feature", geometry=GEOMETRY, properties=PROPERTIES, id=FEATURE_ID) - - -@pytest.mark.asyncio -async def test_external_type_get_model(client: AlternateTypeClient): - """Test getting a Feature object with geometry, properties, and optional id fields.""" - result = await client.external_type.get_model() - - # Validate the response structure based on the TypeSpec example - assert result.type == "Feature" - assert result.geometry.type == "Point" - assert result.geometry.coordinates == [-122.25, 37.87] - assert result.properties == PROPERTIES - assert result.id == FEATURE_ID - - -@pytest.mark.asyncio -async def test_external_type_put_model(client: AlternateTypeClient, feature_geojson): - """Test putting a Feature object in request body.""" - # Should return None (204/empty response) - result = await client.external_type.put_model(body=feature_geojson) - assert result is None - - -@pytest.mark.asyncio -async def test_external_type_get_property(client: AlternateTypeClient): - """Test getting a ModelWithFeatureProperty object with feature and additionalProperty fields.""" - result = await client.external_type.get_property() - - # Validate the response structure based on the TypeSpec example - assert result.feature.type == "Feature" - assert result.feature.geometry.type == "Point" - assert result.feature.geometry.coordinates == [-122.25, 37.87] - assert result.feature.properties == PROPERTIES - assert result.feature.id == FEATURE_ID - assert result.additional_property == "extra" - - -@pytest.mark.asyncio -async def test_external_type_put_property(client: AlternateTypeClient, feature_geojson): - """Test putting a ModelWithFeatureProperty object in request body.""" - model_with_feature = models.ModelWithFeatureProperty(feature=feature_geojson, additional_property="extra") - - # Should return None (204/empty response) - result = await client.external_type.put_property(body=model_with_feature) - assert result is None diff --git a/packages/typespec-python/tests/mock_api/azure/asynctests/test_azure_client_generator_core_api_version_header_async.py b/packages/typespec-python/tests/mock_api/azure/asynctests/test_azure_client_generator_core_api_version_header_async.py index 7fd89f76d6..7baa44a747 100644 --- a/packages/typespec-python/tests/mock_api/azure/asynctests/test_azure_client_generator_core_api_version_header_async.py +++ b/packages/typespec-python/tests/mock_api/azure/asynctests/test_azure_client_generator_core_api_version_header_async.py @@ -4,10 +4,11 @@ # license information. # -------------------------------------------------------------------------- import pytest +import pytest_asyncio from client.alternateapiversion.service.header.aio import HeaderClient -@pytest.fixture +@pytest_asyncio.fixture async def client(): async with HeaderClient(endpoint="http://localhost:3000") as client: yield client diff --git a/packages/typespec-python/tests/mock_api/azure/asynctests/test_azure_client_generator_core_api_version_path_async.py b/packages/typespec-python/tests/mock_api/azure/asynctests/test_azure_client_generator_core_api_version_path_async.py index 3b6c99c116..885f6f6007 100644 --- a/packages/typespec-python/tests/mock_api/azure/asynctests/test_azure_client_generator_core_api_version_path_async.py +++ b/packages/typespec-python/tests/mock_api/azure/asynctests/test_azure_client_generator_core_api_version_path_async.py @@ -4,10 +4,11 @@ # license information. # -------------------------------------------------------------------------- import pytest +import pytest_asyncio from client.alternateapiversion.service.path.aio import PathClient -@pytest.fixture +@pytest_asyncio.fixture async def client(): async with PathClient(endpoint="http://localhost:3000") as client: yield client diff --git a/packages/typespec-python/tests/mock_api/azure/asynctests/test_azure_client_generator_core_api_version_query_async.py b/packages/typespec-python/tests/mock_api/azure/asynctests/test_azure_client_generator_core_api_version_query_async.py index 3163f9a7c3..62f2f9847c 100644 --- a/packages/typespec-python/tests/mock_api/azure/asynctests/test_azure_client_generator_core_api_version_query_async.py +++ b/packages/typespec-python/tests/mock_api/azure/asynctests/test_azure_client_generator_core_api_version_query_async.py @@ -4,10 +4,11 @@ # license information. # -------------------------------------------------------------------------- import pytest +import pytest_asyncio from client.alternateapiversion.service.query.aio import QueryClient -@pytest.fixture +@pytest_asyncio.fixture async def client(): async with QueryClient(endpoint="http://localhost:3000") as client: yield client diff --git a/packages/typespec-python/tests/mock_api/azure/asynctests/test_azure_client_generator_core_client_default_value_async.py b/packages/typespec-python/tests/mock_api/azure/asynctests/test_azure_client_generator_core_client_default_value_async.py index 1d7795bbfc..f4bbaaae04 100644 --- a/packages/typespec-python/tests/mock_api/azure/asynctests/test_azure_client_generator_core_client_default_value_async.py +++ b/packages/typespec-python/tests/mock_api/azure/asynctests/test_azure_client_generator_core_client_default_value_async.py @@ -4,11 +4,12 @@ # license information. # -------------------------------------------------------------------------- import pytest +import pytest_asyncio from specs.azure.clientgenerator.core.clientdefaultvalue.aio import ClientDefaultValueClient from specs.azure.clientgenerator.core.clientdefaultvalue.models import ModelWithDefaultValues -@pytest.fixture +@pytest_asyncio.fixture async def client(): async with ClientDefaultValueClient() as client: yield client diff --git a/packages/typespec-python/tests/mock_api/azure/asynctests/test_azure_client_generator_core_client_location_async.py b/packages/typespec-python/tests/mock_api/azure/asynctests/test_azure_client_generator_core_client_location_async.py index a039f18932..d320f31ffb 100644 --- a/packages/typespec-python/tests/mock_api/azure/asynctests/test_azure_client_generator_core_client_location_async.py +++ b/packages/typespec-python/tests/mock_api/azure/asynctests/test_azure_client_generator_core_client_location_async.py @@ -4,31 +4,32 @@ # license information. # -------------------------------------------------------------------------- import pytest +import pytest_asyncio from specs.azure.clientgenerator.core.clientlocation.parameter.aio import MoveMethodParameterToClient from specs.azure.clientgenerator.core.clientlocation.subclient.aio import MoveToExistingSubClient from specs.azure.clientgenerator.core.clientlocation.newsubclient.aio import MoveToNewSubClient from specs.azure.clientgenerator.core.clientlocation.rootclient.aio import MoveToRootClient -@pytest.fixture +@pytest_asyncio.fixture async def move_method_parameter_to_client(): async with MoveMethodParameterToClient(storage_account="testaccount") as client: yield client -@pytest.fixture +@pytest_asyncio.fixture async def move_to_existing_sub_client(): async with MoveToExistingSubClient() as client: yield client -@pytest.fixture +@pytest_asyncio.fixture async def move_to_new_sub_client(): async with MoveToNewSubClient() as client: yield client -@pytest.fixture +@pytest_asyncio.fixture async def move_to_root_client(): async with MoveToRootClient() as client: yield client diff --git a/packages/typespec-python/tests/mock_api/azure/asynctests/test_azure_client_generator_core_deserialize_empty_string_as_null_async.py b/packages/typespec-python/tests/mock_api/azure/asynctests/test_azure_client_generator_core_deserialize_empty_string_as_null_async.py index fbb4349f5a..e926baf8da 100644 --- a/packages/typespec-python/tests/mock_api/azure/asynctests/test_azure_client_generator_core_deserialize_empty_string_as_null_async.py +++ b/packages/typespec-python/tests/mock_api/azure/asynctests/test_azure_client_generator_core_deserialize_empty_string_as_null_async.py @@ -4,11 +4,12 @@ # license information. # -------------------------------------------------------------------------- import pytest +import pytest_asyncio from specs.azure.clientgenerator.core.emptystring.aio import DeserializeEmptyStringAsNullClient from specs.azure.clientgenerator.core.emptystring import models -@pytest.fixture +@pytest_asyncio.fixture async def client(): async with DeserializeEmptyStringAsNullClient() as client: yield client diff --git a/packages/typespec-python/tests/mock_api/azure/asynctests/test_azure_client_generator_core_flatten_async.py b/packages/typespec-python/tests/mock_api/azure/asynctests/test_azure_client_generator_core_flatten_async.py index cb044b430f..6ebe71ad86 100644 --- a/packages/typespec-python/tests/mock_api/azure/asynctests/test_azure_client_generator_core_flatten_async.py +++ b/packages/typespec-python/tests/mock_api/azure/asynctests/test_azure_client_generator_core_flatten_async.py @@ -4,6 +4,7 @@ # license information. # -------------------------------------------------------------------------- import pytest +import pytest_asyncio from specs.azure.clientgenerator.core.flattenproperty.aio import FlattenPropertyClient from specs.azure.clientgenerator.core.flattenproperty.models import ( ChildFlattenModel, @@ -16,7 +17,7 @@ ) -@pytest.fixture +@pytest_asyncio.fixture async def client(): async with FlattenPropertyClient() as client: yield client diff --git a/packages/typespec-python/tests/mock_api/azure/asynctests/test_azure_client_generator_core_hierrarchy_building_async.py b/packages/typespec-python/tests/mock_api/azure/asynctests/test_azure_client_generator_core_hierrarchy_building_async.py index b940b639fe..9cf9749e50 100644 --- a/packages/typespec-python/tests/mock_api/azure/asynctests/test_azure_client_generator_core_hierrarchy_building_async.py +++ b/packages/typespec-python/tests/mock_api/azure/asynctests/test_azure_client_generator_core_hierrarchy_building_async.py @@ -4,6 +4,7 @@ # license information. # -------------------------------------------------------------------------- import pytest +import pytest_asyncio from specs.azure.clientgenerator.core.hierarchybuilding.aio import HierarchyBuildingClient from specs.azure.clientgenerator.core.hierarchybuilding.models import ( Pet, @@ -11,7 +12,7 @@ ) -@pytest.fixture +@pytest_asyncio.fixture async def client(): async with HierarchyBuildingClient() as client: yield client diff --git a/packages/typespec-python/tests/mock_api/azure/asynctests/test_azure_client_generator_core_next_link_verb_async.py b/packages/typespec-python/tests/mock_api/azure/asynctests/test_azure_client_generator_core_next_link_verb_async.py index 7ce1de3c50..c9061a3f8a 100644 --- a/packages/typespec-python/tests/mock_api/azure/asynctests/test_azure_client_generator_core_next_link_verb_async.py +++ b/packages/typespec-python/tests/mock_api/azure/asynctests/test_azure_client_generator_core_next_link_verb_async.py @@ -4,11 +4,12 @@ # license information. # -------------------------------------------------------------------------- import pytest +import pytest_asyncio from specs.azure.clientgenerator.core.nextlinkverb.aio import NextLinkVerbClient -@pytest.fixture +@pytest_asyncio.fixture async def client(): async with NextLinkVerbClient(endpoint="http://localhost:3000") as client: yield client diff --git a/packages/typespec-python/tests/mock_api/azure/asynctests/test_azure_client_generator_core_override_async.py b/packages/typespec-python/tests/mock_api/azure/asynctests/test_azure_client_generator_core_override_async.py index 8fa5849b46..aeae909856 100644 --- a/packages/typespec-python/tests/mock_api/azure/asynctests/test_azure_client_generator_core_override_async.py +++ b/packages/typespec-python/tests/mock_api/azure/asynctests/test_azure_client_generator_core_override_async.py @@ -5,10 +5,11 @@ # -------------------------------------------------------------------------- import inspect import pytest +import pytest_asyncio from specs.azure.clientgenerator.core.override.aio import OverrideClient -@pytest.fixture +@pytest_asyncio.fixture async def client(): async with OverrideClient() as client: yield client diff --git a/packages/typespec-python/tests/mock_api/azure/asynctests/test_azure_client_generator_core_usage_async.py b/packages/typespec-python/tests/mock_api/azure/asynctests/test_azure_client_generator_core_usage_async.py index 66c7946d88..816279eeb9 100644 --- a/packages/typespec-python/tests/mock_api/azure/asynctests/test_azure_client_generator_core_usage_async.py +++ b/packages/typespec-python/tests/mock_api/azure/asynctests/test_azure_client_generator_core_usage_async.py @@ -4,11 +4,12 @@ # license information. # -------------------------------------------------------------------------- import pytest +import pytest_asyncio from specs.azure.clientgenerator.core.usage.aio import UsageClient from specs.azure.clientgenerator.core.usage import models -@pytest.fixture +@pytest_asyncio.fixture async def client(): async with UsageClient() as client: yield client diff --git a/packages/typespec-python/tests/mock_api/azure/asynctests/test_azure_core_basic_async.py b/packages/typespec-python/tests/mock_api/azure/asynctests/test_azure_core_basic_async.py index 87946f37ca..3ce2c52262 100644 --- a/packages/typespec-python/tests/mock_api/azure/asynctests/test_azure_core_basic_async.py +++ b/packages/typespec-python/tests/mock_api/azure/asynctests/test_azure_core_basic_async.py @@ -4,12 +4,13 @@ # license information. # -------------------------------------------------------------------------- import pytest +import pytest_asyncio from specs.azure.core.basic import models, aio VALID_USER = models.User(id=1, name="Madge", etag="11bdc430-65e8-45ad-81d9-8ffa60d55b59") -@pytest.fixture +@pytest_asyncio.fixture async def client(): async with aio.BasicClient() as client: yield client diff --git a/packages/typespec-python/tests/mock_api/azure/asynctests/test_azure_core_lro_rpc_async.py b/packages/typespec-python/tests/mock_api/azure/asynctests/test_azure_core_lro_rpc_async.py index bc572bf298..57c19c423b 100644 --- a/packages/typespec-python/tests/mock_api/azure/asynctests/test_azure_core_lro_rpc_async.py +++ b/packages/typespec-python/tests/mock_api/azure/asynctests/test_azure_core_lro_rpc_async.py @@ -4,11 +4,12 @@ # license information. # -------------------------------------------------------------------------- import pytest +import pytest_asyncio from specs.azure.core.lro.rpc.aio import RpcClient from specs.azure.core.lro.rpc import models -@pytest.fixture +@pytest_asyncio.fixture async def client(): async with RpcClient() as client: yield client diff --git a/packages/typespec-python/tests/mock_api/azure/asynctests/test_azure_core_lro_standard_async.py b/packages/typespec-python/tests/mock_api/azure/asynctests/test_azure_core_lro_standard_async.py index b9b00668ca..05c4c09a2a 100644 --- a/packages/typespec-python/tests/mock_api/azure/asynctests/test_azure_core_lro_standard_async.py +++ b/packages/typespec-python/tests/mock_api/azure/asynctests/test_azure_core_lro_standard_async.py @@ -4,11 +4,12 @@ # license information. # -------------------------------------------------------------------------- import pytest +import pytest_asyncio from specs.azure.core.lro.standard.aio import StandardClient from specs.azure.core.lro.standard.models import User, ExportedUser -@pytest.fixture +@pytest_asyncio.fixture async def client(): async with StandardClient() as client: yield client diff --git a/packages/typespec-python/tests/mock_api/azure/asynctests/test_azure_core_model_async.py b/packages/typespec-python/tests/mock_api/azure/asynctests/test_azure_core_model_async.py index b2f8eed895..333cfb5dcb 100644 --- a/packages/typespec-python/tests/mock_api/azure/asynctests/test_azure_core_model_async.py +++ b/packages/typespec-python/tests/mock_api/azure/asynctests/test_azure_core_model_async.py @@ -4,11 +4,12 @@ # license information. # -------------------------------------------------------------------------- import pytest +import pytest_asyncio from specs.azure.core.model.aio import ModelClient from specs.azure.core.model.models import AzureEmbeddingModel -@pytest.fixture +@pytest_asyncio.fixture async def client(): async with ModelClient() as client: yield client diff --git a/packages/typespec-python/tests/mock_api/azure/asynctests/test_azure_core_page_async.py b/packages/typespec-python/tests/mock_api/azure/asynctests/test_azure_core_page_async.py index 557c044e9e..01a862620f 100644 --- a/packages/typespec-python/tests/mock_api/azure/asynctests/test_azure_core_page_async.py +++ b/packages/typespec-python/tests/mock_api/azure/asynctests/test_azure_core_page_async.py @@ -4,13 +4,14 @@ # license information. # -------------------------------------------------------------------------- import pytest +import pytest_asyncio from typing import AsyncIterable from specs.azure.core.page import models, aio VALID_USER = models.User(id=1, name="Madge", etag="11bdc430-65e8-45ad-81d9-8ffa60d55b59") -@pytest.fixture +@pytest_asyncio.fixture async def client(): async with aio.PageClient() as client: yield client diff --git a/packages/typespec-python/tests/mock_api/azure/asynctests/test_azure_core_scalar_async.py b/packages/typespec-python/tests/mock_api/azure/asynctests/test_azure_core_scalar_async.py index 4616cc2b5e..321ebf9fc1 100644 --- a/packages/typespec-python/tests/mock_api/azure/asynctests/test_azure_core_scalar_async.py +++ b/packages/typespec-python/tests/mock_api/azure/asynctests/test_azure_core_scalar_async.py @@ -4,11 +4,12 @@ # license information. # -------------------------------------------------------------------------- import pytest +import pytest_asyncio from specs.azure.core.scalar.aio import ScalarClient from specs.azure.core.scalar import models -@pytest.fixture +@pytest_asyncio.fixture async def client(): async with ScalarClient() as client: yield client diff --git a/packages/typespec-python/tests/mock_api/azure/asynctests/test_azure_core_traits_async.py b/packages/typespec-python/tests/mock_api/azure/asynctests/test_azure_core_traits_async.py index dca8185442..02060f56c2 100644 --- a/packages/typespec-python/tests/mock_api/azure/asynctests/test_azure_core_traits_async.py +++ b/packages/typespec-python/tests/mock_api/azure/asynctests/test_azure_core_traits_async.py @@ -7,13 +7,14 @@ from datetime import datetime import pytest +import pytest_asyncio from azure.core.exceptions import HttpResponseError from azure.core import MatchConditions from specs.azure.core.traits.aio import TraitsClient from specs.azure.core.traits.models import UserActionParam -@pytest.fixture +@pytest_asyncio.fixture async def client(): async with TraitsClient() as client: yield client diff --git a/packages/typespec-python/tests/mock_api/azure/asynctests/test_azure_encode_duration_async.py b/packages/typespec-python/tests/mock_api/azure/asynctests/test_azure_encode_duration_async.py index 8a23b94955..d1c4867b89 100644 --- a/packages/typespec-python/tests/mock_api/azure/asynctests/test_azure_encode_duration_async.py +++ b/packages/typespec-python/tests/mock_api/azure/asynctests/test_azure_encode_duration_async.py @@ -4,11 +4,12 @@ # license information. # -------------------------------------------------------------------------- import pytest +import pytest_asyncio from specs.azure.encode.duration.aio import DurationClient from specs.azure.encode.duration import models -@pytest.fixture +@pytest_asyncio.fixture async def client(): async with DurationClient() as client: yield client diff --git a/packages/typespec-python/tests/mock_api/azure/asynctests/test_azure_example_basic_async.py b/packages/typespec-python/tests/mock_api/azure/asynctests/test_azure_example_basic_async.py index 46a66e519d..00ce65cfb4 100644 --- a/packages/typespec-python/tests/mock_api/azure/asynctests/test_azure_example_basic_async.py +++ b/packages/typespec-python/tests/mock_api/azure/asynctests/test_azure_example_basic_async.py @@ -4,11 +4,12 @@ # license information. # -------------------------------------------------------------------------- import pytest +import pytest_asyncio from specs.azure.example.basic.aio import AzureExampleClient from specs.azure.example.basic.models import ActionRequest, Model -@pytest.fixture +@pytest_asyncio.fixture async def client(): async with AzureExampleClient() as client: yield client diff --git a/packages/typespec-python/tests/mock_api/azure/asynctests/test_azure_payload_pageable_async.py b/packages/typespec-python/tests/mock_api/azure/asynctests/test_azure_payload_pageable_async.py index 440a4a5117..1da9e66589 100644 --- a/packages/typespec-python/tests/mock_api/azure/asynctests/test_azure_payload_pageable_async.py +++ b/packages/typespec-python/tests/mock_api/azure/asynctests/test_azure_payload_pageable_async.py @@ -4,10 +4,11 @@ # license information. # -------------------------------------------------------------------------- import pytest +import pytest_asyncio from specs.azure.payload.pageable.aio import PageableClient -@pytest.fixture +@pytest_asyncio.fixture async def client(): async with PageableClient(endpoint="http://localhost:3000") as client: yield client diff --git a/packages/typespec-python/tests/mock_api/azure/asynctests/test_azure_resource_manager_method_subscription_id_async.py b/packages/typespec-python/tests/mock_api/azure/asynctests/test_azure_resource_manager_method_subscription_id_async.py index 7b96c3c297..4cd896a347 100644 --- a/packages/typespec-python/tests/mock_api/azure/asynctests/test_azure_resource_manager_method_subscription_id_async.py +++ b/packages/typespec-python/tests/mock_api/azure/asynctests/test_azure_resource_manager_method_subscription_id_async.py @@ -4,6 +4,7 @@ # license information. # -------------------------------------------------------------------------- import pytest +import pytest_asyncio from azure.resourcemanager.methodsubscriptionid.aio import MethodSubscriptionIdClient from azure.resourcemanager.methodsubscriptionid import models @@ -11,7 +12,7 @@ RESOURCE_GROUP_NAME = "test-rg" -@pytest.fixture +@pytest_asyncio.fixture async def client(credential, authentication_policy): async with MethodSubscriptionIdClient( credential, diff --git a/packages/typespec-python/tests/mock_api/azure/asynctests/test_azure_resource_manager_multi_service_async.py b/packages/typespec-python/tests/mock_api/azure/asynctests/test_azure_resource_manager_multi_service_async.py index 86af8d31a9..b80c58a227 100644 --- a/packages/typespec-python/tests/mock_api/azure/asynctests/test_azure_resource_manager_multi_service_async.py +++ b/packages/typespec-python/tests/mock_api/azure/asynctests/test_azure_resource_manager_multi_service_async.py @@ -4,12 +4,13 @@ # license information. # -------------------------------------------------------------------------- import pytest +import pytest_asyncio from azure.core.exceptions import HttpResponseError from azure.resourcemanager.multiservice.combined.aio import CombinedClient from azure.resourcemanager.multiservice.combined.models import VirtualMachine, Disk -@pytest.fixture +@pytest_asyncio.fixture async def client(credential, authentication_policy): """Create a Combined async client for testing.""" return CombinedClient( diff --git a/packages/typespec-python/tests/mock_api/azure/asynctests/test_azure_resource_manager_multi_service_shared_models_async.py b/packages/typespec-python/tests/mock_api/azure/asynctests/test_azure_resource_manager_multi_service_shared_models_async.py index ee1c21bfbe..57bc4929d9 100644 --- a/packages/typespec-python/tests/mock_api/azure/asynctests/test_azure_resource_manager_multi_service_shared_models_async.py +++ b/packages/typespec-python/tests/mock_api/azure/asynctests/test_azure_resource_manager_multi_service_shared_models_async.py @@ -4,6 +4,7 @@ # license information. # -------------------------------------------------------------------------- import pytest +import pytest_asyncio from azure.resourcemanager.multiservicesharedmodels.combined.aio import CombinedClient from azure.resourcemanager.multiservicesharedmodels.combined.models import ( VirtualMachine, @@ -14,7 +15,7 @@ ) -@pytest.fixture +@pytest_asyncio.fixture async def client(credential, authentication_policy): """Create a Combined async client for testing.""" return CombinedClient( diff --git a/packages/typespec-python/tests/mock_api/azure/asynctests/test_azure_special_headers_client_request_id_async.py b/packages/typespec-python/tests/mock_api/azure/asynctests/test_azure_special_headers_client_request_id_async.py index b89ee4b732..eb066f8427 100644 --- a/packages/typespec-python/tests/mock_api/azure/asynctests/test_azure_special_headers_client_request_id_async.py +++ b/packages/typespec-python/tests/mock_api/azure/asynctests/test_azure_special_headers_client_request_id_async.py @@ -6,11 +6,12 @@ import functools import pytest +import pytest_asyncio from azure.specialheaders.xmsclientrequestid.aio import XmsClientRequestIdClient -@pytest.fixture +@pytest_asyncio.fixture async def client(): async with XmsClientRequestIdClient() as client: yield client diff --git a/packages/typespec-python/tests/mock_api/azure/asynctests/test_azure_versioning_previewversion_async.py b/packages/typespec-python/tests/mock_api/azure/asynctests/test_azure_versioning_previewversion_async.py index 512e69c817..a7d6d3d54c 100644 --- a/packages/typespec-python/tests/mock_api/azure/asynctests/test_azure_versioning_previewversion_async.py +++ b/packages/typespec-python/tests/mock_api/azure/asynctests/test_azure_versioning_previewversion_async.py @@ -4,17 +4,18 @@ # license information. # -------------------------------------------------------------------------- import pytest +import pytest_asyncio from specs.azure.versioning.previewversion.aio import PreviewVersionClient from specs.azure.versioning.previewversion.models import UpdateWidgetColorRequest -@pytest.fixture +@pytest_asyncio.fixture async def client(): async with PreviewVersionClient() as client: yield client -@pytest.fixture +@pytest_asyncio.fixture async def stable_client(): async with PreviewVersionClient(api_version="2024-06-01") as client: yield client diff --git a/packages/typespec-python/tests/mock_api/azure/asynctests/test_client_namespace_async.py b/packages/typespec-python/tests/mock_api/azure/asynctests/test_client_namespace_async.py index adc98e3d56..06812847e2 100644 --- a/packages/typespec-python/tests/mock_api/azure/asynctests/test_client_namespace_async.py +++ b/packages/typespec-python/tests/mock_api/azure/asynctests/test_client_namespace_async.py @@ -4,6 +4,7 @@ # license information. # -------------------------------------------------------------------------- import pytest +import pytest_asyncio from client.clientnamespace.aio import ClientNamespaceFirstClient from client.clientnamespace.first.models import FirstClientResult @@ -12,13 +13,13 @@ from client.clientnamespace.second.sub.models import SecondClientEnumType -@pytest.fixture +@pytest_asyncio.fixture async def first_client(): async with ClientNamespaceFirstClient() as client: yield client -@pytest.fixture +@pytest_asyncio.fixture async def second_client(): async with ClientNamespaceSecondClient() as client: yield client diff --git a/packages/typespec-python/tests/mock_api/azure/asynctests/test_client_naming_async.py b/packages/typespec-python/tests/mock_api/azure/asynctests/test_client_naming_async.py index 4ccd786e80..9a3f249740 100644 --- a/packages/typespec-python/tests/mock_api/azure/asynctests/test_client_naming_async.py +++ b/packages/typespec-python/tests/mock_api/azure/asynctests/test_client_naming_async.py @@ -4,11 +4,12 @@ # license information. # -------------------------------------------------------------------------- import pytest +import pytest_asyncio from client.naming.main.aio import NamingClient from client.naming.main import models -@pytest.fixture +@pytest_asyncio.fixture async def client(): async with NamingClient() as client: yield client diff --git a/packages/typespec-python/tests/mock_api/azure/asynctests/test_client_naming_enum_conflict_async.py b/packages/typespec-python/tests/mock_api/azure/asynctests/test_client_naming_enum_conflict_async.py index 27b7731d98..edcf008684 100644 --- a/packages/typespec-python/tests/mock_api/azure/asynctests/test_client_naming_enum_conflict_async.py +++ b/packages/typespec-python/tests/mock_api/azure/asynctests/test_client_naming_enum_conflict_async.py @@ -4,12 +4,13 @@ # license information. # -------------------------------------------------------------------------- import pytest +import pytest_asyncio from client.naming.enumconflict.aio import EnumConflictClient from client.naming.enumconflict.firstnamespace import models as first_models from client.naming.enumconflict.secondnamespace import models as second_models -@pytest.fixture +@pytest_asyncio.fixture async def client(): async with EnumConflictClient() as client: yield client diff --git a/packages/typespec-python/tests/mock_api/azure/asynctests/test_client_structure_async.py b/packages/typespec-python/tests/mock_api/azure/asynctests/test_client_structure_async.py index fa58bd0f75..8d17c7a069 100644 --- a/packages/typespec-python/tests/mock_api/azure/asynctests/test_client_structure_async.py +++ b/packages/typespec-python/tests/mock_api/azure/asynctests/test_client_structure_async.py @@ -38,7 +38,6 @@ async def test_structure_multiclient(): await client_b.renamed_six() -@pytest.mark.skip(reason="will reopen the cases after upgrade `@azure-tools/typespec-client-generator-core` to 0.67.0") @pytest.mark.asyncio async def test_structure_renamed_operation(): client = RenamedOperationClient(endpoint="http://localhost:3000", client=ClientType.RENAMED_OPERATION) @@ -46,19 +45,18 @@ async def test_structure_renamed_operation(): await client.renamed_three() await client.renamed_five() - await client.renamed_two() - await client.renamed_four() - await client.renamed_six() + await client.group.renamed_two() + await client.group.renamed_four() + await client.group.renamed_six() -@pytest.mark.skip(reason="will reopen the cases after upgrade `@azure-tools/typespec-client-generator-core` to 0.67.0") @pytest.mark.asyncio async def test_structure_two_operation_group(): client = TwoOperationGroupClient(endpoint="http://localhost:3000", client=ClientType.TWO_OPERATION_GROUP) - await client.one() - await client.three() - await client.four() + await client.group1.one() + await client.group1.three() + await client.group1.four() - await client.two() - await client.five() - await client.six() + await client.group2.two() + await client.group2.five() + await client.group2.six() diff --git a/packages/typespec-python/tests/mock_api/azure/asynctests/test_client_structure_clientoperationgroup_async.py b/packages/typespec-python/tests/mock_api/azure/asynctests/test_client_structure_clientoperationgroup_async.py index 0b3ba88d30..96f1157418 100644 --- a/packages/typespec-python/tests/mock_api/azure/asynctests/test_client_structure_clientoperationgroup_async.py +++ b/packages/typespec-python/tests/mock_api/azure/asynctests/test_client_structure_clientoperationgroup_async.py @@ -8,24 +8,22 @@ from client.structure.clientoperationgroup.aio import FirstClient, SecondClient -@pytest.mark.skip(reason="will reopen the cases after upgrade `@azure-tools/typespec-client-generator-core` to 0.67.0") @pytest.mark.asyncio async def test_first_client_operations(): client = FirstClient(endpoint="http://localhost:3000", client=ClientType.CLIENT_OPERATION_GROUP) await client.one() - await client.two() - await client.three() + await client.group3.two() + await client.group3.three() - await client.four() + await client.group4.four() -@pytest.mark.skip(reason="will reopen the cases after upgrade `@azure-tools/typespec-client-generator-core` to 0.67.0") @pytest.mark.asyncio async def test_second_client_operations(): client = SecondClient(endpoint="http://localhost:3000", client=ClientType.CLIENT_OPERATION_GROUP) await client.five() - await client.six() + await client.group5.six() diff --git a/packages/typespec-python/tests/mock_api/azure/asynctests/test_encode_duration_async.py b/packages/typespec-python/tests/mock_api/azure/asynctests/test_encode_duration_async.py index 0fca037194..b79a501903 100644 --- a/packages/typespec-python/tests/mock_api/azure/asynctests/test_encode_duration_async.py +++ b/packages/typespec-python/tests/mock_api/azure/asynctests/test_encode_duration_async.py @@ -6,6 +6,7 @@ import datetime import pytest +import pytest_asyncio from encode.duration.aio import DurationClient from encode.duration.models import ( Int32SecondsDurationProperty, @@ -16,7 +17,7 @@ ) -@pytest.fixture +@pytest_asyncio.fixture async def client(): async with DurationClient() as client: yield client diff --git a/packages/typespec-python/tests/mock_api/azure/asynctests/test_encode_numeric_async.py b/packages/typespec-python/tests/mock_api/azure/asynctests/test_encode_numeric_async.py index 7bd8b5e1f5..a3a8a4d1fe 100644 --- a/packages/typespec-python/tests/mock_api/azure/asynctests/test_encode_numeric_async.py +++ b/packages/typespec-python/tests/mock_api/azure/asynctests/test_encode_numeric_async.py @@ -4,11 +4,12 @@ # license information. # -------------------------------------------------------------------------- import pytest +import pytest_asyncio from encode.numeric.aio import NumericClient from encode.numeric import models -@pytest.fixture +@pytest_asyncio.fixture async def client(): async with NumericClient() as client: yield client diff --git a/packages/typespec-python/tests/mock_api/azure/asynctests/test_parameters_basic_async.py b/packages/typespec-python/tests/mock_api/azure/asynctests/test_parameters_basic_async.py index 969e73a6c8..2b53806b4c 100644 --- a/packages/typespec-python/tests/mock_api/azure/asynctests/test_parameters_basic_async.py +++ b/packages/typespec-python/tests/mock_api/azure/asynctests/test_parameters_basic_async.py @@ -4,11 +4,12 @@ # license information. # -------------------------------------------------------------------------- import pytest +import pytest_asyncio from parameters.basic.aio import BasicClient from parameters.basic.models import User -@pytest.fixture +@pytest_asyncio.fixture async def client(): async with BasicClient() as client: yield client diff --git a/packages/typespec-python/tests/mock_api/azure/asynctests/test_parameters_spread_async.py b/packages/typespec-python/tests/mock_api/azure/asynctests/test_parameters_spread_async.py index 74032d8e51..9fa0802863 100644 --- a/packages/typespec-python/tests/mock_api/azure/asynctests/test_parameters_spread_async.py +++ b/packages/typespec-python/tests/mock_api/azure/asynctests/test_parameters_spread_async.py @@ -4,11 +4,12 @@ # license information. # -------------------------------------------------------------------------- import pytest +import pytest_asyncio from parameters.spread.aio import SpreadClient from parameters.spread.models import BodyParameter -@pytest.fixture +@pytest_asyncio.fixture async def client(): async with SpreadClient() as client: yield client diff --git a/packages/typespec-python/tests/mock_api/azure/asynctests/test_payload_content_negotiation_async.py b/packages/typespec-python/tests/mock_api/azure/asynctests/test_payload_content_negotiation_async.py index 4c2a11f61a..84ec7ba708 100644 --- a/packages/typespec-python/tests/mock_api/azure/asynctests/test_payload_content_negotiation_async.py +++ b/packages/typespec-python/tests/mock_api/azure/asynctests/test_payload_content_negotiation_async.py @@ -5,11 +5,12 @@ # -------------------------------------------------------------------------- import base64 import pytest +import pytest_asyncio from payload.contentnegotiation.aio import ContentNegotiationClient from payload.contentnegotiation.models import PngImageAsJson -@pytest.fixture +@pytest_asyncio.fixture async def client(): async with ContentNegotiationClient(endpoint="http://localhost:3000") as client: yield client diff --git a/packages/typespec-python/tests/mock_api/azure/asynctests/test_payload_multipart_async.py b/packages/typespec-python/tests/mock_api/azure/asynctests/test_payload_multipart_async.py index 5cd750c8c0..03668f5ddb 100644 --- a/packages/typespec-python/tests/mock_api/azure/asynctests/test_payload_multipart_async.py +++ b/packages/typespec-python/tests/mock_api/azure/asynctests/test_payload_multipart_async.py @@ -5,6 +5,7 @@ # -------------------------------------------------------------------------- from pathlib import Path import pytest +import pytest_asyncio from payload.multipart import models from payload.multipart.aio import MultiPartClient @@ -12,7 +13,7 @@ PNG = Path(__file__).parent.parent / "data/image.png" -@pytest.fixture +@pytest_asyncio.fixture async def client(): async with MultiPartClient(endpoint="http://localhost:3000") as client: yield client diff --git a/packages/typespec-python/tests/mock_api/azure/asynctests/test_serialization_encoded_name_json_async.py b/packages/typespec-python/tests/mock_api/azure/asynctests/test_serialization_encoded_name_json_async.py index 70bfcc77ad..76594bbce5 100644 --- a/packages/typespec-python/tests/mock_api/azure/asynctests/test_serialization_encoded_name_json_async.py +++ b/packages/typespec-python/tests/mock_api/azure/asynctests/test_serialization_encoded_name_json_async.py @@ -4,11 +4,12 @@ # license information. # -------------------------------------------------------------------------- import pytest +import pytest_asyncio from serialization.encodedname.json.aio import JsonClient from serialization.encodedname.json import models -@pytest.fixture +@pytest_asyncio.fixture async def client(): async with JsonClient() as client: yield client diff --git a/packages/typespec-python/tests/mock_api/azure/asynctests/test_special_words_async.py b/packages/typespec-python/tests/mock_api/azure/asynctests/test_special_words_async.py index 6c01e8f073..8c59ea0507 100644 --- a/packages/typespec-python/tests/mock_api/azure/asynctests/test_special_words_async.py +++ b/packages/typespec-python/tests/mock_api/azure/asynctests/test_special_words_async.py @@ -4,11 +4,12 @@ # license information. # -------------------------------------------------------------------------- import pytest +import pytest_asyncio from specialwords.aio import SpecialWordsClient from specialwords import models -@pytest.fixture +@pytest_asyncio.fixture async def client(): async with SpecialWordsClient() as client: yield client diff --git a/packages/typespec-python/tests/mock_api/azure/conftest.py b/packages/typespec-python/tests/mock_api/azure/conftest.py index 062e11aeca..20d7152e5d 100644 --- a/packages/typespec-python/tests/mock_api/azure/conftest.py +++ b/packages/typespec-python/tests/mock_api/azure/conftest.py @@ -3,16 +3,10 @@ # Licensed under the MIT License. See License.txt in the project root for # license information. # -------------------------------------------------------------------------- -# Azure-specific fixtures -# Common fixtures (testserver, core_library, key_credential, png_data, jpg_data) -# are inherited from the root tests/conftest.py - -import re -import urllib.parse import pytest -from typing import Literal, List +import re +from typing import Literal from pathlib import Path -from azure.core.rest import HttpRequest FILE_FOLDER = Path(__file__).parent @@ -52,6 +46,9 @@ def func(request, header: str, checked: dict): # ================== after azure-core fix, the following code can be removed (begin) ================== +import urllib.parse +from azure.core.rest import HttpRequest + def update_api_version_of_status_link(status_link: str): request_params = {} @@ -166,5 +163,17 @@ def authentication_policy(): @pytest.fixture -def special_words() -> List[str]: +def special_words() -> list[str]: return SPECIAL_WORDS + + +@pytest.fixture +def png_data() -> bytes: + with open(str(FILE_FOLDER / "data/image.png"), "rb") as file_in: + return file_in.read() + + +@pytest.fixture +def jpg_data() -> bytes: + with open(str(FILE_FOLDER / "data/image.jpg"), "rb") as file_in: + return file_in.read() diff --git a/packages/typespec-python/tests/mock_api/azure/test_azure_client_generator_core_alternate_type.py b/packages/typespec-python/tests/mock_api/azure/test_azure_client_generator_core_alternate_type.py deleted file mode 100644 index c5ae3266c8..0000000000 --- a/packages/typespec-python/tests/mock_api/azure/test_azure_client_generator_core_alternate_type.py +++ /dev/null @@ -1,69 +0,0 @@ -# ------------------------------------------------------------------------- -# Copyright (c) Microsoft Corporation. All rights reserved. -# Licensed under the MIT License. See License.txt in the project root for -# license information. -# -------------------------------------------------------------------------- -import pytest -import geojson -from specs.azure.clientgenerator.core.alternatetype import AlternateTypeClient -from specs.azure.clientgenerator.core.alternatetype import models - -# Shared test data -PROPERTIES = {"name": "A single point of interest", "category": "landmark", "elevation": 100} - -GEOMETRY = geojson.Point((-122.25, 37.87)) - -FEATURE_ID = "feature-1" - - -@pytest.fixture -def client(): - with AlternateTypeClient(endpoint="http://localhost:3000") as client: - yield client - - -@pytest.fixture -def feature_geojson(): - """Shared GeoJSON Feature for tests.""" - return geojson.Feature(type="Feature", geometry=GEOMETRY, properties=PROPERTIES, id=FEATURE_ID) - - -def test_external_type_get_model(client: AlternateTypeClient): - """Test getting a Feature object with geometry, properties, and optional id fields.""" - result = client.external_type.get_model() - - # Validate the response structure based on the TypeSpec example - assert result.type == "Feature" - assert result.geometry.type == "Point" - assert result.geometry.coordinates == [-122.25, 37.87] - assert result.properties == PROPERTIES - assert result.id == FEATURE_ID - - -def test_external_type_put_model(client: AlternateTypeClient, feature_geojson): - """Test putting a Feature object in request body.""" - # Should return None (204/empty response) - result = client.external_type.put_model(body=feature_geojson) - assert result is None - - -def test_external_type_get_property(client: AlternateTypeClient): - """Test getting a ModelWithFeatureProperty object with feature and additionalProperty fields.""" - result = client.external_type.get_property() - - # Validate the response structure based on the TypeSpec example - assert result.feature.type == "Feature" - assert result.feature.geometry.type == "Point" - assert result.feature.geometry.coordinates == [-122.25, 37.87] - assert result.feature.properties == PROPERTIES - assert result.feature.id == FEATURE_ID - assert result.additional_property == "extra" - - -def test_external_type_put_property(client: AlternateTypeClient, feature_geojson): - """Test putting a ModelWithFeatureProperty object in request body.""" - model_with_feature = models.ModelWithFeatureProperty(feature=feature_geojson, additional_property="extra") - - # Should return None (204/empty response) - result = client.external_type.put_property(body=model_with_feature) - assert result is None diff --git a/packages/typespec-python/tests/mock_api/azure/test_clear_output_folder.py b/packages/typespec-python/tests/mock_api/azure/test_clear_output_folder.py index 00125fde0d..9ae9307c19 100644 --- a/packages/typespec-python/tests/mock_api/azure/test_clear_output_folder.py +++ b/packages/typespec-python/tests/mock_api/azure/test_clear_output_folder.py @@ -14,7 +14,6 @@ def test_clear_output_folder(): assert not (folder / "to_be_deleted.py").exists(), "File to_be_deleted.py should be deleted after regeneration" if (GENERATED_PATH / "generation-subdir").exists(): - assert (GENERATED_PATH / "generation-subdir/generated_tests").exists() assert not (GENERATED_PATH / "generation-subdir/generated_tests/to_be_deleted.py").exists() assert (GENERATED_PATH / "generation-subdir/generation/subdir/_generated").exists() diff --git a/packages/typespec-python/tests/mock_api/azure/test_client_structure.py b/packages/typespec-python/tests/mock_api/azure/test_client_structure.py index e83d1ce6d6..d28827376c 100644 --- a/packages/typespec-python/tests/mock_api/azure/test_client_structure.py +++ b/packages/typespec-python/tests/mock_api/azure/test_client_structure.py @@ -3,7 +3,6 @@ # Licensed under the MIT License. See License.txt in the project root for # license information. # -------------------------------------------------------------------------- -import pytest from client.structure.service.models import ClientType from client.structure.service import ServiceClient from client.structure.multiclient import ClientAClient, ClientBClient @@ -36,25 +35,23 @@ def test_structure_multiclient(): client_b.renamed_six() -@pytest.mark.skip(reason="will reopen the cases after upgrade `@azure-tools/typespec-client-generator-core` to 0.67.0") def test_structure_renamed_operation(): client = RenamedOperationClient(endpoint="http://localhost:3000", client=ClientType.RENAMED_OPERATION) client.renamed_one() client.renamed_three() client.renamed_five() - client.renamed_two() - client.renamed_four() - client.renamed_six() + client.group.renamed_two() + client.group.renamed_four() + client.group.renamed_six() -@pytest.mark.skip(reason="will reopen the cases after upgrade `@azure-tools/typespec-client-generator-core` to 0.67.0") def test_structure_two_operation_group(): client = TwoOperationGroupClient(endpoint="http://localhost:3000", client=ClientType.TWO_OPERATION_GROUP) - client.one() - client.three() - client.four() + client.group1.one() + client.group1.three() + client.group1.four() - client.two() - client.five() - client.six() + client.group2.two() + client.group2.five() + client.group2.six() diff --git a/packages/typespec-python/tests/mock_api/azure/test_client_structure_clientoperationgroup.py b/packages/typespec-python/tests/mock_api/azure/test_client_structure_clientoperationgroup.py index aa6d3c8a90..3990830ebd 100644 --- a/packages/typespec-python/tests/mock_api/azure/test_client_structure_clientoperationgroup.py +++ b/packages/typespec-python/tests/mock_api/azure/test_client_structure_clientoperationgroup.py @@ -3,27 +3,24 @@ # Licensed under the MIT License. See License.txt in the project root for # license information. # -------------------------------------------------------------------------- -import pytest from client.structure.clientoperationgroup.models import ClientType from client.structure.clientoperationgroup import FirstClient, SecondClient -@pytest.mark.skip(reason="will reopen the cases after upgrade `@azure-tools/typespec-client-generator-core` to 0.67.0") def test_first_client_operations(): client = FirstClient(endpoint="http://localhost:3000", client=ClientType.CLIENT_OPERATION_GROUP) client.one() - client.two() - client.three() + client.group3.two() + client.group3.three() - client.four() + client.group4.four() -@pytest.mark.skip(reason="will reopen the cases after upgrade `@azure-tools/typespec-client-generator-core` to 0.67.0") def test_second_client_operations(): client = SecondClient(endpoint="http://localhost:3000", client=ClientType.CLIENT_OPERATION_GROUP) client.five() - client.six() + client.group5.six() diff --git a/packages/typespec-python/tests/mock_api/shared/asynctests/test_authentication_async.py b/packages/typespec-python/tests/mock_api/shared/asynctests/test_authentication_async.py index 2276efaf9c..4be9d3a731 100644 --- a/packages/typespec-python/tests/mock_api/shared/asynctests/test_authentication_async.py +++ b/packages/typespec-python/tests/mock_api/shared/asynctests/test_authentication_async.py @@ -4,6 +4,7 @@ # license information. # -------------------------------------------------------------------------- import pytest +import pytest_asyncio from authentication.apikey.aio import ApiKeyClient from authentication.http.custom.aio import CustomClient from authentication.oauth2.aio import OAuth2Client @@ -14,7 +15,7 @@ # Utilities functions -@pytest.fixture +@pytest_asyncio.fixture async def api_key_client(key_credential): client = None @@ -41,7 +42,7 @@ async def get_token_info(*scopes, **kwargs): return FakeCredential() -@pytest.fixture +@pytest_asyncio.fixture async def oauth2_client(token_credential): client = None @@ -54,7 +55,7 @@ def _build_client(client_type): await client.close() -@pytest.fixture +@pytest_asyncio.fixture async def http_custom_client(key_credential): client = None diff --git a/packages/typespec-python/tests/mock_api/shared/asynctests/test_encode_array_async.py b/packages/typespec-python/tests/mock_api/shared/asynctests/test_encode_array_async.py index 925780cd4a..3ec7a56529 100644 --- a/packages/typespec-python/tests/mock_api/shared/asynctests/test_encode_array_async.py +++ b/packages/typespec-python/tests/mock_api/shared/asynctests/test_encode_array_async.py @@ -5,11 +5,12 @@ # -------------------------------------------------------------------------- import pytest +import pytest_asyncio from encode.array.aio import ArrayClient from encode.array import models -@pytest.fixture +@pytest_asyncio.fixture async def client(): async with ArrayClient() as client: yield client diff --git a/packages/typespec-python/tests/mock_api/shared/asynctests/test_encode_bytes_async.py b/packages/typespec-python/tests/mock_api/shared/asynctests/test_encode_bytes_async.py index 584b277edd..5c57cb4281 100644 --- a/packages/typespec-python/tests/mock_api/shared/asynctests/test_encode_bytes_async.py +++ b/packages/typespec-python/tests/mock_api/shared/asynctests/test_encode_bytes_async.py @@ -4,6 +4,7 @@ # license information. # -------------------------------------------------------------------------- import pytest +import pytest_asyncio from pathlib import Path from encode.bytes.aio import BytesClient from encode.bytes.models import ( @@ -16,7 +17,7 @@ FILE_FOLDER = Path(__file__).parent.parent -@pytest.fixture +@pytest_asyncio.fixture async def client(): async with BytesClient() as client: yield client diff --git a/packages/typespec-python/tests/mock_api/shared/asynctests/test_encode_datetime_async.py b/packages/typespec-python/tests/mock_api/shared/asynctests/test_encode_datetime_async.py index 1249b8e8f3..bd5b590a09 100644 --- a/packages/typespec-python/tests/mock_api/shared/asynctests/test_encode_datetime_async.py +++ b/packages/typespec-python/tests/mock_api/shared/asynctests/test_encode_datetime_async.py @@ -6,6 +6,7 @@ import datetime import pytest +import pytest_asyncio from encode.datetime.aio import DatetimeClient from encode.datetime.models import ( DefaultDatetimeProperty, @@ -16,7 +17,7 @@ ) -@pytest.fixture +@pytest_asyncio.fixture async def client(): async with DatetimeClient() as client: yield client diff --git a/packages/typespec-python/tests/mock_api/shared/asynctests/test_generation_subdir2_for_customized_code_async.py b/packages/typespec-python/tests/mock_api/shared/asynctests/test_generation_subdir2_for_customized_code_async.py new file mode 100644 index 0000000000..bbc02dba59 --- /dev/null +++ b/packages/typespec-python/tests/mock_api/shared/asynctests/test_generation_subdir2_for_customized_code_async.py @@ -0,0 +1,37 @@ +# ------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# -------------------------------------------------------------------------- +import pytest +import pytest_asyncio +from generation.subdir2.aio import AddedClient +from generation.subdir2 import ModelV1, ModelV2, EnumV1, EnumV2 + + +@pytest_asyncio.fixture +async def client(): + async with AddedClient(endpoint="http://localhost:3000", version="v2") as client: + yield client + + +@pytest.mark.asyncio +async def test_v1(client: AddedClient): + assert await client.v1( + ModelV1(prop="foo", enum_prop=EnumV1.ENUM_MEMBER_V2, union_prop=10), + header_v2="bar", + ) == ModelV1(prop="foo", enum_prop=EnumV1.ENUM_MEMBER_V2, union_prop=10) + + +@pytest.mark.asyncio +async def test_v2(client: AddedClient): + assert await client.v2(ModelV2(prop="foo", enum_prop=EnumV2.ENUM_MEMBER, union_prop="bar")) == ModelV2( + prop="foo", enum_prop=EnumV2.ENUM_MEMBER, union_prop="bar" + ) + + +@pytest.mark.asyncio +async def test_interface_v2(client: AddedClient): + assert await client.interface_v2.v2_in_interface( + ModelV2(prop="foo", enum_prop=EnumV2.ENUM_MEMBER, union_prop="bar") + ) == ModelV2(prop="foo", enum_prop=EnumV2.ENUM_MEMBER, union_prop="bar") diff --git a/packages/typespec-python/tests/mock_api/shared/asynctests/test_generation_subdir2_for_generated_code_async.py b/packages/typespec-python/tests/mock_api/shared/asynctests/test_generation_subdir2_for_generated_code_async.py new file mode 100644 index 0000000000..f7fa5f9d08 --- /dev/null +++ b/packages/typespec-python/tests/mock_api/shared/asynctests/test_generation_subdir2_for_generated_code_async.py @@ -0,0 +1,37 @@ +# ------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# -------------------------------------------------------------------------- +import pytest +import pytest_asyncio +from generation.subdir2._generated.aio import AddedClient +from generation.subdir2._generated.models import ModelV1, ModelV2, EnumV1, EnumV2 + + +@pytest_asyncio.fixture +async def client(): + async with AddedClient(endpoint="http://localhost:3000", version="v2") as client: + yield client + + +@pytest.mark.asyncio +async def test_v1(client: AddedClient): + assert await client.v1( + ModelV1(prop="foo", enum_prop=EnumV1.ENUM_MEMBER_V2, union_prop=10), + header_v2="bar", + ) == ModelV1(prop="foo", enum_prop=EnumV1.ENUM_MEMBER_V2, union_prop=10) + + +@pytest.mark.asyncio +async def test_v2(client: AddedClient): + assert await client.v2(ModelV2(prop="foo", enum_prop=EnumV2.ENUM_MEMBER, union_prop="bar")) == ModelV2( + prop="foo", enum_prop=EnumV2.ENUM_MEMBER, union_prop="bar" + ) + + +@pytest.mark.asyncio +async def test_interface_v2(client: AddedClient): + assert await client.interface_v2.v2_in_interface( + ModelV2(prop="foo", enum_prop=EnumV2.ENUM_MEMBER, union_prop="bar") + ) == ModelV2(prop="foo", enum_prop=EnumV2.ENUM_MEMBER, union_prop="bar") diff --git a/packages/typespec-python/tests/mock_api/shared/asynctests/test_generation_subdir_for_customized_code_async.py b/packages/typespec-python/tests/mock_api/shared/asynctests/test_generation_subdir_for_customized_code_async.py new file mode 100644 index 0000000000..f992bd973d --- /dev/null +++ b/packages/typespec-python/tests/mock_api/shared/asynctests/test_generation_subdir_for_customized_code_async.py @@ -0,0 +1,19 @@ +# ------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# -------------------------------------------------------------------------- +import pytest +from generation.subdir import Extension +from generation.subdir.aio import CustomizedClient + + +@pytest.mark.asyncio +async def test_custom_method(): + client = CustomizedClient() + assert (await client.customized_get()) == Extension( + { + "level": 0, + "extension": [{"level": 1, "extension": [{"level": 2}]}, {"level": 1}], + } + ) diff --git a/packages/typespec-python/tests/mock_api/shared/asynctests/test_generation_subdir_async.py b/packages/typespec-python/tests/mock_api/shared/asynctests/test_generation_subdir_for_generated_code_async.py similarity index 100% rename from packages/typespec-python/tests/mock_api/shared/asynctests/test_generation_subdir_async.py rename to packages/typespec-python/tests/mock_api/shared/asynctests/test_generation_subdir_for_generated_code_async.py diff --git a/packages/typespec-python/tests/mock_api/shared/asynctests/test_headasboolean_async.py b/packages/typespec-python/tests/mock_api/shared/asynctests/test_headasboolean_async.py index c0f6919871..d2c2f10da0 100644 --- a/packages/typespec-python/tests/mock_api/shared/asynctests/test_headasboolean_async.py +++ b/packages/typespec-python/tests/mock_api/shared/asynctests/test_headasboolean_async.py @@ -4,6 +4,7 @@ # license information. # -------------------------------------------------------------------------- import pytest +import pytest_asyncio from headasbooleantrue.aio import VisibilityClient as HeadAsBooleanTrueClient from headasbooleantrue import models as models_true @@ -11,13 +12,13 @@ from headasbooleanfalse import models as models_false -@pytest.fixture +@pytest_asyncio.fixture async def client_true(): async with HeadAsBooleanTrueClient() as client: yield client -@pytest.fixture +@pytest_asyncio.fixture async def client_false(): async with HeadAsBooleanFalseClient() as client: yield client diff --git a/packages/typespec-python/tests/mock_api/shared/asynctests/test_parameters_body_optionality_async.py b/packages/typespec-python/tests/mock_api/shared/asynctests/test_parameters_body_optionality_async.py index 7c6bbe82c8..47bc79fd47 100644 --- a/packages/typespec-python/tests/mock_api/shared/asynctests/test_parameters_body_optionality_async.py +++ b/packages/typespec-python/tests/mock_api/shared/asynctests/test_parameters_body_optionality_async.py @@ -4,11 +4,12 @@ # license information. # -------------------------------------------------------------------------- import pytest +import pytest_asyncio from parameters.bodyoptionality.aio import BodyOptionalityClient from parameters.bodyoptionality.models import BodyModel -@pytest.fixture +@pytest_asyncio.fixture async def client(): async with BodyOptionalityClient() as client: yield client diff --git a/packages/typespec-python/tests/mock_api/shared/asynctests/test_parameters_collection_format_async.py b/packages/typespec-python/tests/mock_api/shared/asynctests/test_parameters_collection_format_async.py index c3ae969807..6ed93c4651 100644 --- a/packages/typespec-python/tests/mock_api/shared/asynctests/test_parameters_collection_format_async.py +++ b/packages/typespec-python/tests/mock_api/shared/asynctests/test_parameters_collection_format_async.py @@ -4,10 +4,11 @@ # license information. # -------------------------------------------------------------------------- import pytest +import pytest_asyncio from parameters.collectionformat.aio import CollectionFormatClient -@pytest.fixture +@pytest_asyncio.fixture async def client(): async with CollectionFormatClient() as client: yield client diff --git a/packages/typespec-python/tests/mock_api/shared/asynctests/test_parameters_path_async.py b/packages/typespec-python/tests/mock_api/shared/asynctests/test_parameters_path_async.py index eeef36a301..aee4b15005 100644 --- a/packages/typespec-python/tests/mock_api/shared/asynctests/test_parameters_path_async.py +++ b/packages/typespec-python/tests/mock_api/shared/asynctests/test_parameters_path_async.py @@ -4,10 +4,11 @@ # license information. # -------------------------------------------------------------------------- import pytest +import pytest_asyncio from parameters.path.aio import PathClient -@pytest.fixture +@pytest_asyncio.fixture async def client(): async with PathClient() as client: yield client diff --git a/packages/typespec-python/tests/mock_api/shared/asynctests/test_parameters_query_async.py b/packages/typespec-python/tests/mock_api/shared/asynctests/test_parameters_query_async.py index d5879ebdc2..70fb18603d 100644 --- a/packages/typespec-python/tests/mock_api/shared/asynctests/test_parameters_query_async.py +++ b/packages/typespec-python/tests/mock_api/shared/asynctests/test_parameters_query_async.py @@ -4,10 +4,11 @@ # license information. # -------------------------------------------------------------------------- import pytest +import pytest_asyncio from parameters.query.aio import QueryClient -@pytest.fixture +@pytest_asyncio.fixture async def client(): async with QueryClient() as client: yield client diff --git a/packages/typespec-python/tests/mock_api/shared/asynctests/test_payload_json_merge_patch_async.py b/packages/typespec-python/tests/mock_api/shared/asynctests/test_payload_json_merge_patch_async.py index e276944be3..54709bed05 100644 --- a/packages/typespec-python/tests/mock_api/shared/asynctests/test_payload_json_merge_patch_async.py +++ b/packages/typespec-python/tests/mock_api/shared/asynctests/test_payload_json_merge_patch_async.py @@ -4,6 +4,7 @@ # license information. # -------------------------------------------------------------------------- import pytest +import pytest_asyncio from payload.jsonmergepatch.aio import JsonMergePatchClient from payload.jsonmergepatch.models import InnerModel, Resource, ResourcePatch @@ -13,7 +14,7 @@ from corehttp.serialization import NULL -@pytest.fixture +@pytest_asyncio.fixture async def client(): async with JsonMergePatchClient(endpoint="http://localhost:3000") as client: yield client diff --git a/packages/typespec-python/tests/mock_api/shared/asynctests/test_payload_media_type_async.py b/packages/typespec-python/tests/mock_api/shared/asynctests/test_payload_media_type_async.py index d783e6ac61..551d71bc42 100644 --- a/packages/typespec-python/tests/mock_api/shared/asynctests/test_payload_media_type_async.py +++ b/packages/typespec-python/tests/mock_api/shared/asynctests/test_payload_media_type_async.py @@ -4,10 +4,11 @@ # license information. # -------------------------------------------------------------------------- import pytest +import pytest_asyncio from payload.mediatype.aio import MediaTypeClient -@pytest.fixture +@pytest_asyncio.fixture async def client(): async with MediaTypeClient(endpoint="http://localhost:3000") as client: yield client diff --git a/packages/typespec-python/tests/mock_api/shared/asynctests/test_payload_pageable_async.py b/packages/typespec-python/tests/mock_api/shared/asynctests/test_payload_pageable_async.py index f0994df97b..900caa49ed 100644 --- a/packages/typespec-python/tests/mock_api/shared/asynctests/test_payload_pageable_async.py +++ b/packages/typespec-python/tests/mock_api/shared/asynctests/test_payload_pageable_async.py @@ -4,11 +4,12 @@ # license information. # -------------------------------------------------------------------------- import pytest +import pytest_asyncio from payload.pageable.aio import PageableClient from payload.pageable.serverdrivenpagination.alternateinitialverb.models import Filter -@pytest.fixture +@pytest_asyncio.fixture async def client(): async with PageableClient(endpoint="http://localhost:3000") as client: yield client diff --git a/packages/typespec-python/tests/mock_api/shared/asynctests/test_payload_xml_async.py b/packages/typespec-python/tests/mock_api/shared/asynctests/test_payload_xml_async.py index 6cf981a0e4..8ae1cc69ad 100644 --- a/packages/typespec-python/tests/mock_api/shared/asynctests/test_payload_xml_async.py +++ b/packages/typespec-python/tests/mock_api/shared/asynctests/test_payload_xml_async.py @@ -5,6 +5,7 @@ # -------------------------------------------------------------------------- import datetime import pytest +import pytest_asyncio from payload.xml.aio import XmlClient from payload.xml.models import ( Author, @@ -37,7 +38,7 @@ ) -@pytest.fixture +@pytest_asyncio.fixture async def client(): async with XmlClient(endpoint="http://localhost:3000") as client: yield client diff --git a/packages/typespec-python/tests/mock_api/shared/asynctests/test_response_status_code_range_async.py b/packages/typespec-python/tests/mock_api/shared/asynctests/test_response_status_code_range_async.py index 3d6faa0c1f..0dd34b667c 100644 --- a/packages/typespec-python/tests/mock_api/shared/asynctests/test_response_status_code_range_async.py +++ b/packages/typespec-python/tests/mock_api/shared/asynctests/test_response_status_code_range_async.py @@ -4,11 +4,12 @@ # license information. # -------------------------------------------------------------------------- import pytest +import pytest_asyncio from response.statuscoderange.aio import StatusCodeRangeClient from response.statuscoderange.models import ErrorInRange, NotFoundError -@pytest.fixture +@pytest_asyncio.fixture async def client(): async with StatusCodeRangeClient(endpoint="http://localhost:3000") as client: yield client diff --git a/packages/typespec-python/tests/mock_api/shared/asynctests/test_routes_async.py b/packages/typespec-python/tests/mock_api/shared/asynctests/test_routes_async.py index 277e57be3a..d3e34bc1f9 100644 --- a/packages/typespec-python/tests/mock_api/shared/asynctests/test_routes_async.py +++ b/packages/typespec-python/tests/mock_api/shared/asynctests/test_routes_async.py @@ -4,10 +4,11 @@ # license information. # -------------------------------------------------------------------------- import pytest +import pytest_asyncio from routes.aio import RoutesClient -@pytest.fixture +@pytest_asyncio.fixture async def client(): async with RoutesClient() as client: yield client diff --git a/packages/typespec-python/tests/mock_api/shared/asynctests/test_server_endpoint_not_defined_async.py b/packages/typespec-python/tests/mock_api/shared/asynctests/test_server_endpoint_not_defined_async.py index 148e61dd5c..a14997b71c 100644 --- a/packages/typespec-python/tests/mock_api/shared/asynctests/test_server_endpoint_not_defined_async.py +++ b/packages/typespec-python/tests/mock_api/shared/asynctests/test_server_endpoint_not_defined_async.py @@ -4,10 +4,11 @@ # license information. # -------------------------------------------------------------------------- import pytest +import pytest_asyncio from server.endpoint.notdefined.aio import NotDefinedClient -@pytest.fixture +@pytest_asyncio.fixture async def client(): async with NotDefinedClient(endpoint="http://localhost:3000") as client: yield client diff --git a/packages/typespec-python/tests/mock_api/shared/asynctests/test_server_path_multiple_async.py b/packages/typespec-python/tests/mock_api/shared/asynctests/test_server_path_multiple_async.py index bcdc53dd4f..50bc53b5e3 100644 --- a/packages/typespec-python/tests/mock_api/shared/asynctests/test_server_path_multiple_async.py +++ b/packages/typespec-python/tests/mock_api/shared/asynctests/test_server_path_multiple_async.py @@ -4,10 +4,11 @@ # license information. # -------------------------------------------------------------------------- import pytest +import pytest_asyncio from server.path.multiple.aio import MultipleClient -@pytest.fixture +@pytest_asyncio.fixture async def client(): async with MultipleClient(endpoint="http://localhost:3000") as client: yield client diff --git a/packages/typespec-python/tests/mock_api/shared/asynctests/test_server_path_single_async.py b/packages/typespec-python/tests/mock_api/shared/asynctests/test_server_path_single_async.py index d6ebe479f8..efcb703028 100644 --- a/packages/typespec-python/tests/mock_api/shared/asynctests/test_server_path_single_async.py +++ b/packages/typespec-python/tests/mock_api/shared/asynctests/test_server_path_single_async.py @@ -4,10 +4,11 @@ # license information. # -------------------------------------------------------------------------- import pytest +import pytest_asyncio from server.path.single.aio import SingleClient -@pytest.fixture +@pytest_asyncio.fixture async def client(): async with SingleClient(endpoint="http://localhost:3000") as client: yield client diff --git a/packages/typespec-python/tests/mock_api/shared/asynctests/test_server_versions_not_versioned_async.py b/packages/typespec-python/tests/mock_api/shared/asynctests/test_server_versions_not_versioned_async.py index 738364b6e4..14f7e4239d 100644 --- a/packages/typespec-python/tests/mock_api/shared/asynctests/test_server_versions_not_versioned_async.py +++ b/packages/typespec-python/tests/mock_api/shared/asynctests/test_server_versions_not_versioned_async.py @@ -4,10 +4,11 @@ # license information. # -------------------------------------------------------------------------- import pytest +import pytest_asyncio from server.versions.notversioned.aio import NotVersionedClient -@pytest.fixture +@pytest_asyncio.fixture async def client(): async with NotVersionedClient(endpoint="http://localhost:3000") as client: yield client diff --git a/packages/typespec-python/tests/mock_api/shared/asynctests/test_server_versions_versioned_async.py b/packages/typespec-python/tests/mock_api/shared/asynctests/test_server_versions_versioned_async.py index 53e7d194f4..8f96a2f550 100644 --- a/packages/typespec-python/tests/mock_api/shared/asynctests/test_server_versions_versioned_async.py +++ b/packages/typespec-python/tests/mock_api/shared/asynctests/test_server_versions_versioned_async.py @@ -4,10 +4,11 @@ # license information. # -------------------------------------------------------------------------- import pytest +import pytest_asyncio from server.versions.versioned.aio import VersionedClient -@pytest.fixture +@pytest_asyncio.fixture async def client(): async with VersionedClient(endpoint="http://localhost:3000") as client: yield client diff --git a/packages/typespec-python/tests/mock_api/shared/asynctests/test_special_headers_conditional_request_async.py b/packages/typespec-python/tests/mock_api/shared/asynctests/test_special_headers_conditional_request_async.py index e65b9d4e33..dc86ac0635 100644 --- a/packages/typespec-python/tests/mock_api/shared/asynctests/test_special_headers_conditional_request_async.py +++ b/packages/typespec-python/tests/mock_api/shared/asynctests/test_special_headers_conditional_request_async.py @@ -4,11 +4,12 @@ # license information. # -------------------------------------------------------------------------- import pytest +import pytest_asyncio import datetime from specialheaders.conditionalrequest.aio import ConditionalRequestClient -@pytest.fixture +@pytest_asyncio.fixture async def client(): async with ConditionalRequestClient() as client: yield client diff --git a/packages/typespec-python/tests/mock_api/shared/asynctests/test_special_headers_repeatability_async.py b/packages/typespec-python/tests/mock_api/shared/asynctests/test_special_headers_repeatability_async.py index dbf74414e1..941890ea23 100644 --- a/packages/typespec-python/tests/mock_api/shared/asynctests/test_special_headers_repeatability_async.py +++ b/packages/typespec-python/tests/mock_api/shared/asynctests/test_special_headers_repeatability_async.py @@ -4,10 +4,11 @@ # license information. # -------------------------------------------------------------------------- import pytest +import pytest_asyncio from specialheaders.repeatability.aio import RepeatabilityClient -@pytest.fixture +@pytest_asyncio.fixture async def client(): async with RepeatabilityClient() as client: yield client diff --git a/packages/typespec-python/tests/mock_api/shared/asynctests/test_specs_documentation_async.py b/packages/typespec-python/tests/mock_api/shared/asynctests/test_specs_documentation_async.py index 12e4cb2e5a..dff527765c 100644 --- a/packages/typespec-python/tests/mock_api/shared/asynctests/test_specs_documentation_async.py +++ b/packages/typespec-python/tests/mock_api/shared/asynctests/test_specs_documentation_async.py @@ -5,11 +5,12 @@ # -------------------------------------------------------------------------- import pytest +import pytest_asyncio from specs.documentation.aio import DocumentationClient from specs.documentation import models -@pytest.fixture +@pytest_asyncio.fixture async def client(): async with DocumentationClient(endpoint="http://localhost:3000") as client: yield client diff --git a/packages/typespec-python/tests/mock_api/shared/asynctests/test_streaming_jsonl_async.py b/packages/typespec-python/tests/mock_api/shared/asynctests/test_streaming_jsonl_async.py index 803215abd9..74e05cebd1 100644 --- a/packages/typespec-python/tests/mock_api/shared/asynctests/test_streaming_jsonl_async.py +++ b/packages/typespec-python/tests/mock_api/shared/asynctests/test_streaming_jsonl_async.py @@ -4,11 +4,12 @@ # license information. # -------------------------------------------------------------------------- import pytest +import pytest_asyncio from streaming.jsonl.aio import JsonlClient -@pytest.fixture +@pytest_asyncio.fixture async def client(): async with JsonlClient(endpoint="http://localhost:3000") as client: yield client diff --git a/packages/typespec-python/tests/mock_api/shared/asynctests/test_typetest_array_async.py b/packages/typespec-python/tests/mock_api/shared/asynctests/test_typetest_array_async.py index 180887ea76..66c3b24c1f 100644 --- a/packages/typespec-python/tests/mock_api/shared/asynctests/test_typetest_array_async.py +++ b/packages/typespec-python/tests/mock_api/shared/asynctests/test_typetest_array_async.py @@ -5,12 +5,13 @@ # -------------------------------------------------------------------------- import pytest +import pytest_asyncio import isodate from typetest.array.aio import ArrayClient from typetest.array import models -@pytest.fixture +@pytest_asyncio.fixture async def client(): async with ArrayClient() as client: yield client diff --git a/packages/typespec-python/tests/mock_api/shared/asynctests/test_typetest_dictionary_async.py b/packages/typespec-python/tests/mock_api/shared/asynctests/test_typetest_dictionary_async.py index 364868b235..76a57a1ba9 100644 --- a/packages/typespec-python/tests/mock_api/shared/asynctests/test_typetest_dictionary_async.py +++ b/packages/typespec-python/tests/mock_api/shared/asynctests/test_typetest_dictionary_async.py @@ -4,12 +4,13 @@ # license information. # -------------------------------------------------------------------------- import pytest +import pytest_asyncio from typetest.dictionary import models from typetest.dictionary.aio import DictionaryClient import isodate -@pytest.fixture +@pytest_asyncio.fixture async def client(): async with DictionaryClient() as client: yield client diff --git a/packages/typespec-python/tests/mock_api/shared/asynctests/test_typetest_enum_extensible_async.py b/packages/typespec-python/tests/mock_api/shared/asynctests/test_typetest_enum_extensible_async.py index 75fca822dd..aedc6e6a92 100644 --- a/packages/typespec-python/tests/mock_api/shared/asynctests/test_typetest_enum_extensible_async.py +++ b/packages/typespec-python/tests/mock_api/shared/asynctests/test_typetest_enum_extensible_async.py @@ -4,10 +4,11 @@ # license information. # -------------------------------------------------------------------------- import pytest +import pytest_asyncio from typetest.enum.extensible import models, aio -@pytest.fixture +@pytest_asyncio.fixture async def client(): async with aio.ExtensibleClient() as client: yield client diff --git a/packages/typespec-python/tests/mock_api/shared/asynctests/test_typetest_enum_fixed_async.py b/packages/typespec-python/tests/mock_api/shared/asynctests/test_typetest_enum_fixed_async.py index de17f194b6..5b6822fcfd 100644 --- a/packages/typespec-python/tests/mock_api/shared/asynctests/test_typetest_enum_fixed_async.py +++ b/packages/typespec-python/tests/mock_api/shared/asynctests/test_typetest_enum_fixed_async.py @@ -4,10 +4,11 @@ # license information. # -------------------------------------------------------------------------- import pytest +import pytest_asyncio from typetest.enum.fixed import aio, models -@pytest.fixture +@pytest_asyncio.fixture async def client(): async with aio.FixedClient() as client: yield client diff --git a/packages/typespec-python/tests/mock_api/shared/asynctests/test_typetest_model_empty_async.py b/packages/typespec-python/tests/mock_api/shared/asynctests/test_typetest_model_empty_async.py index b5518c5e90..030d3bd84f 100644 --- a/packages/typespec-python/tests/mock_api/shared/asynctests/test_typetest_model_empty_async.py +++ b/packages/typespec-python/tests/mock_api/shared/asynctests/test_typetest_model_empty_async.py @@ -4,11 +4,12 @@ # license information. # -------------------------------------------------------------------------- import pytest +import pytest_asyncio from typetest.model.empty.aio import EmptyClient from typetest.model.empty.models import EmptyInput, EmptyOutput, EmptyInputOutput -@pytest.fixture +@pytest_asyncio.fixture async def client(): async with EmptyClient() as client: yield client diff --git a/packages/typespec-python/tests/mock_api/shared/asynctests/test_typetest_model_inheritance_enum_discriminator_async.py b/packages/typespec-python/tests/mock_api/shared/asynctests/test_typetest_model_inheritance_enum_discriminator_async.py index 0a72d5465e..dc176b3fd9 100644 --- a/packages/typespec-python/tests/mock_api/shared/asynctests/test_typetest_model_inheritance_enum_discriminator_async.py +++ b/packages/typespec-python/tests/mock_api/shared/asynctests/test_typetest_model_inheritance_enum_discriminator_async.py @@ -4,11 +4,12 @@ # license information. # -------------------------------------------------------------------------- import pytest +import pytest_asyncio from typetest.model.enumdiscriminator.aio import EnumDiscriminatorClient from typetest.model.enumdiscriminator import models -@pytest.fixture +@pytest_asyncio.fixture async def client(): async with EnumDiscriminatorClient() as client: yield client diff --git a/packages/typespec-python/tests/mock_api/shared/asynctests/test_typetest_model_inheritance_nested_discriminator_async.py b/packages/typespec-python/tests/mock_api/shared/asynctests/test_typetest_model_inheritance_nested_discriminator_async.py index c641f25eb8..f6e57f5aff 100644 --- a/packages/typespec-python/tests/mock_api/shared/asynctests/test_typetest_model_inheritance_nested_discriminator_async.py +++ b/packages/typespec-python/tests/mock_api/shared/asynctests/test_typetest_model_inheritance_nested_discriminator_async.py @@ -4,17 +4,18 @@ # license information. # -------------------------------------------------------------------------- import pytest +import pytest_asyncio from typetest.model.nesteddiscriminator.aio import NestedDiscriminatorClient from typetest.model.nesteddiscriminator.models import GoblinShark, Salmon, Fish -@pytest.fixture +@pytest_asyncio.fixture async def client(): async with NestedDiscriminatorClient() as client: yield client -@pytest.fixture +@pytest_asyncio.fixture async def valid_body(): return GoblinShark(age=1) @@ -30,7 +31,7 @@ async def test_put_model(client, valid_body): await client.put_model(valid_body) -@pytest.fixture +@pytest_asyncio.fixture async def valid_recursive_body(): return Salmon( { diff --git a/packages/typespec-python/tests/mock_api/shared/asynctests/test_typetest_model_inheritance_not_discriminated_async.py b/packages/typespec-python/tests/mock_api/shared/asynctests/test_typetest_model_inheritance_not_discriminated_async.py index 7e7ce09695..5ed075e3d5 100644 --- a/packages/typespec-python/tests/mock_api/shared/asynctests/test_typetest_model_inheritance_not_discriminated_async.py +++ b/packages/typespec-python/tests/mock_api/shared/asynctests/test_typetest_model_inheritance_not_discriminated_async.py @@ -4,17 +4,18 @@ # license information. # -------------------------------------------------------------------------- import pytest +import pytest_asyncio from typetest.model.notdiscriminated.aio import NotDiscriminatedClient from typetest.model.notdiscriminated.models import Siamese -@pytest.fixture +@pytest_asyncio.fixture async def client(): async with NotDiscriminatedClient() as client: yield client -@pytest.fixture +@pytest_asyncio.fixture async def valid_body(): return Siamese(name="abc", age=32, smart=True) diff --git a/packages/typespec-python/tests/mock_api/shared/asynctests/test_typetest_model_inheritance_recursive_async.py b/packages/typespec-python/tests/mock_api/shared/asynctests/test_typetest_model_inheritance_recursive_async.py index aea6f1bb9e..3056c2c40a 100644 --- a/packages/typespec-python/tests/mock_api/shared/asynctests/test_typetest_model_inheritance_recursive_async.py +++ b/packages/typespec-python/tests/mock_api/shared/asynctests/test_typetest_model_inheritance_recursive_async.py @@ -4,17 +4,18 @@ # license information. # -------------------------------------------------------------------------- import pytest +import pytest_asyncio from typetest.model.recursive.aio import RecursiveClient from typetest.model.recursive.models import Extension -@pytest.fixture +@pytest_asyncio.fixture async def client(): async with RecursiveClient() as client: yield client -@pytest.fixture +@pytest_asyncio.fixture async def expected(): return Extension( { diff --git a/packages/typespec-python/tests/mock_api/shared/asynctests/test_typetest_model_inheritance_single_discriminator_async.py b/packages/typespec-python/tests/mock_api/shared/asynctests/test_typetest_model_inheritance_single_discriminator_async.py index dc98cd81c9..e6ddd9c44f 100644 --- a/packages/typespec-python/tests/mock_api/shared/asynctests/test_typetest_model_inheritance_single_discriminator_async.py +++ b/packages/typespec-python/tests/mock_api/shared/asynctests/test_typetest_model_inheritance_single_discriminator_async.py @@ -4,17 +4,18 @@ # license information. # -------------------------------------------------------------------------- import pytest +import pytest_asyncio from typetest.model.singlediscriminator.aio import SingleDiscriminatorClient from typetest.model.singlediscriminator.models import Sparrow, Eagle, Bird, Dinosaur -@pytest.fixture +@pytest_asyncio.fixture async def client(): async with SingleDiscriminatorClient() as client: yield client -@pytest.fixture +@pytest_asyncio.fixture async def valid_body(): return Sparrow(wingspan=1) @@ -29,7 +30,7 @@ async def test_put_model(client, valid_body): await client.put_model(valid_body) -@pytest.fixture +@pytest_asyncio.fixture async def recursive_body(): return Eagle( { diff --git a/packages/typespec-python/tests/mock_api/shared/asynctests/test_typetest_model_usage_async.py b/packages/typespec-python/tests/mock_api/shared/asynctests/test_typetest_model_usage_async.py index bcebeaec20..a7e0035403 100644 --- a/packages/typespec-python/tests/mock_api/shared/asynctests/test_typetest_model_usage_async.py +++ b/packages/typespec-python/tests/mock_api/shared/asynctests/test_typetest_model_usage_async.py @@ -4,11 +4,12 @@ # license information. # -------------------------------------------------------------------------- import pytest +import pytest_asyncio from typetest.model.usage import models from typetest.model.usage.aio import UsageClient -@pytest.fixture +@pytest_asyncio.fixture async def client(): async with UsageClient() as client: yield client diff --git a/packages/typespec-python/tests/mock_api/shared/asynctests/test_typetest_model_visibility_async.py b/packages/typespec-python/tests/mock_api/shared/asynctests/test_typetest_model_visibility_async.py index 27fb23df8a..6282769581 100644 --- a/packages/typespec-python/tests/mock_api/shared/asynctests/test_typetest_model_visibility_async.py +++ b/packages/typespec-python/tests/mock_api/shared/asynctests/test_typetest_model_visibility_async.py @@ -4,11 +4,12 @@ # license information. # -------------------------------------------------------------------------- import pytest +import pytest_asyncio from typetest.model.visibility.aio import VisibilityClient from typetest.model.visibility import models -@pytest.fixture +@pytest_asyncio.fixture async def client(): async with VisibilityClient() as client: yield client diff --git a/packages/typespec-python/tests/mock_api/shared/asynctests/test_typetest_property_additionalproperties_async.py b/packages/typespec-python/tests/mock_api/shared/asynctests/test_typetest_property_additionalproperties_async.py index 2109ea3179..3bded493a0 100644 --- a/packages/typespec-python/tests/mock_api/shared/asynctests/test_typetest_property_additionalproperties_async.py +++ b/packages/typespec-python/tests/mock_api/shared/asynctests/test_typetest_property_additionalproperties_async.py @@ -4,11 +4,12 @@ # license information. # -------------------------------------------------------------------------- import pytest +import pytest_asyncio from typetest.property.additionalproperties import models from typetest.property.additionalproperties.aio import AdditionalPropertiesClient -@pytest.fixture +@pytest_asyncio.fixture async def client(): async with AdditionalPropertiesClient() as client: yield client @@ -262,22 +263,6 @@ async def test_spread_model_array(client: AdditionalPropertiesClient): await client.spread_model_array.put(body) -@pytest.mark.skip(reason="https://github.com/microsoft/typespec/pull/6425") -@pytest.mark.asyncio -async def test_spread_record_discriminated_union(client: AdditionalPropertiesClient): - body = { - "name": "abc", - "prop1": {"fooProp": "abc", "kind": "kind0"}, - "prop2": { - "end": "2021-01-02T00:00:00Z", - "kind": "kind1", - "start": "2021-01-01T00:00:00Z", - }, - } - assert await client.spread_record_discriminated_union.get() == body - await client.spread_record_discriminated_union.put(body) - - @pytest.mark.asyncio async def test_spread_record_non_discriminated_union( client: AdditionalPropertiesClient, diff --git a/packages/typespec-python/tests/mock_api/shared/asynctests/test_typetest_property_nullable_async.py b/packages/typespec-python/tests/mock_api/shared/asynctests/test_typetest_property_nullable_async.py index a5dc132211..f54cafb464 100644 --- a/packages/typespec-python/tests/mock_api/shared/asynctests/test_typetest_property_nullable_async.py +++ b/packages/typespec-python/tests/mock_api/shared/asynctests/test_typetest_property_nullable_async.py @@ -6,6 +6,7 @@ # -------------------------------------------------------------------------- import json import pytest +import pytest_asyncio from typetest.property.nullable import models from typetest.property.nullable.aio import NullableClient from typetest.property.nullable._utils.model_base import ( # pylint: disable=protected-access @@ -18,7 +19,7 @@ from azure.core.serialization import NULL -@pytest.fixture +@pytest_asyncio.fixture async def client(): async with NullableClient() as client: yield client diff --git a/packages/typespec-python/tests/mock_api/shared/asynctests/test_typetest_property_optional_async.py b/packages/typespec-python/tests/mock_api/shared/asynctests/test_typetest_property_optional_async.py index e7ec09d005..51d8bd362e 100644 --- a/packages/typespec-python/tests/mock_api/shared/asynctests/test_typetest_property_optional_async.py +++ b/packages/typespec-python/tests/mock_api/shared/asynctests/test_typetest_property_optional_async.py @@ -6,11 +6,12 @@ # -------------------------------------------------------------------------- from typing import Any import pytest +import pytest_asyncio from typetest.property.optional import models from typetest.property.optional.aio import OptionalClient -@pytest.fixture +@pytest_asyncio.fixture async def client(): async with OptionalClient() as client: yield client diff --git a/packages/typespec-python/tests/mock_api/shared/asynctests/test_typetest_property_valuetypes_async.py b/packages/typespec-python/tests/mock_api/shared/asynctests/test_typetest_property_valuetypes_async.py index 1b7566c573..15a3e794e1 100644 --- a/packages/typespec-python/tests/mock_api/shared/asynctests/test_typetest_property_valuetypes_async.py +++ b/packages/typespec-python/tests/mock_api/shared/asynctests/test_typetest_property_valuetypes_async.py @@ -7,12 +7,13 @@ import decimal import pytest +import pytest_asyncio import datetime from typetest.property.valuetypes import models from typetest.property.valuetypes.aio import ValueTypesClient -@pytest.fixture +@pytest_asyncio.fixture async def client(): async with ValueTypesClient() as client: yield client diff --git a/packages/typespec-python/tests/mock_api/shared/asynctests/test_typetest_scalar_async.py b/packages/typespec-python/tests/mock_api/shared/asynctests/test_typetest_scalar_async.py index 3e2b308f8b..674ee0e0d4 100644 --- a/packages/typespec-python/tests/mock_api/shared/asynctests/test_typetest_scalar_async.py +++ b/packages/typespec-python/tests/mock_api/shared/asynctests/test_typetest_scalar_async.py @@ -7,10 +7,11 @@ from functools import reduce import pytest +import pytest_asyncio from typetest.scalar.aio import ScalarClient -@pytest.fixture +@pytest_asyncio.fixture async def client(): async with ScalarClient() as client: yield client diff --git a/packages/typespec-python/tests/mock_api/shared/asynctests/test_typetest_union_async.py b/packages/typespec-python/tests/mock_api/shared/asynctests/test_typetest_union_async.py index 9e738701f4..18b5e1aba9 100644 --- a/packages/typespec-python/tests/mock_api/shared/asynctests/test_typetest_union_async.py +++ b/packages/typespec-python/tests/mock_api/shared/asynctests/test_typetest_union_async.py @@ -4,11 +4,12 @@ # license information. # -------------------------------------------------------------------------- import pytest +import pytest_asyncio from typetest.union.aio import UnionClient from typetest.union import models -@pytest.fixture +@pytest_asyncio.fixture async def client(): async with UnionClient() as client: yield client diff --git a/packages/typespec-python/tests/mock_api/shared/asynctests/test_versioning_added_async.py b/packages/typespec-python/tests/mock_api/shared/asynctests/test_versioning_added_async.py index 27e78a8892..8906113b96 100644 --- a/packages/typespec-python/tests/mock_api/shared/asynctests/test_versioning_added_async.py +++ b/packages/typespec-python/tests/mock_api/shared/asynctests/test_versioning_added_async.py @@ -4,11 +4,12 @@ # license information. # -------------------------------------------------------------------------- import pytest +import pytest_asyncio from versioning.added.aio import AddedClient from versioning.added.models import ModelV1, ModelV2, EnumV1, EnumV2 -@pytest.fixture +@pytest_asyncio.fixture async def client(): async with AddedClient(endpoint="http://localhost:3000", version="v2") as client: yield client diff --git a/packages/typespec-python/tests/mock_api/shared/asynctests/test_versioning_made_optional_async.py b/packages/typespec-python/tests/mock_api/shared/asynctests/test_versioning_made_optional_async.py index 27ec811cae..c126d40116 100644 --- a/packages/typespec-python/tests/mock_api/shared/asynctests/test_versioning_made_optional_async.py +++ b/packages/typespec-python/tests/mock_api/shared/asynctests/test_versioning_made_optional_async.py @@ -4,11 +4,12 @@ # license information. # -------------------------------------------------------------------------- import pytest +import pytest_asyncio from versioning.madeoptional.aio import MadeOptionalClient from versioning.madeoptional.models import TestModel -@pytest.fixture +@pytest_asyncio.fixture async def client(): async with MadeOptionalClient(endpoint="http://localhost:3000", version="v2") as client: yield client diff --git a/packages/typespec-python/tests/mock_api/shared/asynctests/test_versioning_removed_async.py b/packages/typespec-python/tests/mock_api/shared/asynctests/test_versioning_removed_async.py index 5442c56e6c..22dab517fd 100644 --- a/packages/typespec-python/tests/mock_api/shared/asynctests/test_versioning_removed_async.py +++ b/packages/typespec-python/tests/mock_api/shared/asynctests/test_versioning_removed_async.py @@ -4,11 +4,12 @@ # license information. # -------------------------------------------------------------------------- import pytest +import pytest_asyncio from versioning.removed.aio import RemovedClient from versioning.removed.models import ModelV2, EnumV2, ModelV3, EnumV3 -@pytest.fixture +@pytest_asyncio.fixture async def client(): async with RemovedClient(endpoint="http://localhost:3000", version="v2") as client: yield client diff --git a/packages/typespec-python/tests/mock_api/shared/asynctests/test_versioning_renamed_from_async.py b/packages/typespec-python/tests/mock_api/shared/asynctests/test_versioning_renamed_from_async.py index 46ef588f65..1e738c2132 100644 --- a/packages/typespec-python/tests/mock_api/shared/asynctests/test_versioning_renamed_from_async.py +++ b/packages/typespec-python/tests/mock_api/shared/asynctests/test_versioning_renamed_from_async.py @@ -4,11 +4,12 @@ # license information. # -------------------------------------------------------------------------- import pytest +import pytest_asyncio from versioning.renamedfrom.aio import RenamedFromClient from versioning.renamedfrom.models import NewModel, NewEnum -@pytest.fixture +@pytest_asyncio.fixture async def client(): async with RenamedFromClient(endpoint="http://localhost:3000", version="v2") as client: yield client diff --git a/packages/typespec-python/tests/mock_api/shared/asynctests/test_versioning_return_type_changed_from_async.py b/packages/typespec-python/tests/mock_api/shared/asynctests/test_versioning_return_type_changed_from_async.py index 80eefecbe2..e6234f6960 100644 --- a/packages/typespec-python/tests/mock_api/shared/asynctests/test_versioning_return_type_changed_from_async.py +++ b/packages/typespec-python/tests/mock_api/shared/asynctests/test_versioning_return_type_changed_from_async.py @@ -4,10 +4,11 @@ # license information. # -------------------------------------------------------------------------- import pytest +import pytest_asyncio from versioning.returntypechangedfrom.aio import ReturnTypeChangedFromClient -@pytest.fixture +@pytest_asyncio.fixture async def client(): async with ReturnTypeChangedFromClient(endpoint="http://localhost:3000", version="v2") as client: yield client diff --git a/packages/typespec-python/tests/mock_api/shared/asynctests/test_versioning_type_changed_from_async.py b/packages/typespec-python/tests/mock_api/shared/asynctests/test_versioning_type_changed_from_async.py index 2374bd1f55..6bf75a87be 100644 --- a/packages/typespec-python/tests/mock_api/shared/asynctests/test_versioning_type_changed_from_async.py +++ b/packages/typespec-python/tests/mock_api/shared/asynctests/test_versioning_type_changed_from_async.py @@ -4,11 +4,12 @@ # license information. # -------------------------------------------------------------------------- import pytest +import pytest_asyncio from versioning.typechangedfrom.aio import TypeChangedFromClient from versioning.typechangedfrom.models import TestModel -@pytest.fixture +@pytest_asyncio.fixture async def client(): async with TypeChangedFromClient(endpoint="http://localhost:3000", version="v2") as client: yield client diff --git a/packages/typespec-python/tests/mock_api/shared/conftest.py b/packages/typespec-python/tests/mock_api/shared/conftest.py index 6b55185794..0f5685c338 100644 --- a/packages/typespec-python/tests/mock_api/shared/conftest.py +++ b/packages/typespec-python/tests/mock_api/shared/conftest.py @@ -3,5 +3,41 @@ # Licensed under the MIT License. See License.txt in the project root for # license information. # -------------------------------------------------------------------------- -# Shared fixtures are inherited from the root tests/conftest.py -# This file can contain additional shared fixtures if needed +import pytest +import importlib +from pathlib import Path + +DATA_FOLDER = Path(__file__).parent.parent + + +""" +Use to disambiguate the core library we use +""" + + +@pytest.fixture +def core_library(): + try: + return importlib.import_module("azure.core") + except ModuleNotFoundError: + return importlib.import_module("corehttp") + + +@pytest.fixture +def key_credential(core_library): + try: + return core_library.credentials.AzureKeyCredential + except AttributeError: + return core_library.credentials.ServiceKeyCredential + + +@pytest.fixture +def png_data() -> bytes: + with open(str(DATA_FOLDER / "data/image.png"), "rb") as file_in: + return file_in.read() + + +@pytest.fixture +def jpg_data() -> bytes: + with open(str(DATA_FOLDER / "data/image.jpg"), "rb") as file_in: + return file_in.read() diff --git a/packages/typespec-python/tests/mock_api/shared/test_generation_subdir2_for_customized_code.py b/packages/typespec-python/tests/mock_api/shared/test_generation_subdir2_for_customized_code.py new file mode 100644 index 0000000000..9a90f290d3 --- /dev/null +++ b/packages/typespec-python/tests/mock_api/shared/test_generation_subdir2_for_customized_code.py @@ -0,0 +1,32 @@ +# ------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# -------------------------------------------------------------------------- +import pytest +from generation.subdir2 import AddedClient, ModelV1, ModelV2, EnumV1, EnumV2 + + +@pytest.fixture +def client(): + with AddedClient(endpoint="http://localhost:3000", version="v2") as client: + yield client + + +def test_v1(client: AddedClient): + assert client.v1( + ModelV1(prop="foo", enum_prop=EnumV1.ENUM_MEMBER_V2, union_prop=10), + header_v2="bar", + ) == ModelV1(prop="foo", enum_prop=EnumV1.ENUM_MEMBER_V2, union_prop=10) + + +def test_v2(client: AddedClient): + assert client.v2(ModelV2(prop="foo", enum_prop=EnumV2.ENUM_MEMBER, union_prop="bar")) == ModelV2( + prop="foo", enum_prop=EnumV2.ENUM_MEMBER, union_prop="bar" + ) + + +def test_interface_v2(client: AddedClient): + assert client.interface_v2.v2_in_interface( + ModelV2(prop="foo", enum_prop=EnumV2.ENUM_MEMBER, union_prop="bar") + ) == ModelV2(prop="foo", enum_prop=EnumV2.ENUM_MEMBER, union_prop="bar") diff --git a/packages/typespec-python/tests/mock_api/shared/test_generation_subdir2_for_generated_code.py b/packages/typespec-python/tests/mock_api/shared/test_generation_subdir2_for_generated_code.py new file mode 100644 index 0000000000..32bbfc784d --- /dev/null +++ b/packages/typespec-python/tests/mock_api/shared/test_generation_subdir2_for_generated_code.py @@ -0,0 +1,33 @@ +# ------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# -------------------------------------------------------------------------- +import pytest +from generation.subdir2._generated import AddedClient +from generation.subdir2._generated.models import ModelV1, ModelV2, EnumV1, EnumV2 + + +@pytest.fixture +def client(): + with AddedClient(endpoint="http://localhost:3000", version="v2") as client: + yield client + + +def test_v1(client: AddedClient): + assert client.v1( + ModelV1(prop="foo", enum_prop=EnumV1.ENUM_MEMBER_V2, union_prop=10), + header_v2="bar", + ) == ModelV1(prop="foo", enum_prop=EnumV1.ENUM_MEMBER_V2, union_prop=10) + + +def test_v2(client: AddedClient): + assert client.v2(ModelV2(prop="foo", enum_prop=EnumV2.ENUM_MEMBER, union_prop="bar")) == ModelV2( + prop="foo", enum_prop=EnumV2.ENUM_MEMBER, union_prop="bar" + ) + + +def test_interface_v2(client: AddedClient): + assert client.interface_v2.v2_in_interface( + ModelV2(prop="foo", enum_prop=EnumV2.ENUM_MEMBER, union_prop="bar") + ) == ModelV2(prop="foo", enum_prop=EnumV2.ENUM_MEMBER, union_prop="bar") \ No newline at end of file diff --git a/packages/typespec-python/tests/mock_api/shared/test_generation_subdir_for_customized_code.py b/packages/typespec-python/tests/mock_api/shared/test_generation_subdir_for_customized_code.py new file mode 100644 index 0000000000..d2b7460818 --- /dev/null +++ b/packages/typespec-python/tests/mock_api/shared/test_generation_subdir_for_customized_code.py @@ -0,0 +1,16 @@ +# ------------------------------------------------------------------------- +# Copyright (c) Microsoft Corporation. All rights reserved. +# Licensed under the MIT License. See License.txt in the project root for +# license information. +# -------------------------------------------------------------------------- +from generation.subdir import CustomizedClient, Extension + + +def test_custom_method(): + client = CustomizedClient() + assert client.customized_get() == Extension( + { + "level": 0, + "extension": [{"level": 1, "extension": [{"level": 2}]}, {"level": 1}], + } + ) diff --git a/packages/typespec-python/tests/mock_api/shared/test_generation_subdir.py b/packages/typespec-python/tests/mock_api/shared/test_generation_subdir_for_generated_code.py similarity index 99% rename from packages/typespec-python/tests/mock_api/shared/test_generation_subdir.py rename to packages/typespec-python/tests/mock_api/shared/test_generation_subdir_for_generated_code.py index 661fbaecf2..3750a98f1a 100644 --- a/packages/typespec-python/tests/mock_api/shared/test_generation_subdir.py +++ b/packages/typespec-python/tests/mock_api/shared/test_generation_subdir_for_generated_code.py @@ -14,4 +14,4 @@ def test_custom_method(): "level": 0, "extension": [{"level": 1, "extension": [{"level": 2}]}, {"level": 1}], } - ) + ) \ No newline at end of file diff --git a/packages/typespec-python/tests/mock_api/shared/test_patch.py b/packages/typespec-python/tests/mock_api/shared/test_patch.py index fc92290a71..8410e4046b 100644 --- a/packages/typespec-python/tests/mock_api/shared/test_patch.py +++ b/packages/typespec-python/tests/mock_api/shared/test_patch.py @@ -9,4 +9,4 @@ def test_patch_mixin_operation_group_in_operations_folder(key_credential): from authentication.apikey import ApiKeyClient, aio assert hasattr(ApiKeyClient(key_credential), "patch_added_operation") - assert hasattr(aio.ApiKeyClient(key_credential), "patch_added_operation") + assert hasattr(aio.ApiKeyClient(key_credential), "patch_added_operation") \ No newline at end of file diff --git a/packages/typespec-python/tests/mock_api/shared/test_typetest_property_additionalproperties.py b/packages/typespec-python/tests/mock_api/shared/test_typetest_property_additionalproperties.py index 45445d9c42..c34c281f82 100644 --- a/packages/typespec-python/tests/mock_api/shared/test_typetest_property_additionalproperties.py +++ b/packages/typespec-python/tests/mock_api/shared/test_typetest_property_additionalproperties.py @@ -236,21 +236,6 @@ def test_spread_model_array(client: AdditionalPropertiesClient): client.spread_model_array.put(body) -@pytest.mark.skip(reason="https://github.com/microsoft/typespec/pull/6425") -def test_spread_record_discriminated_union(client: AdditionalPropertiesClient): - body = { - "name": "abc", - "prop1": {"fooProp": "abc", "kind": "kind0"}, - "prop2": { - "end": "2021-01-02T00:00:00Z", - "kind": "kind1", - "start": "2021-01-01T00:00:00Z", - }, - } - assert client.spread_record_discriminated_union.get() == body - client.spread_record_discriminated_union.put(body) - - def test_spread_record_non_discriminated_union(client: AdditionalPropertiesClient): body = { "name": "abc", diff --git a/packages/typespec-python/tests/mock_api/shared/unittests/test_parse_pyproject.py b/packages/typespec-python/tests/mock_api/shared/unittests/test_parse_pyproject.py index 1d843bbc4d..8ebf108f29 100644 --- a/packages/typespec-python/tests/mock_api/shared/unittests/test_parse_pyproject.py +++ b/packages/typespec-python/tests/mock_api/shared/unittests/test_parse_pyproject.py @@ -1,8 +1,6 @@ from pathlib import Path from typing import Dict, Any -import pytest - try: import tomllib except ImportError: @@ -75,7 +73,6 @@ def get_pyproject_section(package_path: str, section_name: str) -> Dict[str, Any raise AssertionError(f"Error checking pyproject.toml at '{package_path}': {e}") -@pytest.mark.skip(reason="Generator does not yet produce [tool.azure-sdk-build] section") def test_azure_sdk_build(): """Test that authentication-union packages have pyproject.toml with [tool.azure-sdk-build] pyright = false.""" @@ -95,4 +92,4 @@ def test_azure_sdk_build(): ), f"[tool.azure-sdk-build] section does not contain 'pyright' setting in {package_path}" assert ( azure_sdk_build["pyright"] is False - ), f"Expected pyright = false, but got pyright = {azure_sdk_build['pyright']} in {package_path}" + ), f"Expected pyright = false, but got pyright = {azure_sdk_build['pyright']} in {package_path}" \ No newline at end of file diff --git a/packages/typespec-python/tests/mock_api/shared/unittests/test_readme.py b/packages/typespec-python/tests/mock_api/shared/unittests/test_readme.py new file mode 100644 index 0000000000..133f680d9e --- /dev/null +++ b/packages/typespec-python/tests/mock_api/shared/unittests/test_readme.py @@ -0,0 +1,46 @@ +from pathlib import Path + +import pytest + +GENERATED_DIR = Path(__file__).parent / "../../../generated" +FLAVORS = ("azure", "unbranded") + +# Folders that exist in the azure-sdk-for-python baseline checkout but are not +# regenerated by the emitter, so their README.md is never (re)written. Skip +# them to keep this test focused on real generated packages. +# - "azure/service-multiple-services": listed in SKIP_SPECS in +# eng/scripts/ci/regenerate-common.ts (no emitter run for this spec). +# - "azure/azure-client-generator-core-client-initialization": orphan parent +# folder; only its sub-specs (.../default, .../individually, +# .../individuallyParent) are actually emitted as packages. +SKIP_PACKAGES = { + ("azure", "service-multiple-services"), + ("azure", "azure-client-generator-core-client-initialization"), +} + + +def _package_dirs(): + base = GENERATED_DIR.resolve() + dirs = [] + for flavor in FLAVORS: + flavor_dir = base / flavor + if not flavor_dir.is_dir(): + continue + for p in sorted(flavor_dir.iterdir()): + if not p.is_dir(): + continue + if (flavor, p.name) in SKIP_PACKAGES: + continue + dirs.append(p) + return dirs + + +@pytest.mark.parametrize( + "package_dir", + _package_dirs(), + ids=lambda p: f"{p.parent.name}/{p.name}", +) +def test_readme_exists(package_dir: Path): + """Each generated SDK package folder (azure & unbranded) must contain a README.md.""" + readme_path = package_dir / "README.md" + assert readme_path.is_file(), f"README.md not found at {readme_path}" diff --git a/packages/typespec-python/tests/mock_api/unbranded/asynctests/test_encode_duration_async.py b/packages/typespec-python/tests/mock_api/unbranded/asynctests/test_encode_duration_async.py index f14c436a8f..2b0716dc98 100644 --- a/packages/typespec-python/tests/mock_api/unbranded/asynctests/test_encode_duration_async.py +++ b/packages/typespec-python/tests/mock_api/unbranded/asynctests/test_encode_duration_async.py @@ -6,6 +6,7 @@ import datetime import pytest +import pytest_asyncio from encode.duration.aio import DurationClient from encode.duration.property.models import ( Int32SecondsDurationProperty, @@ -16,7 +17,7 @@ ) -@pytest.fixture +@pytest_asyncio.fixture async def client(): async with DurationClient() as client: yield client diff --git a/packages/typespec-python/tests/mock_api/unbranded/asynctests/test_encode_numeric_async.py b/packages/typespec-python/tests/mock_api/unbranded/asynctests/test_encode_numeric_async.py index 19e4f619f2..244f2ee0e1 100644 --- a/packages/typespec-python/tests/mock_api/unbranded/asynctests/test_encode_numeric_async.py +++ b/packages/typespec-python/tests/mock_api/unbranded/asynctests/test_encode_numeric_async.py @@ -4,11 +4,12 @@ # license information. # -------------------------------------------------------------------------- import pytest +import pytest_asyncio from encode.numeric.aio import NumericClient from encode.numeric.property import models -@pytest.fixture +@pytest_asyncio.fixture async def client(): async with NumericClient() as client: yield client diff --git a/packages/typespec-python/tests/mock_api/unbranded/asynctests/test_parameters_basic_async.py b/packages/typespec-python/tests/mock_api/unbranded/asynctests/test_parameters_basic_async.py index 81a0deecdd..f6d8c58a93 100644 --- a/packages/typespec-python/tests/mock_api/unbranded/asynctests/test_parameters_basic_async.py +++ b/packages/typespec-python/tests/mock_api/unbranded/asynctests/test_parameters_basic_async.py @@ -4,11 +4,12 @@ # license information. # -------------------------------------------------------------------------- import pytest +import pytest_asyncio from parameters.basic.aio import BasicClient from parameters.basic.explicitbody.models import User -@pytest.fixture +@pytest_asyncio.fixture async def client(): async with BasicClient() as client: yield client diff --git a/packages/typespec-python/tests/mock_api/unbranded/asynctests/test_parameters_spread_async.py b/packages/typespec-python/tests/mock_api/unbranded/asynctests/test_parameters_spread_async.py index 89f3cec481..57de584d84 100644 --- a/packages/typespec-python/tests/mock_api/unbranded/asynctests/test_parameters_spread_async.py +++ b/packages/typespec-python/tests/mock_api/unbranded/asynctests/test_parameters_spread_async.py @@ -4,11 +4,12 @@ # license information. # -------------------------------------------------------------------------- import pytest +import pytest_asyncio from parameters.spread.aio import SpreadClient from parameters.spread.model.models import BodyParameter -@pytest.fixture +@pytest_asyncio.fixture async def client(): async with SpreadClient() as client: yield client diff --git a/packages/typespec-python/tests/mock_api/unbranded/asynctests/test_payload_content_negotiation_async.py b/packages/typespec-python/tests/mock_api/unbranded/asynctests/test_payload_content_negotiation_async.py index 27db476d9c..3dd1cc324b 100644 --- a/packages/typespec-python/tests/mock_api/unbranded/asynctests/test_payload_content_negotiation_async.py +++ b/packages/typespec-python/tests/mock_api/unbranded/asynctests/test_payload_content_negotiation_async.py @@ -5,11 +5,12 @@ # -------------------------------------------------------------------------- import base64 import pytest +import pytest_asyncio from payload.contentnegotiation.aio import ContentNegotiationClient from payload.contentnegotiation.differentbody.models import PngImageAsJson -@pytest.fixture +@pytest_asyncio.fixture async def client(): async with ContentNegotiationClient(endpoint="http://localhost:3000") as client: yield client diff --git a/packages/typespec-python/tests/mock_api/unbranded/asynctests/test_payload_multipart_async.py b/packages/typespec-python/tests/mock_api/unbranded/asynctests/test_payload_multipart_async.py index 4a1a6a9ced..b5197313bf 100644 --- a/packages/typespec-python/tests/mock_api/unbranded/asynctests/test_payload_multipart_async.py +++ b/packages/typespec-python/tests/mock_api/unbranded/asynctests/test_payload_multipart_async.py @@ -5,6 +5,7 @@ # -------------------------------------------------------------------------- from pathlib import Path import pytest +import pytest_asyncio from payload.multipart import models from payload.multipart.aio import MultiPartClient from payload.multipart.formdata.httpparts.nonstring.models import FloatRequest @@ -14,7 +15,7 @@ PNG = Path(__file__).parent.parent / "data/image.png" -@pytest.fixture +@pytest_asyncio.fixture async def client(): async with MultiPartClient(endpoint="http://localhost:3000") as client: yield client diff --git a/packages/typespec-python/tests/mock_api/unbranded/asynctests/test_serialization_encoded_name_json_async.py b/packages/typespec-python/tests/mock_api/unbranded/asynctests/test_serialization_encoded_name_json_async.py index 7234c7e253..c03899fe7e 100644 --- a/packages/typespec-python/tests/mock_api/unbranded/asynctests/test_serialization_encoded_name_json_async.py +++ b/packages/typespec-python/tests/mock_api/unbranded/asynctests/test_serialization_encoded_name_json_async.py @@ -4,11 +4,12 @@ # license information. # -------------------------------------------------------------------------- import pytest +import pytest_asyncio from serialization.encodedname.json.aio import JsonClient from serialization.encodedname.json.property import models -@pytest.fixture +@pytest_asyncio.fixture async def client(): async with JsonClient() as client: yield client diff --git a/packages/typespec-python/tests/mock_api/unbranded/asynctests/test_special_words_async.py b/packages/typespec-python/tests/mock_api/unbranded/asynctests/test_special_words_async.py index 112533fa2d..c1e3bc0a8f 100644 --- a/packages/typespec-python/tests/mock_api/unbranded/asynctests/test_special_words_async.py +++ b/packages/typespec-python/tests/mock_api/unbranded/asynctests/test_special_words_async.py @@ -4,13 +4,14 @@ # license information. # -------------------------------------------------------------------------- import pytest +import pytest_asyncio from specialwords.aio import SpecialWordsClient from specialwords.models import models from specialwords.modelproperties import models as model_properties_models from specialwords.extensiblestrings import models as extensible_strings_models -@pytest.fixture +@pytest_asyncio.fixture async def client(): async with SpecialWordsClient() as client: yield client diff --git a/packages/typespec-python/tests/mock_api/unbranded/asynctests/test_unbranded_async.py b/packages/typespec-python/tests/mock_api/unbranded/asynctests/test_unbranded_async.py index 139d323cf3..0218d8578e 100644 --- a/packages/typespec-python/tests/mock_api/unbranded/asynctests/test_unbranded_async.py +++ b/packages/typespec-python/tests/mock_api/unbranded/asynctests/test_unbranded_async.py @@ -4,11 +4,12 @@ # ------------------------------------ import traceback import pytest +import pytest_asyncio from typetest.scalar.aio import ScalarClient from corehttp.exceptions import HttpResponseError -@pytest.fixture +@pytest_asyncio.fixture async def client(): async with ScalarClient() as client: yield client @@ -20,8 +21,5 @@ async def test_track_back(client: ScalarClient): await client.string.put("to raise exception") except HttpResponseError: track_back = traceback.format_exc().lower() - # Filter out file path lines (contain repo path which may include "azure"/"microsoft") - lines = [line for line in track_back.split("\n") if not line.strip().startswith("file ")] - filtered = "\n".join(lines) - assert "azure" not in filtered - assert "microsoft" not in filtered + assert "azure" not in track_back + assert "microsoft" not in track_back diff --git a/packages/typespec-python/tests/mock_api/unbranded/conftest.py b/packages/typespec-python/tests/mock_api/unbranded/conftest.py index a94535b690..df7cb9efbe 100644 --- a/packages/typespec-python/tests/mock_api/unbranded/conftest.py +++ b/packages/typespec-python/tests/mock_api/unbranded/conftest.py @@ -3,12 +3,10 @@ # Licensed under the MIT License. See License.txt in the project root for # license information. # -------------------------------------------------------------------------- -# Unbranded-specific fixtures -# Common fixtures (testserver, core_library, key_credential, png_data, jpg_data) -# are inherited from the root tests/conftest.py - import pytest -from typing import List +from pathlib import Path + +FILE_FOLDER = Path(__file__).parent SPECIAL_WORDS = [ @@ -49,5 +47,17 @@ @pytest.fixture -def special_words() -> List[str]: +def special_words() -> list[str]: return SPECIAL_WORDS + + +@pytest.fixture +def png_data() -> bytes: + with open(str(FILE_FOLDER / "data/image.png"), "rb") as file_in: + return file_in.read() + + +@pytest.fixture +def jpg_data() -> bytes: + with open(str(FILE_FOLDER / "data/image.jpg"), "rb") as file_in: + return file_in.read() diff --git a/packages/typespec-python/tests/mock_api/unbranded/test_unbranded.py b/packages/typespec-python/tests/mock_api/unbranded/test_unbranded.py deleted file mode 100644 index 681d55785a..0000000000 --- a/packages/typespec-python/tests/mock_api/unbranded/test_unbranded.py +++ /dev/null @@ -1,59 +0,0 @@ -# ------------------------------------ -# Copyright (c) Microsoft Corporation. -# Licensed under the MIT License. -# ------------------------------------ -import os -import re -from subprocess import getoutput -from pathlib import Path -import traceback -from importlib import import_module -import pytest -from typetest.scalar import ScalarClient -from corehttp.exceptions import HttpResponseError - - -@pytest.fixture -def client(): - with ScalarClient() as client: - yield client - - -def test_module(): - with pytest.raises(ModuleNotFoundError): - import_module("azure") - - -def test_track_back(client: ScalarClient): - try: - client.string.put("to raise exception") - except HttpResponseError: - track_back = traceback.format_exc().lower() - # Filter out file path lines (contain repo path which may include "azure"/"microsoft") - lines = [line for line in track_back.split("\n") if not line.strip().startswith("file ")] - filtered = "\n".join(lines) - assert "azure" not in filtered - assert "microsoft" not in filtered - - -def check_sensitive_word(folder: Path, word: str) -> str: - special_folders = ["__pycache__", "pytest_cache"] - if os.name == "nt": - skip_folders = "|".join(special_folders) - output = getoutput( - f"powershell \"ls -r -Path {folder} | where fullname -notmatch '{skip_folders}' | Select-String -Pattern '{word}'\"" - ).replace("\\", "/") - else: - skip_folders = "{" + ",".join(special_folders) + "}" - output = getoutput(f"grep -ri --exclude-dir={skip_folders} {word} {folder}") - - result = set() - for item in re.findall(f"{folder.as_posix()}[^:]+", output.replace("\n", "")): - result.add(Path(item).relative_to(folder).parts[0]) - return sorted(list(result)) - - -def test_sensitive_word(): - """Verify unbranded generated code doesn't contain Azure-specific branding.""" - check_folder = (Path(os.path.dirname(__file__)) / "../../generated/unbranded").resolve() - assert [] == check_sensitive_word(check_folder, "azure") diff --git a/packages/typespec-python/tests/pytest.ini b/packages/typespec-python/tests/pytest.ini deleted file mode 100644 index c8c9c7579d..0000000000 --- a/packages/typespec-python/tests/pytest.ini +++ /dev/null @@ -1,3 +0,0 @@ -[pytest] -asyncio_mode = auto -asyncio_default_fixture_loop_scope = function diff --git a/packages/typespec-python/tests/requirements/azure.txt b/packages/typespec-python/tests/requirements/azure.txt index 7dd647dfe0..ca59fac7d9 100644 --- a/packages/typespec-python/tests/requirements/azure.txt +++ b/packages/typespec-python/tests/requirements/azure.txt @@ -1,5 +1,5 @@ # Azure SDK dependencies -r base.txt -azure-core>=1.30.0 -azure-mgmt-core>=1.6.0 +azure-core>=1.37.0 +azure-mgmt-core==1.6.0 geojson>=3.0.0 diff --git a/packages/typespec-python/tests/requirements/docs.txt b/packages/typespec-python/tests/requirements/docs.txt index 7839a0e726..5b80499ba3 100644 --- a/packages/typespec-python/tests/requirements/docs.txt +++ b/packages/typespec-python/tests/requirements/docs.txt @@ -1,6 +1,8 @@ # Documentation dependencies -r base.txt pip +pylint +pkginfo sphinx>=7.0.0 sphinx_rtd_theme>=2.0.0 myst_parser>=2.0.0 diff --git a/packages/typespec-python/tests/requirements/lint.txt b/packages/typespec-python/tests/requirements/lint.txt index 2a9896f8d7..736a780654 100644 --- a/packages/typespec-python/tests/requirements/lint.txt +++ b/packages/typespec-python/tests/requirements/lint.txt @@ -1,4 +1,4 @@ # Linting dependencies -r base.txt pylint==4.0.4 -black==24.8.0 +black==26.3.1 diff --git a/packages/typespec-python/tests/requirements/unbranded.txt b/packages/typespec-python/tests/requirements/unbranded.txt index 73749e218c..724244f16e 100644 --- a/packages/typespec-python/tests/requirements/unbranded.txt +++ b/packages/typespec-python/tests/requirements/unbranded.txt @@ -1,3 +1,5 @@ +# === common unbranded dependencies across repos === # Unbranded SDK dependencies -r base.txt corehttp[requests] +# === end common unbranded dependencies across repos ===