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.
- add-connectorSkill
- connection-setupSkill
- trigger-registrationSkill
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
- Very specific: line 10
Follow the live-download procedure in GENERATION.md.
- Distinctive: line 12
Keep refreshes within the existing repository structure.
- Very specific: line 17
Use unsuffixed operation names.
- Very specific: line 18
Keep direct connector imports and lazy package-root client exports.
- Very specific: line 49
Use from __future__ import annotations for forward references
- Distinctive: line 66
Use complete, unabbreviated English terms for all identifiers
- Very specific: line 67
No single-letter variable names except for well-known conventions (i, j for indices, e for exceptions in handlers)
- Very specific: line 68
No placeholder names (blah, foo, temp, x) — always use meaningful names
- Distinctive: line 76
Keep modules focused — split large modules into submodules
- Very specific: line 95
Use from __future__ import annotations for modern syntax
- Very specific: line 96
Use Optional[X] or X | None for nullable types
- Very specific: line 97
Use list[str] (lowercase) instead of List[str] (Python 3.9+)
- Very specific: line 99
Use TypedDict for structured dictionaries
- Very specific: line 100
Use Protocol for duck typing instead of ABCs when appropriate
- Very specific: line 114
Use async with for resources that need cleanup
- Very specific: line 115
Prefer asyncio.gather() for concurrent operations
- Very specific: line 116
Never use asyncio.run() inside async code
- Very specific: line 117
Use asyncio.create_task() for fire-and-forget operations (with proper error handling)
- Distinctive: line 135
Single line: if all parameters fit:
- Very specific: line 155
Use keyword-only arguments (after *) for optional parameters with defaults
- Very specific: line 189
Document all parameters in Args: section
- Very specific: line 190
Document return value in Returns: section (skip for None)
- Very specific: line 191
Document exceptions in Raises: section
- Distinctive: line 206
NO blank line between comment and code it describes
- Very specific: line 227
Always chain exceptions with from e to preserve context
- Very specific: line 319
Test function naming: test_<method>_<scenario>_<expected> or test_<behavior>
- Very specific: line 320
Use pytest.mark.asyncio for async tests
- Very specific: line 321
Use AsyncMock for mocking async functions
- Distinctive: line 328
Never push directly to main
- Distinctive: line 329
Always create PR for review
- Very specific: line 342
Run the ADO release pipeline with the intended version.
- Distinctive: line 356
Use Python/PEP 440 pre-release suffixes:
- Distinctive: line 366
After artifacts are uploaded to the Azure SDK partner drops location, manually trigger the partner-release pipeline using the BlobPath …
- Distinctive: line 373
Do not delete or recreate a published release tag as a normal retry path.
- Very specific: line 399
When adding a new connector client to the SDK, use the add-connector skill (.github/skills/add-connector/SKILL.md) for detailed templates …
- Very specific: line 405
Do not create a synchronous mirror or an azure.connectors.aio module
≈ “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.
Files
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
resultaliases 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 withNone, 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.aiocatalog. - Use unsuffixed operation names. Pageable operations return
AsyncItemPaged; retained Location-backed long-running operations returnAsyncLROPollerafter awaitingbegin_*. - 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."""
passRules:
- Copyright header: Use
# ----(4 dashes) format with double space before "All rights reserved" - Module docstring immediately after copyright
- Use
from __future__ import annotationsfor forward references - Imports sorted: standard library, third-party, local (use
isort) - One blank line between import groups
Naming Conventions
| Element | Rule | Example |
| Modules | lowercase with underscores | http_client.py |
| Classes | PascalCase | ConnectorClient |
| Functions/methods | snake_case | get_connection_status() |
| Constants | UPPER_SNAKE_CASE | DEFAULT_TIMEOUT_SECONDS |
| Private members | leading underscore | _internal_method(), _private_attr |
| Type variables | PascalCase with suffix | ResponseT, ItemT |
Variable naming rules:
- Use complete, unabbreviated English terms for all identifiers
- No single-letter variable names except for well-known conventions (
i,jfor indices,efor 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.pycontainsHttpClient) - 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 annotationsfor modern syntax - Use
Optional[X]orX | Nonefor nullable types - Use
list[str](lowercase) instead ofList[str](Python 3.9+) - Return type is required for all public functions/methods
- Use
TypedDictfor structured dictionaries - Use
Protocolfor 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 withfor 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 insteadFunction 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 forNone) - 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 eRules:
- Exception variable name:
e(or more specific likehttp_error) - Always chain exceptions with
from eto 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 genericString 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
- Class docstring
- Class-level constants
__init__method__enter__/__exit__or__aenter__/__aexit__(context managers)- Properties
- Public methods
- Private methods (
_method) - Magic methods (
__str__,__repr__, etc.)
Module Layout Order
- Module docstring
from __future__ import annotations- Standard library imports
- Third-party imports
- Local imports
- Constants
- Type aliases
- Classes
- Functions
if __name__ == "__main__":block (if applicable)
Patterns to Avoid
| Anti-Pattern | Correct Pattern |
requests in async code | aiohttp with async/await |
| Mutable default arguments | None with default inside function |
Bare except: | except Exception as e: |
isinstance(x, str) for type unions | Use typing.Union or | with type guards |
| Magic numbers | Named constants (e.g., DEFAULT_TIMEOUT_SECONDS) |
Magic strings (e.g., "type", "object") | Named constants or enums |
list[0] without length check | next(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>ortest_<behavior> - Use
pytest.mark.asynciofor async tests - Use
AsyncMockfor 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.3rc1Pre-release
Use Python/PEP 440 pre-release suffixes:
1.2.3a1 # Alpha
1.2.3b1 # Beta
1.2.3rc1 # Release candidateManual 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.post1Note: 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
- Creates and validates a
release/{version}branch - Builds wheel and sdist with
python -m build - Creates a GitHub draft release with the distribution files attached
- Uploads distribution files to Azure SDK partner drops
- 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
- Generate one async-only root connector using the live-download procedure in
GENERATION.md:src/azure/connectors/<connector>.pyowns the client, models, and trigger metadata- Do not create a synchronous mirror or an
azure.connectors.aiomodule
Verify direct imports from the root connector module:
from azure.connectors.office365 import Office365Client, SendEmailInput- Keep package exports lazy — regenerate
src/azure/connectors/_client_registry.pyandsrc/azure/connectors/__init__.pyiwith the connector inventory. Preserve the lazy loader insrc/azure/connectors/__init__.py; do not add eager generated-client barrel imports or try/except imports that replace broken clients withNone. - Update
README.md— add the connector to the "Validated Connectors" table with status (✅ E2E Validatedor🔄 SDK Generated) and test count - Add unit tests — create
test_<connector>.pyintests/for the root async client:- Constructor options,
await client.close(), andasync withlifecycle - Unsuffixed operations, mocked success and error responses, and serialization round-trips
AsyncItemPagediteration without awaiting construction, including continuation and empty pages- Retained Location-backed operations: await
begin_*for anAsyncLROPoller, then awaitpoller.result()while the client remains open - Direct imports and lazy package-root exports agree with the generated registry and type stub
- Constructor options,
- Create one async sample under
samples/sample_connector_usage/. Use root connector imports,azure.identity.aiocredentials,async with, andawait. End the__main__guard withasyncio.run(main()), not an extra baremain()call. - Update
samples/sample_connector_usage/README.md— add the new sample to the samples table - Update
CHANGELOG.md— add the new connector to the[Unreleased]section under### Added - Update the connection setup skill — add the connector's API name to the supported list in
.github/skills/connection-setup/SKILL.md(Step 2) - Run all tests —
pytestmust pass with zero failures before committing - 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
AsyncItemPagedand awaitedAsyncLROPollercontracts 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
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
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.

