Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/python.instructions.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ Before completing any Python code changes, verify:
## Testing

- Aim for 90+% code coverage for each file.
- Slow tests (> 0.1s runtime) should be identified and fixed, if possible.
- Add or update pytest unit tests when changing behavior.
- Prefer focused tests for the code being changed.
- Avoid tests that require live Azure access; mock Azure CLI interactions and `azure_resources` helpers.
Expand Down
85 changes: 43 additions & 42 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -1,44 +1,45 @@
{
"python.analysis.exclude": [
"**/node_modules",
"**/__pycache__",
".git",
"**/build",
"env/**",
"**/.*",
"**/.venv",
"**/venv",
"**/env"
],
"files.trimTrailingWhitespace": true,
"files.insertFinalNewline": true,
"files.trimFinalNewlines": true,
"files.eol": "\n",
"editor.renderWhitespace": "trailing",
"python.defaultInterpreterPath": "./.venv/Scripts/python.exe",
"python.envFile": "${workspaceFolder}/.env",
"[python]": {
"editor.codeActionsOnSave": {
"source.organizeImports": "explicit",
"source.unusedImports": "explicit"
},
"editor.formatOnSave": true
"[markdown]": {
"files.trimTrailingWhitespace": false
},
"[python]": {
"editor.codeActionsOnSave": {
"source.organizeImports": "explicit",
"source.unusedImports": "explicit"
},
"[markdown]": {
"files.trimTrailingWhitespace": false
},
"terminal.integrated.defaultProfile.windows": "PowerShell",
"plantuml.render": "Local",
"plantuml.exportFormat": "svg",
"plantuml.java": "C:\\Program Files\\OpenJDK\\jdk-22.0.2\\bin\\java.exe",
"plantuml.diagramsRoot": "assets/diagrams/src",
"plantuml.exportOutDir": "assets/diagrams/out",
"python.terminal.activateEnvironment": true,
"python.terminal.activateEnvInCurrentTerminal": true,
"python.testing.pytestEnabled": true,
"python.linting.enabled": true,
"python.linting.pylintEnabled": true,
"jupyter.kernels.trusted": [
"./.venv/Scripts/python.exe"
]
}
"editor.formatOnSave": true
},
"editor.renderWhitespace": "trailing",
"files.eol": "\n",
"files.insertFinalNewline": true,
"files.trimFinalNewlines": true,
"files.trimTrailingWhitespace": true,
"jupyter.kernels.trusted": [
"./.venv/Scripts/python.exe"
],
"plantuml.diagramsRoot": "assets/diagrams/src",
"plantuml.exportFormat": "svg",
"plantuml.exportOutDir": "assets/diagrams/out",
"plantuml.java": "C:\\Program Files\\OpenJDK\\jdk-22.0.2\\bin\\java.exe",
"plantuml.render": "Local",
"python.analysis.exclude": [
"**/node_modules",
"**/__pycache__",
".git",
"**/build",
"env/**",
"**/.*",
"**/.venv",
"**/venv",
"**/env"
],
"python.defaultInterpreterPath": "./.venv/Scripts/python.exe",
"python.envFile": "${workspaceFolder}/.env",
"python.linting.enabled": true,
"python.linting.pylintEnabled": true,
"python.terminal.activateEnvInCurrentTerminal": true,
"python.terminal.activateEnvironment": true,
"python.terminal.useEnvFile": true,
"python.testing.pytestEnabled": true,
"terminal.integrated.defaultProfile.windows": "PowerShell"
}
25 changes: 20 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,23 @@ Historically, there were two general paths to experimenting with APIM. Standing

It's quick and easy to get started!

1. Follow one of the two [setup options](#️-setup).
1. Select an [infrastructure](#-list-of-infrastructures) to deploy.
### ⚡ Quick Start (Recommended Path for First-Time Users)

1. **Choose your setup** (pick one):
- **Easiest**: Use [GitHub Codespaces or Dev Container](#️-setup) - everything is pre-configured
- **Prefer local development**: Follow [Full Local Setup](#️-setup)

2. **Deploy an infrastructure** - Choose one based on your needs:
- **Just starting out?** → [Simple API Management][infra-simple-apim] (fastest, lowest cost)
- **Want to explore containers?** → [API Management & Container Apps][infra-apim-aca]
- **Exploring secure Azure Front Door?** → [Front Door & API Management with Private Link][infra-afd-apim-pe]
- **Prefer Application Gateway?** → [Application Gateway (Private Link) & API Management][infra-appgw-apim-pe] or [Application Gateway (VNet) & API Management][infra-appgw-apim]

3. **Run a sample** - Open the desired sample's `create.ipynb` file and run it (nearly all samples work with all infrastructures)

4. **Experiment** - Modify policies, make API calls, and learn!

> 💡 **First time?** Start with the [Simple API Management][infra-simple-apim] infrastructure and the [General][sample-general] sample. It takes ~5 minutes to deploy and costs ~$1-2/hour to run.


## 📁 List of Infrastructures
Expand Down Expand Up @@ -81,12 +96,12 @@ This menu-driven interface provides quick access to:

APIM Samples supports two setup options:

### Option 1: GitHub Codespaces / Dev Container (Recommended)
### Option 1: GitHub Codespaces / Dev Container (Recommended for First-Time Users)
<details>

**The fastest way to get started is using our pre-configured development environment.**
**The fastest way to get started is using our pre-configured development environment.** Everything is pre-installed and configured—just sign in to Azure and you're ready to go.

Each supported Python version has its own dev container, providing you with a more tailored environment that hopefully more closely resembles your own workloads.
This is especially helpful if you're new to APIM, unfamiliar with Python environments, or want to avoid local setup complexity. The entire setup takes 2-3 minutes.

**GitHub Codespaces**: Click the green "Code" button → "Codespaces" → "..." → "New with options..." → "Dev container configuration" (select Python version but ignore *Default project configuration*) → "Create codespace"

Expand Down
49 changes: 25 additions & 24 deletions setup/local_setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -74,29 +74,29 @@ def check_azure_cli_installed():
"""Check if Azure CLI is installed."""
az_path = shutil.which('az') or shutil.which('az.cmd') or shutil.which('az.bat')
if not az_path:
print(" ❌ Azure CLI is not installed. Please install from: https://learn.microsoft.com/cli/azure/install-azure-cli")
print("❌ Azure CLI is not installed. Please install from: https://learn.microsoft.com/cli/azure/install-azure-cli")
return False
try:
subprocess.run([az_path, '--version'], capture_output=True, text=True, check=True)
print(" ✅ Azure CLI is installed")
print("✅ Azure CLI is installed")
return True
except subprocess.CalledProcessError:
print(" ❌ Azure CLI is not installed. Please install from: https://learn.microsoft.com/cli/azure/install-azure-cli")
print("❌ Azure CLI is not installed. Please install from: https://learn.microsoft.com/cli/azure/install-azure-cli")
return False

def check_bicep_cli_installed():
"""Check if Azure Bicep CLI is installed."""
az_path = shutil.which('az') or shutil.which('az.cmd') or shutil.which('az.bat')
if not az_path:
print(" ❌ Azure CLI is not installed. Please install from: https://learn.microsoft.com/cli/azure/install-azure-cli")
print("❌ Azure CLI is not installed. Please install from: https://learn.microsoft.com/cli/azure/install-azure-cli")
return False

try:
subprocess.run([az_path, 'bicep', 'version'], capture_output=True, text=True, check=True)
print(" ✅ Azure Bicep CLI is installed (via az bicep)")
print("✅ Azure Bicep CLI is installed (via az bicep)")
return True
except subprocess.CalledProcessError:
print(" ❌ Azure Bicep CLI is not installed. Install with: az bicep install")
print("❌ Azure Bicep CLI is not installed. Install with: az bicep install")
return False

def check_azure_providers_registered():
Expand Down Expand Up @@ -133,10 +133,10 @@ def check_azure_providers_registered():
missing_providers = [p for p in required_providers if p not in registered_providers]

if not missing_providers:
print(" ✅ All required Azure resource providers are registered")
print("✅ All required Azure resource providers are registered")
return True

print(f" ❌ Missing {len(missing_providers)} Azure provider(s):")
print(f"❌ Missing {len(missing_providers)} Azure provider(s):")
for provider in missing_providers:
print(f" • {provider}")
print(" Register with: az provider register -n <provider-namespace>")
Expand Down Expand Up @@ -306,7 +306,7 @@ def generate_env_file() -> None:
with open(env_file_path, 'w', encoding='utf-8') as f:
f.write(env_content)

print(f"\nSuccessfully generated .env file: {env_file_path}\n")
print(f"\n✅ Successfully generated .env file: {env_file_path}\n")


def install_jupyter_kernel():
Expand Down Expand Up @@ -380,8 +380,6 @@ def create_vscode_settings():
"python.terminal.activateEnvironment": True,
"python.terminal.activateEnvInCurrentTerminal": True,
"python.testing.pytestEnabled": True,
"python.linting.enabled": True,
"python.linting.pylintEnabled": True,
"jupyter.kernels.trusted": [venv_python],
}

Expand All @@ -408,7 +406,8 @@ def create_vscode_settings():
)

with open(settings_file, 'w', encoding='utf-8') as f:
json.dump(merged_settings, f, indent=4)
json.dump(merged_settings, f, indent=2, sort_keys=True)
f.write('\n')

print(f"✅ VS Code settings updated: {settings_file}")
print(" - Existing settings preserved")
Expand All @@ -419,7 +418,8 @@ def create_vscode_settings():
required_settings["python.analysis.exclude"] = DEFAULT_PYTHON_ANALYSIS_EXCLUDE

with open(settings_file, 'w', encoding='utf-8') as f:
json.dump(required_settings, f, indent=4)
json.dump(required_settings, f, indent=2, sort_keys=True)
f.write('\n')

print(f"✅ VS Code settings created: {settings_file}")
print(" - Python interpreter configured for .venv")
Expand Down Expand Up @@ -502,7 +502,8 @@ def force_kernel_consistency():
)

with open(settings_file, 'w', encoding='utf-8') as f:
json.dump(merged_settings, f, indent=4)
json.dump(merged_settings, f, indent=2)
f.write('\n')

print("✅ Kernel trust refreshed without overriding user settings")
return True
Expand All @@ -522,8 +523,8 @@ def setup_complete_environment():

print("🚀 Setting up complete APIM Samples environment...\n")

# Step 0: Check Azure prerequisites
print("0. Checking Azure prerequisites...")
# Step 1: Check Azure prerequisites
print("1/5) Checking Azure prerequisites...\n")
azure_cli_ok = check_azure_cli_installed()
bicep_ok = check_bicep_cli_installed()
providers_ok = check_azure_providers_registered()
Expand All @@ -532,20 +533,20 @@ def setup_complete_environment():
print("\n⚠️ Some Azure prerequisites are missing. Please address the issues above and re-run this script.")
return

# Step 1: Generate .env file
print("\n1. Generating .env file for Python path configuration...")
# Step 2: Generate .env file
print("\n2/5) Generating .env file for Python path configuration...")
generate_env_file()

# Step 2: Register Jupyter kernel
print("2. Registering standardized Jupyter kernel...")
# Step 3: Register Jupyter kernel
print("3/5) Registering standardized Jupyter kernel...\n")
kernel_success = install_jupyter_kernel()

# Step 3: Configure VS Code settings with minimal, merged defaults
print("\n3. Configuring VS Code workspace settings...")
# Step 4: Configure VS Code settings with minimal, merged defaults
print("\n4/5) Configuring VS Code workspace settings...\n")
vscode_success = create_vscode_settings()

# Step 4: Enforce kernel consistency
print("\n4. Enforcing kernel consistency for future reliability...")
# Step 5: Enforce kernel consistency
print("\n5/5) Enforcing kernel consistency for future reliability...\n")
consistency_success = force_kernel_consistency()

# Summary
Expand Down
19 changes: 15 additions & 4 deletions setup/verify_local_setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,9 @@ def check_azure_cli():
try:
result = subprocess.run([az_path, '--version'], capture_output=True, text=True, check=True)
version_line = (result.stdout.splitlines() or ["unknown version"])[0].strip()
print_status(f"Azure CLI is installed ({version_line})")
# Extract just the version number from "azure-cli 2.81.0"
version = version_line.split()[-1] if version_line else "unknown"
print_status(f"Azure CLI is installed ({version})")
return True
except subprocess.CalledProcessError:
print_status("Azure CLI is not installed or not in PATH", False)
Expand All @@ -216,7 +218,16 @@ def check_bicep_cli():
try:
result = subprocess.run([az_path, 'bicep', 'version'], capture_output=True, text=True, check=True)
version_line = (result.stdout.splitlines() or ["unknown version"])[0].strip()
print_status(f"Azure Bicep CLI is installed (az bicep version: {version_line})")
# Extract version number from "Bicep CLI version 0.39.26 (1e90b06e40)"
version = "unknown"
if "version" in version_line.lower():
parts = version_line.split()
# Find index of "version" and get the next part
for i, part in enumerate(parts):
if part.lower() == "version" and i + 1 < len(parts):
version = parts[i + 1]
break
print_status(f"Azure Bicep CLI is installed ({version})")
return True
except subprocess.CalledProcessError:
print_status("Azure Bicep CLI is not installed. Install with: az bicep install", False)
Expand Down Expand Up @@ -262,10 +273,10 @@ def check_azure_providers():
print(f" - {provider}")

if not missing_providers:
print_status("All required Azure providers are registered")
print_status("\nAll required Azure providers are registered")
return True

print_status(f"Missing {len(missing_providers)} provider(s): {', '.join(missing_providers)}", False)
print_status(f"\nMissing {len(missing_providers)} provider(s): {', '.join(missing_providers)}", False)
print(" Register missing providers with:")
for provider in missing_providers:
print(f" az provider register -n {provider}")
Expand Down
Loading
Loading