Skip to content

Commit 01771e1

Browse files
committed
Added skills
1 parent 893b5cf commit 01771e1

File tree

5 files changed

+327
-14
lines changed

5 files changed

+327
-14
lines changed

.github/copilot-instructions.md

Lines changed: 24 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,21 @@ This project requires **Python 3.10 or newer**.
5454

5555
---
5656

57+
## CODE FORMATTING
58+
59+
### RUNNING BLACK
60+
61+
**COMMAND:**
62+
```bash
63+
black featuremanagement
64+
```
65+
66+
Line length is configured to 120 in `pyproject.toml`.
67+
68+
**Always run black before pylint and mypy**, as formatting fixes can resolve issues those tools detect.
69+
70+
---
71+
5772
## PYLINT OPERATIONS
5873

5974
### RUNNING PYLINT
@@ -94,19 +109,6 @@ The project uses `strict = True` in `mypy.ini`.
94109

95110
---
96111

97-
## CODE FORMATTING
98-
99-
### RUNNING BLACK
100-
101-
**COMMAND:**
102-
```bash
103-
black featuremanagement
104-
```
105-
106-
Line length is configured to 120 in `pyproject.toml`.
107-
108-
---
109-
110112
## TESTING
111113

112114
### RUNNING TESTS
@@ -119,3 +121,12 @@ pytest tests
119121
- Sync tests are in `tests/test_*.py`
120122
- Async tests use `pytest-asyncio` and are in files ending with `_async.py`
121123
- Run tests with: `pytest tests`
124+
125+
---
126+
127+
## NEW FEATURES
128+
129+
When adding a new user-facing feature or capability:
130+
131+
- Create a sample in `samples/` demonstrating the feature.
132+
- Add corresponding unit tests (sync and async where applicable).
Lines changed: 84 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,84 @@
1+
---
2+
name: ci-failure-debugging
3+
description: >
4+
Debug and fix failing CI validation checks for this Python project.
5+
Use when asked to fix CI failures, debug failing PR checks, fix pylint/mypy/black/cspell/pytest errors,
6+
or when a PR validation workflow fails.
7+
---
8+
9+
# CI Failure Debugging
10+
11+
This project's PR validation runs the following checks on Python 3.10–3.14. To debug failures, identify which step failed and follow the corresponding section below.
12+
13+
## Step 1: Identify the failing check
14+
15+
The validation workflow runs these steps in order:
16+
17+
1. **black** — code formatting
18+
2. **pylint** — static analysis
19+
3. **mypy** — type checking (strict mode)
20+
4. **cspell** — spell checking
21+
5. **pytest** — unit tests with coverage
22+
6. **pylint (samples/tests)** — lint samples and tests with relaxed rules
23+
24+
## Step 2: Reproduce locally
25+
26+
Set up the environment first:
27+
28+
```bash
29+
python -m pip install -e ".[dev,test]"
30+
```
31+
32+
Then run the specific failing check:
33+
34+
| Check | Command | Notes |
35+
|-------|---------|-------|
36+
| pylint | `pylint featuremanagement` | |
37+
| black | `black --check featuremanagement` | Use `black featuremanagement` to auto-fix |
38+
| mypy | `mypy featuremanagement` | Uses `strict = True` from `mypy.ini` |
39+
| cspell | `npx cspell "**"` | Config in `cspell.config.yaml`, custom words in `project-words.txt` |
40+
| pytest | `pytest tests --doctest-modules --cov-report=xml --cov-report=html` | |
41+
| pylint (samples) | `pylint --disable=missing-function-docstring,missing-class-docstring samples tests` | Requires `python -m pip install -r samples/requirements.txt` |
42+
43+
## Step 3: Fix the issue
44+
45+
### pylint failures
46+
47+
- Run `pylint featuremanagement` and fix reported issues.
48+
- Do NOT add `# pylint: disable` comments unless absolutely necessary.
49+
- Do NOT add new imports or dependencies to fix warnings.
50+
- The project disables `duplicate-code` in `pyproject.toml`.
51+
- Max line length is 120. Min public methods is 1. Max branches is 20. Max returns is 7.
52+
53+
### black failures
54+
55+
- Run `black featuremanagement` to auto-format. Line length is 120 (configured in `pyproject.toml`).
56+
- If CI uses `black --check`, it means files need reformatting — run `black` locally to fix.
57+
58+
### mypy failures
59+
60+
- Run `mypy featuremanagement`. The project uses `strict = True` with Python 3.10 target.
61+
- All functions must have type annotations.
62+
- Use `Optional[X]` or `X | None` for nullable types.
63+
- Check `mypy.ini` for the full configuration.
64+
65+
### cspell failures
66+
67+
- Misspelled words: fix the typo in your code.
68+
- Legitimate technical terms: add the word to `project-words.txt` (one word per line, alphabetically sorted).
69+
- Do NOT modify `cspell.config.yaml` unless adding a new ignore path.
70+
71+
### pytest failures
72+
73+
- Run `pytest tests` to reproduce.
74+
- Sync tests: `tests/test_*.py`
75+
- Async tests: `tests/test_*_async.py` (use `pytest-asyncio`)
76+
- Time window filter tests: `tests/time_window_filter/`
77+
- Telemetry tests: `tests/test_send_telemetry_appinsights.py`
78+
- If adding new code, ensure both sync and async tests exist where applicable.
79+
80+
### pylint (samples/tests) failures
81+
82+
- This step runs with `--disable=missing-function-docstring,missing-class-docstring`.
83+
- Requires sample dependencies: `python -m pip install -r samples/requirements.txt`.
84+
- Fix any remaining pylint issues in `samples/` and `tests/` directories.

.github/skills/samples/SKILL.md

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
---
2+
name: samples
3+
description: >
4+
Guide for creating or updating sample applications in this project.
5+
Use when adding a new sample, modifying an existing sample, or when asked to demonstrate
6+
a feature management capability with example code.
7+
---
8+
9+
# Sample Applications
10+
11+
Samples live in `samples/` and demonstrate feature management capabilities to users.
12+
13+
## File conventions
14+
15+
- Every sample must have the Microsoft copyright header:
16+
```python
17+
# ------------------------------------------------------------------------
18+
# Copyright (c) Microsoft Corporation. All rights reserved.
19+
# Licensed under the MIT License. See License.txt in the project root for
20+
# license information.
21+
# -------------------------------------------------------------------------
22+
```
23+
- Every sample must have a module-level docstring (one-liner describing what it demonstrates).
24+
- Filename should end with `_sample.py` and describe what is being demonstrated (e.g., `feature_flag_sample.py`, `feature_variant_sample_with_telemetry.py`).
25+
26+
## Structure of a sample
27+
28+
Samples follow this general pattern:
29+
30+
```python
31+
# (copyright header)
32+
"""Sample demonstrating <what this shows>."""
33+
34+
import json
35+
import os
36+
import sys
37+
from featuremanagement import FeatureManager, TargetingContext
38+
39+
# Load feature flags from the local JSON file
40+
file_path = os.path.dirname(os.path.abspath(sys.argv[0]))
41+
with open(os.path.join(file_path, "formatted_feature_flags.json"), encoding="utf-8") as f:
42+
feature_flags = json.load(f)
43+
44+
# Create FeatureManager
45+
feature_manager = FeatureManager(feature_flags)
46+
47+
# Demonstrate the feature
48+
result = feature_manager.is_enabled("FlagName")
49+
print(f"FlagName is {'enabled' if result else 'disabled'}")
50+
```
51+
52+
## Feature flag definitions
53+
54+
Sample feature flags go in `formatted_feature_flags.json` under `feature_management.feature_flags`. Each flag needs at minimum `id` and `enabled`. Add filters, variants, allocation, or telemetry as needed for the sample.
55+
56+
## Custom filters
57+
58+
Custom filters used by samples are defined in their own file (e.g., `random_filter.py`) and imported by the samples that need them.
59+
60+
## Async samples
61+
62+
Async samples import from `featuremanagement.aio` instead of `featuremanagement`. See `quarty_sample.py` for the async pattern.
63+
64+
## Azure-connected samples
65+
66+
Samples that connect to Azure App Configuration:
67+
- Use `azure.appconfiguration.provider.load()` to get configuration
68+
- Authenticate using `DefaultAzureCredential` from `azure-identity`, never connection strings
69+
- List Azure dependencies in `samples/requirements.txt`
70+
71+
## Telemetry samples
72+
73+
Two patterns exist:
74+
1. **Callback-based**: Pass `on_feature_evaluated=publish_telemetry` to `FeatureManager` and use `track_event()` from `featuremanagement.azuremonitor`.
75+
2. **Web app span processor**: Use `TargetingSpanProcessor` from `featuremanagement.azuremonitor` with `configure_azure_monitor(span_processors=[...])`.
76+
77+
## Dependencies
78+
79+
Any new package a sample needs must be added to `samples/requirements.txt`. CI installs these before linting samples.
80+
81+
## Linting
82+
83+
Samples are linted with relaxed rules:
84+
```bash
85+
pylint --disable=missing-function-docstring,missing-class-docstring samples tests
86+
```
87+
88+
Function and class docstrings are NOT required in samples, but module-level docstrings ARE.
89+
90+
## Checklist for adding a new sample
91+
92+
1. [ ] Create `samples/feature_<name>_sample.py` with copyright header and module docstring.
93+
2. [ ] Add any new feature flags to `formatted_feature_flags.json`.
94+
3. [ ] Add any new dependencies to `samples/requirements.txt`.
95+
4. [ ] Verify lint passes: `pylint --disable=missing-function-docstring,missing-class-docstring samples`
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
---
2+
name: sync-async-pattern
3+
description: >
4+
Guide for implementing sync/async mirrored code in this project.
5+
Use when adding new classes, methods, or feature filters that need both sync and async versions,
6+
or when modifying existing sync code that has an async counterpart in featuremanagement/aio/.
7+
---
8+
9+
# Sync/Async Mirroring Pattern
10+
11+
This project maintains parallel sync and async implementations. Every change to sync code in `featuremanagement/` must be mirrored in `featuremanagement/aio/`, and vice versa.
12+
13+
## Directory mapping
14+
15+
| Sync | Async |
16+
|------|-------|
17+
| `featuremanagement/_featuremanager.py` | `featuremanagement/aio/_featuremanager.py` |
18+
| `featuremanagement/_featurefilters.py` | `featuremanagement/aio/_featurefilters.py` |
19+
| `featuremanagement/_defaultfilters.py` | `featuremanagement/aio/_defaultfilters.py` |
20+
| `featuremanagement/__init__.py` | `featuremanagement/aio/__init__.py` |
21+
22+
Shared code that does NOT have an async counterpart:
23+
- `featuremanagement/_featuremanagerbase.py` — base class used by both sync and async `FeatureManager`
24+
- `featuremanagement/_models/` — data models imported by both
25+
- `featuremanagement/_time_window_filter/` — time window logic (no I/O, used as-is)
26+
- `featuremanagement/azuremonitor/` — telemetry (no async version)
27+
28+
## Copyright header
29+
30+
Every source file MUST start with:
31+
32+
```python
33+
# ------------------------------------------------------------------------
34+
# Copyright (c) Microsoft Corporation. All rights reserved.
35+
# Licensed under the MIT License. See License.txt in the project root for
36+
# license information.
37+
# -------------------------------------------------------------------------
38+
```
39+
40+
Followed by a module-level docstring.
41+
42+
## How to convert sync to async
43+
44+
### Classes
45+
46+
- Keep the **same class name** (e.g., both are `FeatureManager`). Users disambiguate by import path.
47+
- Both sync and async `FeatureManager` inherit from `FeatureManagerBase`.
48+
49+
### Methods
50+
51+
- Add `async` to method definitions: `def evaluate(...)``async def evaluate(...)`
52+
- Add `await` to calls that invoke filters, callbacks, or accessors.
53+
54+
### Default filters (composition pattern)
55+
56+
Async default filters do NOT duplicate logic. They wrap the sync implementation:
57+
58+
```python
59+
from .._defaultfilters import TimeWindowFilter as SyncTimeWindowFilter
60+
61+
class TimeWindowFilter(FeatureFilter):
62+
def __init__(self):
63+
self._filter = SyncTimeWindowFilter()
64+
65+
@FeatureFilter.alias("Microsoft.TimeWindow")
66+
async def evaluate(self, context, **kwargs):
67+
return self._filter.evaluate(context, **kwargs)
68+
```
69+
70+
Use this pattern for any new filter whose `evaluate` does not perform I/O.
71+
72+
### Callbacks and accessors
73+
74+
The async `FeatureManager` supports BOTH sync and async callbacks. Use `inspect.iscoroutinefunction` to detect and handle both:
75+
76+
```python
77+
import inspect
78+
79+
if inspect.iscoroutinefunction(self._on_feature_evaluated):
80+
await self._on_feature_evaluated(result)
81+
else:
82+
self._on_feature_evaluated(result)
83+
```
84+
85+
### Imports
86+
87+
- Sync files import from `._models`, `._featurefilters`, etc.
88+
- Async files import from `.._models`, `.._featurefilters`, etc. (one level up from `aio/`).
89+
90+
## `__init__.py` exports
91+
92+
### Sync (`featuremanagement/__init__.py`)
93+
94+
Exports everything: `FeatureManager`, filters, all models, `__version__`, and defines `__all__`.
95+
96+
### Async (`featuremanagement/aio/__init__.py`)
97+
98+
Exports ONLY async-specific classes: `FeatureManager`, `FeatureFilter`, `TimeWindowFilter`, `TargetingFilter`. Does NOT re-export models or `__version__` — users import those from the sync package.
99+
100+
When adding a new public class:
101+
1. Add to sync `__init__.py` with `__all__` entry.
102+
2. If it has an async version, add to async `__init__.py` with `__all__` entry.
103+
104+
## Test file naming
105+
106+
| Sync test | Async counterpart |
107+
|-----------|-------------------|
108+
| `tests/test_feature_manager.py` | `tests/test_feature_manager_async.py` |
109+
| `tests/test_feature_variants.py` | `tests/test_feature_variants_async.py` |
110+
| `tests/test_default_feature_flags.py` | `tests/test_default_feature_flags_async.py` |
111+
112+
- Async test files append `_async` to the sync filename.
113+
- Async tests use `pytest-asyncio` with `@pytest.mark.asyncio` on test functions.
114+
- Not every sync test needs an async counterpart (e.g., refresh and telemetry tests are sync-only).
115+
116+
## Checklist for adding new code
117+
118+
1. [ ] Write the sync implementation in `featuremanagement/`.
119+
2. [ ] Write the async mirror in `featuremanagement/aio/` following the patterns above.
120+
3. [ ] Export from both `__init__.py` files if public.
121+
4. [ ] Write sync tests in `tests/test_*.py`.
122+
5. [ ] Write async tests in `tests/test_*_async.py`.
123+
6. [ ] Run all validation: `pylint featuremanagement`, `black featuremanagement`, `mypy featuremanagement`, `pytest tests`.

.github/workflows/validate.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,10 @@ jobs:
1818
run: |
1919
python -m pip install --upgrade pip
2020
python -m pip install ".[dev]"
21+
- uses: psf/black@26.3.0
2122
- name: Analysing the code with pylint
2223
run: |
2324
pylint featuremanagement
24-
- uses: psf/black@26.3.0
2525
- name: Run mypy
2626
run: |
2727
mypy featuremanagement

0 commit comments

Comments
 (0)