Skip to content

Commit 8febb62

Browse files
Feature/pylint refactoring (#98)
1 parent ebd0483 commit 8febb62

29 files changed

+1483
-1290
lines changed

.github/copilot-instructions.md

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ applyTo: "**"
66

77
## Purpose
88

9-
This instructions file is designed to guide GitHub Copilot's behavior specifically for this repository. It is intended to provide clear, general, and maintainable guidelines for code generation, style, and collaboration.
9+
This instructions file is designed to guide GitHub Copilot's behavior specifically for this repository. It is intended to provide clear, general, and maintainable guidelines for code generation, style, and collaboration.
1010

1111
**In case of any conflict, instructions from other individualized or project-specific files (such as `my-copilot.instructions.md`) take precedence over this file.**
1212

@@ -24,7 +24,7 @@ In case of any conflicting instructions, the following hierarchy shall apply. If
2424
1. Individualized instructions (e.g. a developer's or an organization's instruction file(s)), if present
2525
2. This repository's `.github/.copilot-instructions.md`
2626
3. General best practices and guidelines from sources such as [Microsoft Learn](https://learn.microsoft.com/docs/)
27-
This includes the [Microsoft Cloud Adoption Framework](https://learn.microsoft.com/azure/cloud-adoption-framework/).
27+
This includes the [Microsoft Cloud Adoption Framework](https://learn.microsoft.com/azure/cloud-adoption-framework/).
2828
4. Official [GitHub Copilot best practices documentation](https://docs.github.com/enterprise-cloud@latest/copilot/using-github-copilot/coding-agent/best-practices-for-using-copilot-to-work-on-tasks)
2929

3030
## Copilot Personality Behavior
@@ -58,11 +58,11 @@ In case of any conflicting instructions, the following hierarchy shall apply. If
5858
- `/`: Root directory containing the main files and folders. Bicep configuration is stored in `bicepconfig.json`.
5959
- The following folders are all at the root level:
6060
- `assets/`: PlantUML diagrams and images. Static assets such as these should be placed here. Any diagrams should be placed in the /diagrams/src subfolder.
61-
- `infrastructure/`: Contains Jupyter notebooks for setting up various API Management infrastructures. When modifying samples, these notebooks should not need to be modified.
61+
- `infrastructure/`: Contains Jupyter notebooks for setting up various API Management infrastructures. When modifying samples, these notebooks should not need to be modified.
6262
- `samples/`: Various policy and scenario samples that can be applied to the infrastructures.
6363
- `setup/`: General setup scripts and configurations for the repository and dev environment setup.
6464
- `shared/`: Shared resources, such as Bicep modules, Python libraries, and other reusable components.
65-
- `tests/`: Contains unit tests for Python code and Bicep modules. This folder should contain all tests for all code in the repository.
65+
- `tests/`: Contains unit tests for Python code and Bicep modules. This folder should contain all tests for all code in the repository.
6666

6767
## Formatting and Style
6868

@@ -109,14 +109,14 @@ param resourceSuffix string = uniqueString(subscription().id, resourceGroup().id
109109

110110
- Overall layout of a Bicep file should be:
111111
- Visible sections of code with the following format should be used:
112-
112+
113113
```bicep
114114
// ------------------------------
115115
// <SECTION HEADER>
116116
// ------------------------------
117117
```
118118

119-
- <SECTION HEADER> should be indented three spaces and be in all caps.
119+
- <SECTION HEADER> should be indented three spaces and be in all caps.
120120
- Section headers should have only two blank lines before and only one blank line after.
121121
- Top-to-bottom, the following comma-separated section headers should be inserted unless the section is empty:
122122
- Parameters
@@ -128,19 +128,20 @@ param resourceSuffix string = uniqueString(subscription().id, resourceGroup().id
128128
### Python Instructions
129129

130130
- Prefer Python 3.12+ syntax and features unless otherwise specified.
131+
- Respect the repository's `.pylintrc` file for linting rules. The file is found in the `tests/python/` folder.
131132
- When inserting a comment to describe a method, insert a blank line after the comment section.
132133
- Never leave a blank line at the very top of a Python file. The file must start immediately with the module docstring or code. Always remove any leading blank line at the top.
133134
- Do not have imports such as `from shared.python import Foo`. The /shared/python directory is covered by a root `.env` file. Just use `import Foo` or `from Foo import Bar` as appropriate.
134135
- After the module docstring, all import statements must come before any section headers (e.g., CONSTANTS, VARIABLES, etc.). Section headers should only appear after the imports. Here is a more explicit example:
135-
136+
136137
```python
137138
"""
138139
Module docstring.
139140
"""
140141

141142
import ...
142143
...
143-
144+
144145

145146
# ------------------------------
146147
# CONSTANTS
@@ -150,14 +151,14 @@ param resourceSuffix string = uniqueString(subscription().id, resourceGroup().id
150151

151152
- Overall layout of a Python file should be:
152153
- Visible sections of code with the following format should be used:
153-
154+
154155
```python
155156
# ------------------------------
156157
# <SECTION HEADER>
157158
# ------------------------------
158159
```
159160

160-
- <SECTION HEADER> should be indented three spaces and be in all caps.
161+
- <SECTION HEADER> should be indented three spaces and be in all caps.
161162
- Section headers should have only two blank lines before and only one blank line after.
162163
- Top-to-bottom, the following comma-separated section headers should be inserted unless the section is empty:
163164
- Constants
@@ -173,7 +174,7 @@ param resourceSuffix string = uniqueString(subscription().id, resourceGroup().id
173174
- Private Methods
174175
- Public Methods
175176

176-
- Python Docstring/Class Formatting Rule:
177+
- Python Docstring/Class Formatting Rule:
177178
- Always insert a single blank line after a class docstring and before any class attributes or methods.
178179
- Never place class attributes or decorators on the same line as the docstring. Example:
179180

@@ -186,7 +187,7 @@ param resourceSuffix string = uniqueString(subscription().id, resourceGroup().id
186187
attribute: str
187188
...
188189
```
189-
190+
190191
### Jupyter Notebook Instructions
191192

192193
- Use these [configuration settings](https://github.com/microsoft/vscode-jupyter/blob/dd568fde/package.nls.json) as a reference for the VS Code Jupyter extension configuration.
@@ -195,10 +196,10 @@ param resourceSuffix string = uniqueString(subscription().id, resourceGroup().id
195196

196197
- Ensure you verify that all include links are correct and up to date. This link provides a starting point: https://github.com/plantuml-stdlib/Azure-PlantUML/blob/master/AzureSymbols.md
197198
- Keep diagrams simple. For Azure, include major components, not individual aspects of components. For example, there is no need for individual policies in WAFs or APIs in API Management, Smart Detector Alert Rules, etc.
198-
- Less is more. Don't be too verbose in the diagrams.
199+
- Less is more. Don't be too verbose in the diagrams.
199200
- Never include subscription IDs, resource group names, or any other sensitive information in the diagrams. That data is not relevant.
200201
- Don't use the "legend" command if the information is relatively obvious.
201202

202203
### API Management Policy XML Instructions
203204

204-
- Policies should use camelCase for all variable names.
205+
- Policies should use camelCase for all variable names.

.gitignore

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,11 @@ labs-in-progress/
2828
.coverage
2929
tests/python/htmlcov/
3030

31+
# Pylint reports
32+
tests/python/pylint/reports/
33+
tests/python/$JsonReport
34+
tests/python/$TextReport
35+
3136
shared/bicep/modules/**/*.json
3237
main.json
3338

.vscode/settings.json

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,11 @@
11
{
2+
// Whitespace and formatting
3+
"files.trimTrailingWhitespace": true,
4+
"files.insertFinalNewline": true,
5+
"files.trimFinalNewlines": true,
6+
"editor.renderWhitespace": "trailing",
7+
8+
// PlantUML
29
"plantuml.diagramsRoot": "assets/diagrams/src",
310
"plantuml.exportFormat": "svg",
411
"plantuml.exportOutDir": "assets/diagrams/out",

README.md

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -217,7 +217,35 @@ As you work with this repo, you will likely want to make your own customizations
217217

218218
The repo uses the bicep linter and has rules defined in `bicepconfig.json`. See the [bicep linter documentation][bicep-linter-docs] for details.
219219

220-
**We welcome contributions!** Please consider forking the repo and creating issues and pull requests to share your samples. Please see [CONTRIBUTING.md](CONTRIBUTING.md) for details. Thank you!
220+
**We welcome contributions!** Please consider forking the repo and creating issues and pull requests to share your samples. Please see [CONTRIBUTING.md](CONTRIBUTING.md) for details. Thank you!
221+
222+
### 🔍 Code Quality & Linting
223+
224+
The repository uses [pylint][pylint-docs] to maintain Python code quality standards. Configuration is located in `tests/python/.pylintrc`.
225+
226+
#### Running Pylint
227+
228+
**Using the convenience script (recommended):**
229+
```powershell
230+
# From tests/python directory
231+
.\run_pylint.ps1 # Run with default settings
232+
.\run_pylint.ps1 -ShowReport # Include full detailed report
233+
.\run_pylint.ps1 -Target "../../samples" # Analyze a different directory
234+
```
235+
236+
**Manual execution:**
237+
```powershell
238+
pylint --rcfile tests/python/.pylintrc shared/python
239+
```
240+
241+
#### Pylint Reports
242+
243+
All pylint runs generate timestamped reports in `tests/python/pylint/reports/`:
244+
- **JSON format**: Machine-readable for CI/CD integration
245+
- **Text format**: Human-readable detailed analysis
246+
- **Latest symlinks**: `latest.json` and `latest.txt` always point to the most recent run
247+
248+
The script automatically displays a **Top 10 Issues Summary** showing the most frequent code quality issues to help prioritize improvements.
221249

222250
### ➕ Adding a Sample
223251

@@ -310,6 +338,7 @@ The original author of this project is [Simon Kurtz][simon-kurtz].
310338
[badge-python-tests]: https://github.com/Azure-Samples/Apim-Samples/actions/workflows/python-tests.yml/badge.svg?branch=main
311339
[bicep-linter-docs]: https://learn.microsoft.com/azure/azure-resource-manager/bicep/bicep-config-linter
312340
[houssem-dellai]: https://github.com/HoussemDellai
341+
[pylint-docs]: https://pylint.pycqa.org/
313342
[import-troubleshooting]: .devcontainer/IMPORT-TROUBLESHOOTING.md
314343
[infra-afd-apim-pe]: ./infrastructure/afd-apim-pe
315344
[infra-apim-aca]: ./infrastructure/apim-aca

infrastructure/afd-apim-pe/create_infrastructure.py

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,15 @@ def create_infrastructure(location: str, index: int, apim_sku: APIM_SKU, no_aca:
1313
try:
1414
# Check if infrastructure already exists to determine messaging
1515
infrastructure_exists = utils.does_resource_group_exist(utils.get_infra_rg_name(utils.INFRASTRUCTURE.AFD_APIM_PE, index))
16-
16+
1717
# Create custom APIs for AFD-APIM-PE with optional Container Apps backends
1818
custom_apis = _create_afd_specific_apis(not no_aca)
19-
19+
2020
infra = AfdApimAcaInfrastructure(location, index, apim_sku, infra_apis = custom_apis)
2121
result = infra.deploy_infrastructure(infrastructure_exists)
22-
22+
2323
sys.exit(0 if result.success else 1)
24-
24+
2525
except Exception as e:
2626
print(f'\n💥 Error: {str(e)}')
2727
sys.exit(1)
@@ -30,14 +30,14 @@ def create_infrastructure(location: str, index: int, apim_sku: APIM_SKU, no_aca:
3030
def _create_afd_specific_apis(use_aca: bool = True) -> list[API]:
3131
"""
3232
Create AFD-APIM-PE specific APIs with optional Container Apps backends.
33-
33+
3434
Args:
3535
use_aca (bool): Whether to include Azure Container Apps backends. Defaults to true.
36-
36+
3737
Returns:
3838
list[API]: List of AFD-specific APIs.
3939
"""
40-
40+
4141
# If Container Apps is enabled, create the ACA APIs in APIM
4242
if use_aca:
4343
pol_backend = utils.read_policy_xml(BACKEND_XML_POLICY_PATH)
@@ -58,13 +58,13 @@ def _create_afd_specific_apis(use_aca: bool = True) -> list[API]:
5858
api_hwaca_pool = API('hello-world-aca-pool', 'Hello World (ACA Pool)', '/aca-pool', 'This is the ACA API for Backend Pool', pol_aca_backend_pool, [api_hwaca_pool_get])
5959

6060
return [api_hwaca_1, api_hwaca_2, api_hwaca_pool]
61-
61+
6262
return []
6363
def main():
6464
"""
6565
Main entry point for command-line usage.
6666
"""
67-
67+
6868
parser = argparse.ArgumentParser(description = 'Create AFD-APIM-PE infrastructure')
6969
parser.add_argument('--location', default = 'eastus2', help = 'Azure region (default: eastus2)')
7070
parser.add_argument('--index', type = int, help = 'Infrastructure index')

infrastructure/apim-aca/create_infrastructure.py

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,15 @@ def create_infrastructure(location: str, index: int, apim_sku: APIM_SKU) -> None
1313
try:
1414
# Check if infrastructure already exists to determine messaging
1515
infrastructure_exists = utils.does_resource_group_exist(utils.get_infra_rg_name(utils.INFRASTRUCTURE.APIM_ACA, index))
16-
16+
1717
# Create custom APIs for APIM-ACA with Container Apps backends
1818
custom_apis = _create_aca_specific_apis()
19-
19+
2020
infra = ApimAcaInfrastructure(location, index, apim_sku, infra_apis = custom_apis)
2121
result = infra.deploy_infrastructure(infrastructure_exists)
22-
22+
2323
sys.exit(0 if result.success else 1)
24-
24+
2525
except Exception as e:
2626
print(f'\n💥 Error: {str(e)}')
2727
sys.exit(1)
@@ -30,11 +30,11 @@ def create_infrastructure(location: str, index: int, apim_sku: APIM_SKU) -> None
3030
def _create_aca_specific_apis() -> list[API]:
3131
"""
3232
Create APIM-ACA specific APIs with Container Apps backends.
33-
33+
3434
Returns:
3535
list[API]: List of ACA-specific APIs.
3636
"""
37-
37+
3838
# Define the APIs with Container Apps backends
3939
pol_backend = utils.read_policy_xml(BACKEND_XML_POLICY_PATH)
4040
pol_aca_backend_1 = pol_backend.format(backend_id = 'aca-backend-1')
@@ -52,18 +52,18 @@ def _create_aca_specific_apis() -> list[API]:
5252
# API 3: Hello World (ACA Backend Pool)
5353
api_hwaca_pool_get = GET_APIOperation('This is a GET for Hello World on ACA Backend Pool')
5454
api_hwaca_pool = API('hello-world-aca-pool', 'Hello World (ACA Pool)', '/aca-pool', 'This is the ACA API for Backend Pool', pol_aca_backend_pool, [api_hwaca_pool_get])
55-
55+
5656
return [api_hwaca_1, api_hwaca_2, api_hwaca_pool]
5757

5858
def main():
5959
"""
6060
Main entry point for command-line usage.
6161
"""
62-
62+
6363
parser = argparse.ArgumentParser(description = 'Create APIM-ACA infrastructure')
6464
parser.add_argument('--location', default = 'eastus2', help = 'Azure region (default: eastus2)')
6565
parser.add_argument('--index', type = int, help = 'Infrastructure index')
66-
parser.add_argument('--sku', choices = ['Basicv2', 'Standardv2', 'Premiumv2'], default = 'Basicv2', help = 'APIM SKU (default: Basicv2)')
66+
parser.add_argument('--sku', choices = ['Basicv2', 'Standardv2', 'Premiumv2'], default = 'Basicv2', help = 'APIM SKU (default: Basicv2)')
6767
args = parser.parse_args()
6868

6969
# Convert SKU string to enum using the enum's built-in functionality

infrastructure/simple-apim/create_infrastructure.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,14 @@
99
import utils
1010

1111

12-
def create_infrastructure(location: str, index: int, apim_sku: APIM_SKU) -> None:
12+
def create_infrastructure(location: str, index: int, apim_sku: APIM_SKU) -> None:
1313
try:
1414
# Check if infrastructure already exists to determine messaging
1515
infrastructure_exists = utils.does_resource_group_exist(utils.get_infra_rg_name(utils.INFRASTRUCTURE.SIMPLE_APIM, index))
16-
16+
1717
result = SimpleApimInfrastructure(location, index, apim_sku).deploy_infrastructure(infrastructure_exists)
1818
sys.exit(0 if result.success else 1)
19-
19+
2020
except Exception as e:
2121
print(f'\n💥 Error: {str(e)}')
2222
sys.exit(1)
@@ -25,11 +25,11 @@ def main():
2525
"""
2626
Main entry point for command-line usage.
2727
"""
28-
28+
2929
parser = argparse.ArgumentParser(description = 'Create Simple APIM infrastructure')
3030
parser.add_argument('--location', default = 'eastus2', help = 'Azure region (default: eastus2)')
3131
parser.add_argument('--index', type = int, help = 'Infrastructure index')
32-
parser.add_argument('--sku', choices = ['Basicv2', 'Standardv2', 'Premiumv2'], default = 'Basicv2', help = 'APIM SKU (default: Basicv2)')
32+
parser.add_argument('--sku', choices = ['Basicv2', 'Standardv2', 'Premiumv2'], default = 'Basicv2', help = 'APIM SKU (default: Basicv2)')
3333
args = parser.parse_args()
3434

3535
# Convert SKU string to enum using the enum's built-in functionality
@@ -42,4 +42,4 @@ def main():
4242
create_infrastructure(args.location, args.index, apim_sku)
4343

4444
if __name__ == '__main__':
45-
main()
45+
main()

requirements.txt

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,19 @@
11
# This is a comprehensive requirements.txt file to ensure that one installation covers everything.
22

3+
# Core dependencies
34
requests
45
setuptools
56
pandas
67
matplotlib
78
pyjwt
8-
pytest
9-
pytest-cov
109
azure.storage.blob
1110
azure.identity
1211
jupyter
1312
ipykernel
1413
notebook
15-
python-dotenv
14+
python-dotenv
15+
16+
# Dev tools for linting, formatting, testing, etc.
17+
pylint
18+
pytest
19+
pytest-cov

0 commit comments

Comments
 (0)