You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@airflow.apache.org by "sunank200 (via GitHub)" <gi...@apache.org> on 2025/04/08 22:32:49 UTC
Re: [I] Airflow 2 to 3 Rest API Changes [airflow]
sunank200 commented on issue #43378:
URL: https://github.com/apache/airflow/issues/43378#issuecomment-2787798694
I’ve written a Python script to automatically compare the OpenAPI specifications for Airflow 2.10.5 (v1) and Airflow 3.0.0 (v2) to identify breaking changes. In our context, a **breaking change** includes:
- Removal of an entire endpoint or an HTTP method.
- Changes in endpoint definitions that modify required parameters, request bodies, or response schemas.
### How to Run the Script
1. **Install dependencies:**
```bash
pip install pyyaml requests deepdiff
```
2. **Use the raw URL for the v1 spec:**
- v1: `https://raw.githubusercontent.com/apache/airflow/2.10.5/airflow/api_connexion/openapi/v1.yaml`
- v2 (local): `http://localhost:28080/openapi.json`
3. **Run the script with:**
```bash
python compare_openapi.py https://raw.githubusercontent.com/apache/airflow/2.10.5/airflow/api_connexion/openapi/v1.yaml http://localhost:28080/openapi.json
```
The script will print the detected breaking changes and write a report to `report.txt`.
### Final Script
```python
import sys
import json
import yaml
import requests
from deepdiff import DeepDiff
import pprint
def load_spec_from_file(filepath):
with open(filepath, "r") as f:
if filepath.endswith(".json"):
return json.load(f)
else:
return yaml.safe_load(f)
def load_spec_from_url(url):
response = requests.get(url)
response.raise_for_status()
if url.endswith(".json"):
return response.json()
else:
return yaml.safe_load(response.text)
def normalize_spec(spec, version_prefixes=("/api/v1", "/api/v2")):
new_paths = {}
for path, methods in spec.get("paths", {}).items():
for prefix in version_prefixes:
if path.startswith(prefix):
path = path[len(prefix):]
new_paths[path or "/"] = methods
spec["paths"] = new_paths
spec.pop("servers", None)
return spec
def compare_specs(spec1, spec2):
breaking_changes = []
notes = []
pp = pprint.PrettyPrinter(indent=2)
paths1 = spec1.get("paths", {})
paths2 = spec2.get("paths", {})
for path in sorted(paths1.keys()):
if path not in paths2:
breaking_changes.append(f"Endpoint '{path}' removed in new spec")
else:
for method in sorted(paths1[path].keys()):
if method not in paths2[path]:
breaking_changes.append(f"Method '{method.upper()}' for endpoint '{path}' removed in new spec")
else:
diff = DeepDiff(paths1[path][method], paths2[path][method], ignore_order=True)
if diff:
formatted_diff = pp.pformat(diff)
breaking_changes.append(f"Method '{method.upper()}' for endpoint '{path}' changed:\n{formatted_diff}")
for path in sorted(paths2.keys()):
if path not in paths1:
notes.append(f"New endpoint '{path}' added in new spec")
else:
for method in sorted(paths2[path].keys()):
if method not in paths1[path]:
notes.append(f"New method '{method.upper()}' for endpoint '{path}' added in new spec")
return breaking_changes, notes
if __name__ == "__main__":
if len(sys.argv) < 3:
print("Usage: python compare_openapi.py <spec1_path_or_url> <spec2_path_or_url>")
sys.exit(1)
spec1_source = sys.argv[1]
spec2_source = sys.argv[2]
try:
if spec1_source.startswith("http"):
spec1 = load_spec_from_url(spec1_source)
else:
spec1 = load_spec_from_file(spec1_source)
except Exception as e:
print(f"Failed to load spec from {spec1_source}: {e}")
sys.exit(1)
try:
if spec2_source.startswith("http"):
spec2 = load_spec_from_url(spec2_source)
else:
spec2 = load_spec_from_file(spec2_source)
except Exception as e:
print(f"Failed to load spec from {spec2_source}: {e}")
sys.exit(1)
spec1 = normalize_spec(spec1)
spec2 = normalize_spec(spec2)
breaking, notes = compare_specs(spec1, spec2)
report_lines = []
report_lines.append("API Comparison Report:")
report_lines.append("======================\n")
if notes:
report_lines.append("Additional Notes (Non-Breaking Changes):")
for note in notes:
report_lines.append(" - " + note)
report_lines.append("")
if breaking:
report_lines.append("Breaking Changes Detected:")
for change in breaking:
report_lines.append("\n - " + change)
else:
report_lines.append("No breaking changes detected.")
report_content = "\n".join(report_lines)
print(report_content)
output_filename = "report.txt"
try:
with open(output_filename, "w") as f:
f.write(report_content)
print(f"\nReport written to {output_filename}")
except Exception as e:
print(f"Failed to write report to {output_filename}: {e}")
```
Following is the report.txt I got from the script:
[report.txt](https://github.com/user-attachments/files/19657773/report.txt)
--
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.
To unsubscribe, e-mail: commits-unsubscribe@airflow.apache.org
For queries about this service, please contact Infrastructure at:
users@infra.apache.org