agentleFS
Microsoft AzureKnown publisherAzure/connectors-python-sdk/.github/copilot-instructions.md

connectors-python-sdk

Copilot instructions0 starsChanged 18 days agoMITView on GitHub

This repository contains the Python SDK for Azure Connectors. Code must follow Python best practices (PEP 8, PEP 484) and the team's conventions for async-only APIs, type safety, and API design. Variable naming rules:

  • Where files are headingNew Copilot instructions files have more rules about branches and pull requests, and fewer about code style.

    Of the Copilot instructions files we read, the 2,336 first seen in the last six months, against the older ones: the kind of rule that gained most share, and the kind that lost most. About files like this in general, not this one, and an estimate, since a kind is a pattern in the words.

  • In this repository3 other files it gives its agents.

The file

Show its rules and text

We split this copilot-instructions.md into the rules it gives an agent

36 rules. About 0 are common, 10 are distinctive, and 26 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈10

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 26

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  • could be automatic: a setting could enforce it
  1. Very specific:

    Follow the live-download procedure in GENERATION.md.

    line 10
  2. Distinctive:

    Keep refreshes within the existing repository structure.

    line 12
  3. Very specific:

    Use unsuffixed operation names.

    line 17
  4. Very specific:

    Keep direct connector imports and lazy package-root client exports.

    line 18
  5. Very specific:

    Use from __future__ import annotations for forward references

    line 49
  6. Distinctive:

    Use complete, unabbreviated English terms for all identifiers

    line 66
  7. Very specific:

    No single-letter variable names except for well-known conventions (i, j for indices, e for exceptions in handlers)

    line 67
  8. Very specific:

    No placeholder names (blah, foo, temp, x) — always use meaningful names

    line 68
  9. Distinctive:

    Keep modules focused — split large modules into submodules

    line 76
  10. Very specific:

    Use from __future__ import annotations for modern syntax

    line 95
  11. Very specific:

    Use Optional[X] or X | None for nullable types

    line 96
  12. Very specific:

    Use list[str] (lowercase) instead of List[str] (Python 3.9+)

    line 97
  13. Very specific:

    Use TypedDict for structured dictionaries

    line 99
  14. Very specific:

    Use Protocol for duck typing instead of ABCs when appropriate

    line 100
  15. Very specific:

    Use async with for resources that need cleanup

    line 114
  16. Very specific:

    Prefer asyncio.gather() for concurrent operations

    line 115
  17. Very specific:

    Never use asyncio.run() inside async code

    line 116
  18. Very specific:

    Use asyncio.create_task() for fire-and-forget operations (with proper error handling)

    line 117
  19. Distinctive:

    Single line: if all parameters fit:

    line 135
  20. Very specific:

    Use keyword-only arguments (after *) for optional parameters with defaults

    line 155
  21. Very specific:

    Document all parameters in Args: section

    line 189
  22. Very specific:

    Document return value in Returns: section (skip for None)

    line 190
  23. Very specific:

    Document exceptions in Raises: section

    line 191
  24. Distinctive:

    NO blank line between comment and code it describes

    line 206
  25. Very specific:

    Always chain exceptions with from e to preserve context

    line 227
  26. Very specific:

    Test function naming: test_<method>_<scenario>_<expected> or test_<behavior>

    line 319
  27. Very specific:

    Use pytest.mark.asyncio for async tests

    line 320
  28. Very specific:

    Use AsyncMock for mocking async functions

    line 321
  29. Distinctive:

    Never push directly to main

    line 328
  30. Distinctive:

    Always create PR for review

    line 329
  31. Very specific:

    Run the ADO release pipeline with the intended version.

    line 342
  32. Distinctive:

    Use Python/PEP 440 pre-release suffixes:

    line 356
  33. Distinctive:

    After artifacts are uploaded to the Azure SDK partner drops location, manually trigger the partner-release pipeline using the BlobPath …

    line 366
  34. Distinctive:

    Do not delete or recreate a published release tag as a normal retry path.

    line 373
  35. Very specific:

    When adding a new connector client to the SDK, use the add-connector skill (.github/skills/add-connector/SKILL.md) for detailed templates …

    line 399
  36. Very specific:

    Do not create a synchronous mirror or an azure.connectors.aio module

    line 405

≈ “Common” is an estimate: at least 1 in 5 of the 3,779 Copilot instructions files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
copilot-instructions.md

Copilot Instructions for azure-connectors-python-sdk

Overview

This repository contains the Python SDK for Azure Connectors. Code must follow Python best practices (PEP 8, PEP 484) and the team's conventions for async-only APIs, type safety, and API design.

Client Refresh and Review

  • Paired generator/SDK PRs may publish generated output from the linked active BPM PR. Record its exact source commit and active/unmerged status. Do not require upstream merge merely to show the generated result; upstream integration is a separate final SDK merge gate.
  • Follow the live-download procedure in GENERATION.md. Refresh existing clients from live definitions with an explicit subscription/region and a new empty temporary ARMCACHE_PATH; never seed it from stored responses or fixtures.
  • Reviewers independently fetch live definitions using their own empty temporary directory. Do not supply the producer's responses as review inputs or substitute a cache when live results differ.
  • Keep refreshes within the existing repository structure. Do not introduce cache folders, replay manifests, or new scaffolding unless explicitly requested. Temporary response stores are removed after the run.
  • Capture known response data directly in semantic variables, without redundant result aliases or duplicated conditions. Check the actual returned shape after regeneration; an item list is not a response dictionary.
  • Preserve nullable-response guards such as response and response.get(...); they are not duplicate conditions. Test non-pageable consumers with None, empty dictionaries, and populated responses separately. Do not simplify conditions by matching text prefixes.
  • Execute changed samples with empty and nonempty mocked results and assert their output, including absence of caught-and-printed errors. Pagination tests must exercise continuation requests, later-page items, and termination, including an empty initial page with a next link.
  • Generate one async-only client and its models in azure.connectors.<connector>. Do not add synchronous mirrors or a duplicate .aio catalog.
  • Use unsuffixed operation names. Pageable operations return AsyncItemPaged; retained Location-backed long-running operations return AsyncLROPoller after awaiting begin_*.
  • Keep direct connector imports and lazy package-root client exports. Do not eagerly import every generated module from package __init__.py.

Quick Reference: Coding Style Rules

File Structure

# ------------------------------------------------------------
# Copyright (c) Microsoft Corporation.  All rights reserved.
# ------------------------------------------------------------

"""Module docstring describing the purpose of this module."""

from __future__ import annotations

import asyncio
from typing import Any, Optional

from azure.connectors.sdk import ConnectorClient


class YourClass:
    """Class docstring."""
    
    pass

Rules:

  • Copyright header: Use # ---- (4 dashes) format with double space before "All rights reserved"
  • Module docstring immediately after copyright
  • Use from __future__ import annotations for forward references
  • Imports sorted: standard library, third-party, local (use isort)
  • One blank line between import groups

Naming Conventions

ElementRuleExample
Moduleslowercase with underscoreshttp_client.py
ClassesPascalCaseConnectorClient
Functions/methodssnake_caseget_connection_status()
ConstantsUPPER_SNAKE_CASEDEFAULT_TIMEOUT_SECONDS
Private membersleading underscore_internal_method(), _private_attr
Type variablesPascalCase with suffixResponseT, ItemT

Variable naming rules:

  • Use complete, unabbreviated English terms for all identifiers
  • No single-letter variable names except for well-known conventions (i, j for indices, e for exceptions in handlers)
  • No placeholder names (blah, foo, temp, x) — always use meaningful names

File Organization

One primary class per module:

  • Main class should match the module name (e.g., http_client.py contains HttpClient)
  • Helper classes and functions can coexist in the same module if tightly coupled
  • Keep modules focused — split large modules into submodules

Type Hints

ALWAYS use type hints for public APIs:

from typing import Optional

async def get_user(
    user_id: str,
    include_details: bool = False,
) -> Optional[User]:
    """Retrieve a user by ID."""
    ...

Rules:

  • Use from __future__ import annotations for modern syntax
  • Use Optional[X] or X | None for nullable types
  • Use list[str] (lowercase) instead of List[str] (Python 3.9+)
  • Return type is required for all public functions/methods
  • Use TypedDict for structured dictionaries
  • Use Protocol for duck typing instead of ABCs when appropriate

Async/Await Patterns

Use async context managers:

async with aiohttp.ClientSession() as session:
    async with session.get(url) as response:
        data = await response.json()

Rules:

  • Use async with for resources that need cleanup
  • Prefer asyncio.gather() for concurrent operations
  • Never use asyncio.run() inside async code
  • Use asyncio.create_task() for fire-and-forget operations (with proper error handling)

DO NOT:

# Wrong: Blocking call in async function
response = requests.get(url)  # Use aiohttp instead

# Wrong: Manual cleanup without context manager
session = aiohttp.ClientSession()
try:
    ...
finally:
    await session.close()  # Use async with instead

Function Signatures

Single line if all parameters fit:

def process_item(item: Item, validate: bool = True) -> Result:

Multi-line for longer signatures:

async def create_connection(
    connection_id: str,
    connection_type: str,
    *,
    timeout_seconds: int = 30,
    retry_count: int = 3,
) -> Connection:

Rules:

  • Use keyword-only arguments (after *) for optional parameters with defaults
  • Boolean parameters SHOULD use keyword-only to avoid ambiguity
  • Trailing comma after last parameter in multi-line signatures
  • Opening paren stays with function name

Docstrings

Use Google-style docstrings:

def process_request(
    request: Request,
    *,
    validate: bool = True,
) -> Response:
    """Process an incoming request and return the response.
    
    Args:
        request: The request to process.
        validate: Whether to validate the request before processing.
    
    Returns:
        The processed response.
    
    Raises:
        ValidationError: If validation is enabled and the request is invalid.
        ConnectionError: If the connection to the backend fails.
    """

Rules:

  • First line is a concise summary ending with a period
  • Blank line between summary and sections
  • Document all parameters in Args: section
  • Document return value in Returns: section (skip for None)
  • Document exceptions in Raises: section

Comments

Inline comments - use NOTE format:

# NOTE(username): Explanation of why this code exists.
result = do_something()

Rules:

  • Two spaces before inline comment
  • Blank line ABOVE standalone comment (unless first line in block)
  • NO blank line between comment and code it describes
  • Prefix: # NOTE(username): where username is your GitHub username
  • Comment on the 'why', not the 'what'

Exception Handling

try:
    result = await client.get_resource(resource_id)
except SpecificError as e:
    logger.error("Failed to get resource '%s': %s", resource_id, e)
    raise
except Exception as e:
    raise ConnectorError(
        f"Unexpected error retrieving resource '{resource_id}'."
    ) from e

Rules:

  • Exception variable name: e (or more specific like http_error)
  • Always chain exceptions with from e to preserve context
  • Wrap inserted values in single quotes in error messages
  • End error messages with period
  • All exceptions must have descriptive messages — never raise exceptions without context

DO:

raise ValueError(f"Parameter 'connection_id' cannot be empty.")
raise ConnectorError(f"Operation '{operation_id}' is not supported.")

DO NOT:

raise ValueError()  # No message
raise Exception("error")  # Non-descriptive, too generic

String Formatting

Use f-strings for simple formatting:

message = f"Processing request '{request_id}' for user '{user_id}'."

Use logging-style formatting for log messages:

logger.info("Processing request '%s' for user '%s'.", request_id, user_id)

Class Layout Order

  1. Class docstring
  2. Class-level constants
  3. __init__ method
  4. __enter__/__exit__ or __aenter__/__aexit__ (context managers)
  5. Properties
  6. Public methods
  7. Private methods (_method)
  8. Magic methods (__str__, __repr__, etc.)

Module Layout Order

  1. Module docstring
  2. from __future__ import annotations
  3. Standard library imports
  4. Third-party imports
  5. Local imports
  6. Constants
  7. Type aliases
  8. Classes
  9. Functions
  10. if __name__ == "__main__": block (if applicable)

Patterns to Avoid

Anti-PatternCorrect Pattern
requests in async codeaiohttp with async/await
Mutable default argumentsNone with default inside function
Bare except:except Exception as e:
isinstance(x, str) for type unionsUse typing.Union or | with type guards
Magic numbersNamed constants (e.g., DEFAULT_TIMEOUT_SECONDS)
Magic strings (e.g., "type", "object")Named constants or enums
list[0] without length checknext(iter(list), None) or explicit validation

Testing

import pytest
from unittest.mock import AsyncMock, patch

@pytest.mark.asyncio
async def test_get_user_returns_user_when_found():
    """Test that get_user returns the user when found."""
    # Arrange
    mock_client = AsyncMock()
    mock_client.get.return_value = {"id": "123", "name": "Test User"}
    
    # Act
    result = await get_user(mock_client, user_id="123")
    
    # Assert
    assert result is not None
    assert result.id == "123"

Rules:

  • Test function naming: test_<method>_<scenario>_<expected> or test_<behavior>
  • Use pytest.mark.asyncio for async tests
  • Use AsyncMock for mocking async functions
  • Follow Arrange-Act-Assert pattern
  • One assertion concept per test (multiple related asserts are OK)

Git Workflow

  • Branch naming: feature/description, fix/description, docs/description
  • Never push directly to main
  • Always create PR for review

Releasing a New Version

The official release flow is the Azure DevOps eng/ci/library-release.yml pipeline. It creates a release/{version} branch, updates src/azure/connectors/__init__.py, builds the wheel and source distribution, creates a GitHub draft release with the artifacts, uploads partner drops, and then requires a manual partner-release/PyPI step. The package version is loaded from azure.connectors.__version__ via src/pyproject.toml.

Standard Release (ADO pipeline)

Run the ADO release pipeline with the intended version. The pipeline creates a bare numeric Git tag such as 1.2.3 or 1.2.3b1; Python release tags are not v-prefixed.

# Example versions accepted by the release pipeline:
1.2.3
1.2.3a1
1.2.3b1
1.2.3rc1

Pre-release

Use Python/PEP 440 pre-release suffixes:

1.2.3a1   # Alpha
1.2.3b1   # Beta
1.2.3rc1  # Release candidate

Manual PyPI Publication

After artifacts are uploaded to the Azure SDK partner drops location, manually trigger the partner-release pipeline using the BlobPath printed by the official pipeline. Return to the original run and approve the final manual gate once the partner-release pipeline has started.

Re-releasing a Version

Do not delete or recreate a published release tag as a normal retry path. Release tags are part of the supply-chain integrity boundary and are protected by tag rulesets. If a release fails after a tag was pushed, use a new version. For a retry of the same base package, use a PEP 440 post-release such as 1.2.3.post1.

# Run the official release pipeline again with a new version:
1.2.3.post1

Note: PyPI does not allow re-uploading the same version. If the package was pushed successfully but the release failed, always use a new version number. Deleting or retagging an existing remote tag should be treated as break-glass admin work, not the standard process.

What the Release Workflow Does

  1. Creates and validates a release/{version} branch
  2. Builds wheel and sdist with python -m build
  3. Creates a GitHub draft release with the distribution files attached
  4. Uploads distribution files to Azure SDK partner drops
  5. Guides the maintainer through the manual partner-release/PyPI publication

Adding a New Connector

When adding a new connector client to the SDK, use the add-connector skill (.github/skills/add-connector/SKILL.md) for detailed templates and step-by-step guidance.

Steps

  1. Generate one async-only root connector using the live-download procedure in GENERATION.md:
    • src/azure/connectors/<connector>.py owns the client, models, and trigger metadata
    • Do not create a synchronous mirror or an azure.connectors.aio module
  2. Verify direct imports from the root connector module:

    from azure.connectors.office365 import Office365Client, SendEmailInput
  3. Keep package exports lazy — regenerate src/azure/connectors/_client_registry.py and src/azure/connectors/__init__.pyi with the connector inventory. Preserve the lazy loader in src/azure/connectors/__init__.py; do not add eager generated-client barrel imports or try/except imports that replace broken clients with None.
  4. Update README.md — add the connector to the "Validated Connectors" table with status (✅ E2E Validated or 🔄 SDK Generated) and test count
  5. Add unit tests — create test_<connector>.py in tests/ for the root async client:
    • Constructor options, await client.close(), and async with lifecycle
    • Unsuffixed operations, mocked success and error responses, and serialization round-trips
    • AsyncItemPaged iteration without awaiting construction, including continuation and empty pages
    • Retained Location-backed operations: await begin_* for an AsyncLROPoller, then await poller.result() while the client remains open
    • Direct imports and lazy package-root exports agree with the generated registry and type stub
  6. Create one async sample under samples/sample_connector_usage/. Use root connector imports, azure.identity.aio credentials, async with, and await. End the __main__ guard with asyncio.run(main()), not an extra bare main() call.
  7. Update samples/sample_connector_usage/README.md — add the new sample to the samples table
  8. Update CHANGELOG.md — add the new connector to the [Unreleased] section under ### Added
  9. Update the connection setup skill — add the connector's API name to the supported list in .github/skills/connection-setup/SKILL.md (Step 2)
  10. Run all tests — pytest must pass with zero failures before committing
  11. Create a PR — reference the GitHub issue (e.g., Closes #9)

Validation checklist

  • Module follows naming conventions (snake_case.py)
  • Client class has proper type hints on all public methods
  • Docstrings present on class and all public methods
  • Existing connector tests still pass (no regressions)
  • New connector tests cover constructor options, async lifecycle, mocked responses, exceptions, and serialization
  • One root module owns the async-only client, models, and trigger metadata; no sync or aio mirror exists
  • Unsuffixed operations use AsyncItemPaged and awaited AsyncLROPoller contracts where applicable
  • Generated client registry and package type stub match; package-root exports remain lazy and import failures propagate
  • README.md connector table updated
  • One async sample imports public root types and compiles without errors
  • Offline sample tests check output for empty and nonempty results and run the actual main guard without unawaited coroutine warnings
  • Samples README updated
  • CHANGELOG.md updated
  • Connection setup skill updated

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.