pythograph 0.1.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- pythograph-0.1.0/.gitignore +25 -0
- pythograph-0.1.0/CHANGELOG.md +102 -0
- pythograph-0.1.0/LICENSE +21 -0
- pythograph-0.1.0/PKG-INFO +336 -0
- pythograph-0.1.0/README.ko.md +246 -0
- pythograph-0.1.0/README.md +307 -0
- pythograph-0.1.0/pyproject.toml +96 -0
- pythograph-0.1.0/src/pythograph/__init__.py +7 -0
- pythograph-0.1.0/src/pythograph/__main__.py +6 -0
- pythograph-0.1.0/src/pythograph/cli/__init__.py +1 -0
- pythograph-0.1.0/src/pythograph/cli/common.py +73 -0
- pythograph-0.1.0/src/pythograph/cli/graph_commands.py +383 -0
- pythograph-0.1.0/src/pythograph/cli/main.py +450 -0
- pythograph-0.1.0/src/pythograph/exchange/__init__.py +1 -0
- pythograph-0.1.0/src/pythograph/exchange/document.py +280 -0
- pythograph-0.1.0/src/pythograph/exchange/order.py +168 -0
- pythograph-0.1.0/src/pythograph/exchange/persistence.py +144 -0
- pythograph-0.1.0/src/pythograph/exchange/scope.py +271 -0
- pythograph-0.1.0/src/pythograph/exchange/template.py +177 -0
- pythograph-0.1.0/src/pythograph/graph/__init__.py +0 -0
- pythograph-0.1.0/src/pythograph/graph/build.py +511 -0
- pythograph-0.1.0/src/pythograph/graph/calls.py +381 -0
- pythograph-0.1.0/src/pythograph/graph/collect.py +383 -0
- pythograph-0.1.0/src/pythograph/graph/document.py +272 -0
- pythograph-0.1.0/src/pythograph/graph/framework.py +176 -0
- pythograph-0.1.0/src/pythograph/graph/framework_table.py +1728 -0
- pythograph-0.1.0/src/pythograph/graph/index.py +262 -0
- pythograph-0.1.0/src/pythograph/graph/limitations.py +149 -0
- pythograph-0.1.0/src/pythograph/graph/model.py +120 -0
- pythograph-0.1.0/src/pythograph/graph/mro.py +314 -0
- pythograph-0.1.0/src/pythograph/graph/revision.py +73 -0
- pythograph-0.1.0/src/pythograph/graph/roots.py +122 -0
- pythograph-0.1.0/src/pythograph/graph/scope.py +882 -0
- pythograph-0.1.0/src/pythograph/graph/traversal.py +474 -0
- pythograph-0.1.0/src/pythograph/graph/values.py +129 -0
- pythograph-0.1.0/src/pythograph/persistence/__init__.py +0 -0
- pythograph-0.1.0/src/pythograph/persistence/command.py +288 -0
- pythograph-0.1.0/src/pythograph/persistence/django/__init__.py +0 -0
- pythograph-0.1.0/src/pythograph/persistence/django/apps.py +231 -0
- pythograph-0.1.0/src/pythograph/persistence/django/catalog.py +1120 -0
- pythograph-0.1.0/src/pythograph/persistence/django/declarations.py +128 -0
- pythograph-0.1.0/src/pythograph/persistence/django/fields.py +352 -0
- pythograph-0.1.0/src/pythograph/persistence/django/queries.py +998 -0
- pythograph-0.1.0/src/pythograph/persistence/django/settings.py +384 -0
- pythograph-0.1.0/src/pythograph/persistence/location.py +33 -0
- pythograph-0.1.0/src/pythograph/persistence/model.py +102 -0
- pythograph-0.1.0/src/pythograph/persistence/names.py +214 -0
- pythograph-0.1.0/src/pythograph/persistence/scope.py +261 -0
- pythograph-0.1.0/src/pythograph/persistence/sql.py +795 -0
- pythograph-0.1.0/src/pythograph/persistence/sqlalchemy/__init__.py +0 -0
- pythograph-0.1.0/src/pythograph/persistence/sqlalchemy/catalog.py +1082 -0
- pythograph-0.1.0/src/pythograph/persistence/sqlalchemy/queries.py +540 -0
- pythograph-0.1.0/src/pythograph/persistence/sqltext.py +296 -0
- pythograph-0.1.0/src/pythograph/py.typed +0 -0
- pythograph-0.1.0/src/pythograph/routes/__init__.py +1 -0
- pythograph-0.1.0/src/pythograph/routes/classes.py +493 -0
- pythograph-0.1.0/src/pythograph/routes/command.py +137 -0
- pythograph-0.1.0/src/pythograph/routes/django/__init__.py +1 -0
- pythograph-0.1.0/src/pythograph/routes/django/drf.py +389 -0
- pythograph-0.1.0/src/pythograph/routes/django/extract.py +527 -0
- pythograph-0.1.0/src/pythograph/routes/django/settings.py +141 -0
- pythograph-0.1.0/src/pythograph/routes/django/urlconf.py +951 -0
- pythograph-0.1.0/src/pythograph/routes/django/views.py +378 -0
- pythograph-0.1.0/src/pythograph/routes/flask/__init__.py +1 -0
- pythograph-0.1.0/src/pythograph/routes/flask/extract.py +1018 -0
- pythograph-0.1.0/src/pythograph/routes/flask/rules.py +256 -0
- pythograph-0.1.0/src/pythograph/routes/model.py +134 -0
- pythograph-0.1.0/src/pythograph/routes/pattern.py +883 -0
- pythograph-0.1.0/src/pythograph/routes/versions.py +232 -0
- pythograph-0.1.0/src/pythograph/source/__init__.py +1 -0
- pythograph-0.1.0/src/pythograph/source/evaluate.py +164 -0
- pythograph-0.1.0/src/pythograph/source/project.py +371 -0
- pythograph-0.1.0/src/pythograph/source/symbols.py +562 -0
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# 가상 환경과 도구 캐시
|
|
2
|
+
.venv/
|
|
3
|
+
venv/
|
|
4
|
+
__pycache__/
|
|
5
|
+
*.py[cod]
|
|
6
|
+
.pytest_cache/
|
|
7
|
+
.mypy_cache/
|
|
8
|
+
.ruff_cache/
|
|
9
|
+
.coverage
|
|
10
|
+
.coverage.*
|
|
11
|
+
coverage.xml
|
|
12
|
+
htmlcov/
|
|
13
|
+
build/
|
|
14
|
+
dist/
|
|
15
|
+
*.egg-info/
|
|
16
|
+
|
|
17
|
+
# 비밀값과 로컬 설정은 커밋하지 않는다
|
|
18
|
+
.env
|
|
19
|
+
.env.*
|
|
20
|
+
|
|
21
|
+
# fixture 실행 부산물(오라클은 스크래치 가상 환경에서 fixture를 import한다)
|
|
22
|
+
db.sqlite3
|
|
23
|
+
|
|
24
|
+
# GLM 리뷰 패킷 임시 파일
|
|
25
|
+
.review-tmp/
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
이 프로젝트의 주요 변경 사항을 기록한다.
|
|
4
|
+
|
|
5
|
+
## [Unreleased]
|
|
6
|
+
|
|
7
|
+
## [0.1.0] - 2026-09-30
|
|
8
|
+
|
|
9
|
+
### Changed
|
|
10
|
+
|
|
11
|
+
- Phase 6 종료 조건 기록(`experiments/e2e/recorded/`)의 Android 문서를 kartograph `4c09d91`(#122 Retrofit route-call usr·상속
|
|
12
|
+
인터페이스 호출 간선, #123 Retrofit baseUrl 결합)로 다시 기록하고, route 선택에 `POST /api/checkout/`을 더해 trace를 다시
|
|
13
|
+
만들었다. Android `OrdersService.getOrder` → `OrderRepository.load`(1) → `OrderViewModel.refresh`(2)와 `OrdersService.checkout`
|
|
14
|
+
→ `CheckoutRepository.submit`(1) → `CheckoutViewModel.pay`(2)가 체인에 붙고, `unattributed-calls-omitted` gap(route 3개·
|
|
15
|
+
relation 2개)이 사라졌다. **Android 기록은 kartograph `4c09d91` 이상에 의존한다** — 그 전 판으로 다시 기록하면
|
|
16
|
+
`tests/test_e2e_trace.py`가 실패한다. iOS 기록(cartograph 0.22.0)과 서버 문서는 바뀌지 않았다.
|
|
17
|
+
- `experiments/e2e/record_clients.py --client ios|android`: 한 클라이언트만 다시 기록한다(생략하면 둘 다).
|
|
18
|
+
|
|
19
|
+
### Added
|
|
20
|
+
|
|
21
|
+
- 릴리스 준비(게시는 아직 하지 않음): `.github/workflows/release.yml` 초안(`v*` 태그 push → ubuntu·macOS × Python 3.10·3.13 검증 →
|
|
22
|
+
태그·버전 대조와 sdist·wheel 빌드, 깨끗한 가상 환경의 wheel 설치·`--version`·CLI 계약 확인 → 환경 `pypi`에서 PyPI Trusted
|
|
23
|
+
Publishing 게시, 액션은 커밋 SHA 고정)과 관리자가 직접 할 절차를 적은 `RELEASING.md`.
|
|
24
|
+
- `DOGFOOD.md`: 공개 앱 네 개(Django-Styleguide-Example, babybuddy, microblog, netbox)의 route 오라클 정밀도·재현율,
|
|
25
|
+
schemagraph·isthmus 조인율, 핸들러 → relation-use 도달률, 미해석 호출 분포, 실행 시간·메모리와 고친 문제.
|
|
26
|
+
|
|
27
|
+
- `pythograph graph`·`reach`·`impact`: 표준 라이브러리 `ast`로 만든 파이썬 호출 그래프(`pythograph-graph` v1 스냅샷)와
|
|
28
|
+
isthmus `language-traversal` v1 순회 문서(`docs/GRAPH.md`). 정점은 모듈·함수·메서드·클래스·중첩 정의와 routes가 등록
|
|
29
|
+
클래스 기준으로 내는 상속 멤버 핸들러(`<클래스>.<멤버>`)이고, id는 routes·schema `symbol.usr`와 같다. 간선은 import(절대·상대·
|
|
30
|
+
별칭·`__init__` 재수출·`*`)·모듈 속성·생성자(`__init__`)·C3 MRO로 푼 `self`·`super()`·주석 수신자·property·장식자·콜백·참조·
|
|
31
|
+
클래스 속성·상속이며 근거 등급 `direct`와 하위 클래스 재정의 `candidate`를 싣는다(`bound`는 아직 없다 — `--dispatch bound`는
|
|
32
|
+
direct 그래프다). 대상을 모르는 호출은 추측하지 않고 이유별로 세어 `unresolvedCalls`로 낸다(타입 모르는 수신자는 프로젝트가
|
|
33
|
+
그 이름을 정의·대입하지 않을 때만 외부로 확정).
|
|
34
|
+
- 프레임워크 디스패치: Django 5.2.17·DRF 3.18.1·Flask 3.1.3 설치본 소스를 ast로 읽어 만든 훅 표
|
|
35
|
+
(`experiments/graph/dump_framework_hooks.py` → `graph/framework_table.py`)로 `as_view()` 디스패치 경로와 프레임워크 구현
|
|
36
|
+
(`ModelViewSet.retrieve` → `get_queryset`, `ModelSerializer.save` → `create`)이 부르는 프로젝트 훅을 잇는다. 프레임워크가 클래스
|
|
37
|
+
속성으로 만드는 객체는 `framework-callback` 미해석으로 센다.
|
|
38
|
+
- 순회: `--dispatch direct|bound|candidates`, `--max-depth`, `--max-reached`, `--roots-from <file|->`(JSON 배열·bridge-facts),
|
|
39
|
+
`--revision`, `--generated-at`. 다중 root 단일 패스(root별 오라클과 무작위 그래프 비교 테스트), root별 하한 `evidence`,
|
|
40
|
+
`rootsTruncated`, 정점이 아닌 root는 문서를 쓰고 64(root-not-found), 제어 문자 id는 64. `revision`은 작업 트리가 깨끗할 때의
|
|
41
|
+
git HEAD(또는 `--revision`), `graphRevision`은 그래프 내용 SHA-256.
|
|
42
|
+
- `exchange/order.py`: registration-order `order` 검증기. isthmus `f9dcd1d`의 `http-dispatch` 벡터를 벤더링하고
|
|
43
|
+
`dispatch.validate` 18건과 routes 출력 golden에 적용한다(생산자 사례 78건 100%).
|
|
44
|
+
- Phase 6 종료 조건 fixture(`fixtures/e2e/shop-api`, `experiments/e2e/`): 합성 Django+DRF 서버, Django DDL의 schemagraph 카탈로그,
|
|
45
|
+
합성 iOS(cartograph)·Android(kartograph) 클라이언트를 isthmus `trace`(workspace)로 잇고 세 질문(API → DB·DB 의존자, API →
|
|
46
|
+
클라이언트 호출부 → 영향 심볼, 테이블 → API → 클라이언트)의 기대 경로 일치를 기록한다. `tests/test_e2e_trace.py`가 오프라인으로
|
|
47
|
+
다시 확인한다.
|
|
48
|
+
|
|
49
|
+
- 저장소 골격: `pyproject.toml`(hatchling, Python 3.10 이상, 런타임 의존성 없음, 콘솔 스크립트 `pythograph`),
|
|
50
|
+
`uv tool install`·`pipx install` 배포, dev 도구(uv·pytest·pytest-cov·ruff·mypy), GitHub Actions CI(ubuntu·macOS,
|
|
51
|
+
Python 3.10·3.13, lint·format·typecheck·테스트·커버리지 90%·CLI 계약·wheel 설치 계약).
|
|
52
|
+
- CLI: `pythograph --version`, `help`, 종료 코드 계약 0/2/64(1은 예약, 예기치 못한 내부 오류도 2),
|
|
53
|
+
키 정렬 결정적 JSON, `--generated-at`으로 바이트 단위 재현.
|
|
54
|
+
- `pythograph routes --role server`: Django URLconf(설정 모듈·`ROOT_URLCONF`·`include`·변환기·`re_path` 정규식 구문
|
|
55
|
+
트리 변환·함수/클래스 뷰 method), Django REST framework(`SimpleRouter`·`DefaultRouter`·`@action`·형식 접미사·
|
|
56
|
+
`api_view`·generic view·viewset), Flask/Werkzeug(앱 팩토리·블루프린트 중첩·`add_url_rule`·`MethodView`·변환기·
|
|
57
|
+
`strict_slashes`·`merge_slashes`)를 isthmus http `route-decl`(platform `python`)로 낸다. Django는
|
|
58
|
+
`dispatch: "registration-order"`와 `order`, Flask는 `specificity`다. 규칙은 Django 5.2.17·DRF 3.18.1·Flask 3.1.3·
|
|
59
|
+
Werkzeug 3.1.9 소스로 확인했다(`docs/HTTP-ROUTES.md`).
|
|
60
|
+
- 심볼 id 규칙 `<프로젝트 상대 경로>#<어휘적 점 경로>`(클래스 핸들러는 등록 클래스 기준). 다음 단계 호출 그래프가
|
|
61
|
+
같은 id를 쓴다.
|
|
62
|
+
- `--dispatch specificity`: registration-order를 아직 받지 않는 isthmus용 근사(거짓 match만 가능, 거짓 error 없음).
|
|
63
|
+
- isthmus 공유 적합성 벡터를 `78d3dee`에서 벤더링하고 `conformance.lock`으로 고정했다. 해당 생산자 사례 60건을
|
|
64
|
+
100% 통과한다.
|
|
65
|
+
- `pythograph schema`: Django 모델(추상·프록시·다중 테이블 상속, `INSTALLED_APPS`·`AppConfig` 앱 라벨, 백엔드별
|
|
66
|
+
`truncate_name` 절단, M2M 중간 테이블, django.contrib 모델)과 QuerySet 사용(조회식 조인·역관계·관계 매니저·
|
|
67
|
+
`values`·`F`·`Q`·집계·`raw()`·`RawSQL`·`extra(tables=)`), SQLAlchemy 2.x·Flask-SQLAlchemy 3 매핑(Declarative·
|
|
68
|
+
믹스인·상속·스키마·Core `Table`·자동 snake_case 이름, 버전 미상이면 2·3 규칙이 같은 이름만)과 질의
|
|
69
|
+
(`select`·`session.query`·`Model.query`·`filter_by`·`text()`), SQL 텍스트(가족 공유 추출기 포트와 공유 벡터)를
|
|
70
|
+
isthmus persistence `relation-use`(platform `python`)로 낸다. 확정하지 못한 이름은 dynamic 사실과 한계다. 테스트·
|
|
71
|
+
마이그레이션은 기본으로 읽지 않는다. 규칙은 Django 5.2.17·SQLAlchemy 2.0.54·Flask-SQLAlchemy 3.1.1(2.5.1 비교)
|
|
72
|
+
소스로 확인했다(`docs/PERSISTENCE.md`).
|
|
73
|
+
- persistence 명명 벡터(`fixtures/persistence-naming/`)와 오라클(`experiments/persistence/`): 실제 ORM(Django 백엔드 4개,
|
|
74
|
+
SQLAlchemy, Flask-SQLAlchemy) 이름과 100% 일치, 기록을 오프라인 테스트로 다시 확인한다. 사용 fixture 두 개의 ORM DDL로
|
|
75
|
+
schemagraph·isthmus 종단 조인에서 error 0을 확인했다.
|
|
76
|
+
- 합성 fixture(`fixtures/django/drf-shop`, `fixtures/flask/blog-app`)와 오라클 하네스(`experiments/oracle/`):
|
|
77
|
+
resolver 순회·DRF 라우터·Flask `url_map` 대비 정밀도 100%, 기록을 오프라인 테스트로 다시 확인한다.
|
|
78
|
+
|
|
79
|
+
### Fixed
|
|
80
|
+
|
|
81
|
+
- 이름 해석: 프로젝트 모듈의 `from x import *`를 패키지 `__init__`을 거쳐 거듭 따라가고 리터럴 `__all__`·밑줄 규칙을 지킨다.
|
|
82
|
+
외부·`__all__` 미확정 모듈의 `*`는 가림(그래프 `star-import`)이다. routes·schema는 별 import를 전혀 따라가지 않았고
|
|
83
|
+
그래프는 한 단계만 따라갔다.
|
|
84
|
+
- schema: `django.forms` 필드(`forms.CharField` 등)를 선언한 ModelForm·Form을 모델로 오인해 없는 테이블을 내던 문제,
|
|
85
|
+
`from app import models` 뒤 `models.Book.objects`처럼 모듈 속성으로 닿은 모델을 풀지 않던 문제(바깥 함수까지 지역에서 다시
|
|
86
|
+
묶으면 풀지 않는다), 조건부 `INSTALLED_APPS.remove(...)`가 앱 목록 전체를 불완전하게 만들던 문제(무조건 `remove`는 적용).
|
|
87
|
+
- routes(Django): 맨 앞 include 문자열이 설정값(`path(settings.BASE_PATH, include(...))`)이면 하위 경로가 모두 dynamic이던
|
|
88
|
+
문제 → 그 조각을 떼고 `pathAnchor: "base"`와 `unresolved-route-prefix:`. `django.contrib.auth.views` 클래스(`LogoutView`는
|
|
89
|
+
`http_method_names`로 post만)와 `Base…View`·`ProcessFormView`·`DeletionMixin`·템플릿·날짜 믹스인을 알려진 클래스 표에
|
|
90
|
+
더했다(Django 5.2.17 설치본 조사). 호출 그래프 프레임워크 표도 같은 클래스로 다시 만들어 상속 핸들러 route usr가 그래프
|
|
91
|
+
정점이 된다. 설정 모듈을 찾지 못한 한계 문구에 `--settings` 안내를 더했다.
|
|
92
|
+
- routes(Flask): 앱 팩토리 안의 import(`from app.auth import bp as auth_bp`, `from . import main`)를 따라가 `register_blueprint`
|
|
93
|
+
대상과 `url_prefix`를 푼다. import 뒤 대입·반복 변수·with 대상으로 다시 묶은 이름은 풀지 않는다.
|
|
94
|
+
- `graph`: 16 Mi 문자 출력 상한을 넘을 때 "isthmus rejects" 대신 상한과 `reach`/`impact` 사용을 안내한다.
|
|
95
|
+
|
|
96
|
+
### Changed
|
|
97
|
+
|
|
98
|
+
- 배포 메타데이터: Python 3.10~3.13·Django·Flask·`Typing :: Typed` 분류자, Repository·Changelog URL. README의 문서 링크를
|
|
99
|
+
PyPI에서도 열리도록 절대 URL로 바꾸고, 설치 절에 PyPI 릴리스 전까지의 GitHub·wheel 설치(`uv tool install`·`pipx install`)를 적었다.
|
|
100
|
+
- isthmus 공유 적합성 벡터를 `f9dcd1d`로 다시 벤더링했다(`http-dispatch.json` 추가).
|
|
101
|
+
- README·`docs/HTTP-ROUTES.md`·`docs/PERSISTENCE.md`: isthmus `main`(`f9dcd1d`)이 `platform: "python"`(http
|
|
102
|
+
`registration-order`, persistence, python 순회 분석)을 받는다는 호환 정보와 호출 그래프 구현을 반영했다.
|
pythograph-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Coden
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: pythograph
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Static facts for Python (Django, Django REST framework, Flask, SQLAlchemy) services for the isthmus bridge-facts exchange: HTTP route declarations and persistence relation uses from the standard-library ast, without importing the analyzed project.
|
|
5
|
+
Project-URL: Homepage, https://github.com/ictechgy/pythograph
|
|
6
|
+
Project-URL: Repository, https://github.com/ictechgy/pythograph
|
|
7
|
+
Project-URL: Issues, https://github.com/ictechgy/pythograph/issues
|
|
8
|
+
Project-URL: Changelog, https://github.com/ictechgy/pythograph/blob/main/CHANGELOG.md
|
|
9
|
+
Author: ictechgy
|
|
10
|
+
License-Expression: MIT
|
|
11
|
+
License-File: LICENSE
|
|
12
|
+
Keywords: django,django-rest-framework,flask,http,isthmus,persistence,routes,sqlalchemy,static-analysis
|
|
13
|
+
Classifier: Development Status :: 3 - Alpha
|
|
14
|
+
Classifier: Environment :: Console
|
|
15
|
+
Classifier: Framework :: Django
|
|
16
|
+
Classifier: Framework :: Flask
|
|
17
|
+
Classifier: Intended Audience :: Developers
|
|
18
|
+
Classifier: Operating System :: OS Independent
|
|
19
|
+
Classifier: Programming Language :: Python :: 3
|
|
20
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
22
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
24
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
25
|
+
Classifier: Topic :: Software Development :: Quality Assurance
|
|
26
|
+
Classifier: Typing :: Typed
|
|
27
|
+
Requires-Python: >=3.10
|
|
28
|
+
Description-Content-Type: text/markdown
|
|
29
|
+
|
|
30
|
+
# pythograph
|
|
31
|
+
|
|
32
|
+
[한국어](https://github.com/ictechgy/pythograph/blob/main/README.ko.md)
|
|
33
|
+
|
|
34
|
+
Static facts for Python services (Django, Django REST framework, Flask, SQLAlchemy), emitted in the
|
|
35
|
+
[isthmus](https://github.com/ictechgy/isthmus) bridge-facts exchange format.
|
|
36
|
+
|
|
37
|
+
pythograph is the Python member of a family of static-analysis CLIs (tsograph for
|
|
38
|
+
TypeScript/JavaScript, cartograph for Swift, kartograph for Kotlin, dartograph for Dart, gartograph for
|
|
39
|
+
Go, rustograph for Rust, schemagraph for SQL). Each tool reports only what it observes in its own language;
|
|
40
|
+
isthmus joins the documents.
|
|
41
|
+
|
|
42
|
+
The analyzed project is parsed with the standard-library `ast` only. It is never imported or executed,
|
|
43
|
+
and pythograph has no runtime dependencies and uses no network (`graph`, `reach`, and `impact` run only the project
|
|
44
|
+
root's git to read `revision`).
|
|
45
|
+
|
|
46
|
+
## Status
|
|
47
|
+
|
|
48
|
+
| Area | State |
|
|
49
|
+
|---|---|
|
|
50
|
+
| `pythograph routes --role server`: Django URLconf, Django REST framework routers and views, Flask/Werkzeug rules → `route-decl` facts | Implemented |
|
|
51
|
+
| `pythograph schema`: Django models and QuerySets, SQLAlchemy 2.x / Flask-SQLAlchemy 3 mappings and queries, SQL text → persistence `relation-use` facts | Implemented |
|
|
52
|
+
| `pythograph graph` / `reach` / `impact`: Python call graph → isthmus `language-traversal` v1 (evidence tiers `direct`/`candidate`, `unresolvedCalls`, Django/DRF/Flask dispatch) | Implemented |
|
|
53
|
+
| Client route-calls (requests, httpx) | Planned |
|
|
54
|
+
|
|
55
|
+
isthmus `main` (`f9dcd1d`) accepts `platform: "python"` http documents (including `registration-order`), persistence
|
|
56
|
+
documents, and python `language-traversal` analyses (see [isthmus compatibility](#isthmus-compatibility)).
|
|
57
|
+
|
|
58
|
+
## Requirements and installation
|
|
59
|
+
|
|
60
|
+
- Python 3.10 or newer (Django 5.x needs 3.10+, and pythograph parses the analyzed code with the running
|
|
61
|
+
interpreter, so run it with the same or a newer Python than the project).
|
|
62
|
+
|
|
63
|
+
pythograph is not on PyPI yet. Until the first release, install it from GitHub (the `main` branch, or a release tag
|
|
64
|
+
such as `@v0.1.0` once it exists):
|
|
65
|
+
|
|
66
|
+
```sh
|
|
67
|
+
uv tool install git+https://github.com/ictechgy/pythograph
|
|
68
|
+
# or
|
|
69
|
+
pipx install git+https://github.com/ictechgy/pythograph
|
|
70
|
+
|
|
71
|
+
pythograph --version
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
A wheel built from a checkout (`uv build`) installs the same way: `uv tool install dist/pythograph-<version>-py3-none-any.whl`
|
|
75
|
+
or `pipx install dist/pythograph-<version>-py3-none-any.whl`. After the PyPI release, `uv tool install pythograph` and
|
|
76
|
+
`pipx install pythograph` will work. The release steps are in [RELEASING.md](https://github.com/ictechgy/pythograph/blob/main/RELEASING.md)
|
|
77
|
+
(Korean).
|
|
78
|
+
|
|
79
|
+
## `pythograph routes --role server`
|
|
80
|
+
|
|
81
|
+
```sh
|
|
82
|
+
pythograph routes --role server --project <root> [--service <name>] [--include-tests]
|
|
83
|
+
[--framework auto|django|flask] [--settings <module>]
|
|
84
|
+
[--dispatch specificity] [--generated-at <timestamp>] [--format json]
|
|
85
|
+
```
|
|
86
|
+
|
|
87
|
+
Writes a bridge-facts v1 document to stdout: `platform: "python"`, `target: "http"`, `roles: ["server"]`,
|
|
88
|
+
`dispatch`, `sourceSets`, and one `route-decl` fact per (route, HTTP method).
|
|
89
|
+
|
|
90
|
+
- `--project` (required): the project root. `project` is its POSIX realpath and every `location.path` is
|
|
91
|
+
relative to it.
|
|
92
|
+
- `--framework`: `auto` (default) detects Django (a `DJANGO_SETTINGS_MODULE` default in `manage.py`,
|
|
93
|
+
`wsgi.py`, or `asgi.py`, or `--settings`) and Flask (a `Flask(...)` or `Blueprint(...)` object). A project
|
|
94
|
+
with both is a usage error until you pick one.
|
|
95
|
+
- `--settings`: the Django settings module when the entry files do not name it.
|
|
96
|
+
- `--include-tests`: also emit routes declared in test sources (`test_*.py`, `*_test.py`, `tests.py`,
|
|
97
|
+
`conftest.py`, files under `tests/` or `test/`) with `testSource: true` and `sourceSets.tests: "included"`.
|
|
98
|
+
- `--dispatch specificity`: declare `specificity` for a Django project and omit `order` (see
|
|
99
|
+
[Decisions](#decisions)).
|
|
100
|
+
- `--generated-at`: a fixed `generatedAt` for byte-identical output.
|
|
101
|
+
- Exit codes: `0` success (zero facts is still success, not proof of completeness), `2` unreadable project,
|
|
102
|
+
more than 100,000 facts, output over 16 Mi characters, or an internal error (the message states the cause
|
|
103
|
+
and a fix, never source text or absolute paths), `64` usage error. `1` is reserved.
|
|
104
|
+
|
|
105
|
+
Example (synthetic, compacted; the real output is key-sorted JSON with two-space indentation):
|
|
106
|
+
|
|
107
|
+
```json
|
|
108
|
+
{
|
|
109
|
+
"dispatch": "registration-order",
|
|
110
|
+
"facts": [
|
|
111
|
+
{
|
|
112
|
+
"channel": "/catalog/items/{}/edit/",
|
|
113
|
+
"dynamic": false,
|
|
114
|
+
"kind": "route-decl",
|
|
115
|
+
"location": { "column": 10, "line": 20, "path": "catalog/urls.py" },
|
|
116
|
+
"method": "POST",
|
|
117
|
+
"order": { "group": "django:shop.urls", "index": 7 },
|
|
118
|
+
"paramConstraints": [{ "kind": "int", "segment": 2 }],
|
|
119
|
+
"pathAnchor": "root",
|
|
120
|
+
"symbol": { "qualifiedName": "catalog/views.py#ItemEditView.post", "usr": "catalog/views.py#ItemEditView.post" },
|
|
121
|
+
"trailingSlash": "strict"
|
|
122
|
+
}
|
|
123
|
+
],
|
|
124
|
+
"format": "bridge-facts",
|
|
125
|
+
"platform": "python",
|
|
126
|
+
"roles": ["server"],
|
|
127
|
+
"target": "http",
|
|
128
|
+
"tool": { "name": "pythograph", "version": "0.1.0" },
|
|
129
|
+
"version": 1
|
|
130
|
+
}
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
### What is modeled
|
|
134
|
+
|
|
135
|
+
Every rule was checked against the installed package sources (Django 5.2.17, djangorestframework 3.18.1,
|
|
136
|
+
Flask 3.1.3, Werkzeug 3.1.9). The full table with source files is in [docs/HTTP-ROUTES.md](https://github.com/ictechgy/pythograph/blob/main/docs/HTTP-ROUTES.md)
|
|
137
|
+
(Korean).
|
|
138
|
+
|
|
139
|
+
- **Django**: `ROOT_URLCONF` from the settings module (star imports of project settings modules are
|
|
140
|
+
followed), `urlpatterns` built with lists, `+`, `+=`, `.append`, `.extend`, `.insert`, `path()`, `re_path()`,
|
|
141
|
+
`include()` (module strings, module objects, lists, `(patterns, app_name)` tuples, nested), default and
|
|
142
|
+
registered path converters, `re_path` regular expressions converted from their syntax tree when possible,
|
|
143
|
+
function views (`ANY`, narrowed by `require_http_methods`/`require_GET`/`require_POST`/`require_safe`/
|
|
144
|
+
`api_view`), class views (`as_view()`, handlers along the class chain, `http_method_names`), and the
|
|
145
|
+
registration order (first match wins).
|
|
146
|
+
- **Django REST framework**: `SimpleRouter`/`DefaultRouter` (`trailing_slash`, `use_regex_path`, lookup
|
|
147
|
+
settings, `@action` with `detail`, `methods`, `url_path`, and `.mapping`), the `DefaultRouter` API root and
|
|
148
|
+
format-suffix variants, `format_suffix_patterns`, `APIView`, generic views, and viewsets.
|
|
149
|
+
- **Flask**: `Flask(...)` and `Blueprint(...)` objects at module level or in app factories, `@route`,
|
|
150
|
+
`@get`/`@post`/`@put`/`@delete`/`@patch`, `add_url_rule`, `register_blueprint` (nested, `url_prefix`),
|
|
151
|
+
`MethodView`/`View`, Werkzeug converters (`string`, `int`, `float`, `uuid`, `path`, `any`, custom),
|
|
152
|
+
`strict_slashes`, and `merge_slashes`.
|
|
153
|
+
|
|
154
|
+
### How facts are built
|
|
155
|
+
|
|
156
|
+
- **channel**: the canonical path template. A parameter filling a whole segment is `{}`, a partial segment
|
|
157
|
+
keeps its literal skeleton (`/files/{}.json`), a final parameter that can match `/` is `{**}`, and anything
|
|
158
|
+
else that cannot be proven (a segment with two parameters, a middle catch-all, lookarounds, unanchored
|
|
159
|
+
regular expressions) is `dynamic` with a `route-coverage:` limitation. Literals are in the decoded path
|
|
160
|
+
space, so non-pchar characters are UTF-8 percent-encoded and `%` becomes `%25`.
|
|
161
|
+
- **method**: uppercase verbs or `ANY`. `HEAD` next to `GET` and automatic `OPTIONS` are not emitted (isthmus
|
|
162
|
+
matches them with `head-as-get` and `options-any`); an explicitly declared `OPTIONS` is.
|
|
163
|
+
- **paramConstraints**: `int`, `slug`, `uuid`, `path` (for `{**}`), or `regex` with the pattern.
|
|
164
|
+
- **trailingSlash**: Django and strict Flask rules are `strict`; `/?` in a regex and Flask
|
|
165
|
+
`strict_slashes=False` are `optional`; omitted after `{**}`.
|
|
166
|
+
- **order** (Django): `{group: "django:<ROOT_URLCONF>", index: <depth-first position>}`.
|
|
167
|
+
- **location**: the route string argument (Django `path()`/`re_path()`, Flask decorator or `add_url_rule`), the
|
|
168
|
+
`router.register()` prefix for DRF routes, or the `@action` decorator for extra actions; 1-based line and
|
|
169
|
+
1-based UTF-8 byte column.
|
|
170
|
+
|
|
171
|
+
### Symbol ids
|
|
172
|
+
|
|
173
|
+
`symbol.usr` is `<project-relative POSIX path>#<lexical dotted name>`, outermost declaration first and
|
|
174
|
+
without `<locals>`: `catalog/views.py#item_list`, `blog/__init__.py#create_app.index`,
|
|
175
|
+
`catalog/views.py#ItemEditView.get`, `orders/views.py#OrderViewSet.list`. Class handlers are named after the
|
|
176
|
+
class registered in the URL even when the method is inherited; the call graph (`pythograph graph`) has an
|
|
177
|
+
inherited-member node with the same id. Views defined outside the project have no usr and are counted under
|
|
178
|
+
`missing-route-usrs:`.
|
|
179
|
+
|
|
180
|
+
### Limitations
|
|
181
|
+
|
|
182
|
+
When a value cannot be proven, pythograph does not guess: it emits `dynamic`, `pathAnchor: "base"`, or a
|
|
183
|
+
limitation with one of the contract's prefixes, and adds `limitationScopes` only when it can prove an upper
|
|
184
|
+
bound. Examples: conditional registrations (`if settings.DEBUG:`) become `route-coverage:` scoped to their
|
|
185
|
+
templates; unresolved includes and third-party URL modules are scoped to their include prefix; the Django admin,
|
|
186
|
+
`static()`, `django.contrib.staticfiles`, and Flask static files are `framework-provided-routes:` with prefix
|
|
187
|
+
scopes (Flask static also with `GET`/`HEAD`); `FORCE_SCRIPT_NAME`, `i18n_patterns`, and blueprints whose
|
|
188
|
+
registration is not visible use a `base` anchor with `unresolved-route-prefix:`; a project that does not pin
|
|
189
|
+
Django 5, DRF 3, or Flask 3 gets `route-framework-version-unknown:`.
|
|
190
|
+
|
|
191
|
+
### Decisions
|
|
192
|
+
|
|
193
|
+
- **Django is `registration-order`, Flask is `specificity`**, as verified from the sources. isthmus releases
|
|
194
|
+
before `f9dcd1d` reject `registration-order`; `--dispatch specificity` declares specificity for Django and omits
|
|
195
|
+
`order`. That approximation can only produce false matches (a shadowed pattern matched), never false errors,
|
|
196
|
+
because isthmus filters by method first and reports a method mismatch only when no candidate accepts the
|
|
197
|
+
method, which is also when Django answers 405.
|
|
198
|
+
- **Shadowed patterns are still declarations**; shadowing is the consumer's judgement from `order`.
|
|
199
|
+
- **Conditional registrations are scoped limitations**, not declarations.
|
|
200
|
+
|
|
201
|
+
## `pythograph schema`
|
|
202
|
+
|
|
203
|
+
```sh
|
|
204
|
+
pythograph schema --project <root> [--include-tests] [--settings <module>] [--generated-at <timestamp>] [--format json]
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
Writes a bridge-facts v1 document with `platform: "python"`, `target: "persistence"` (or `null` when there are no
|
|
208
|
+
facts), and one `relation-use` fact per observed relation or column reference. isthmus joins it with a
|
|
209
|
+
`platform: "sql"` document (schemagraph `facts --document <catalog>`) under the persistence rules of
|
|
210
|
+
`docs/GRAPH-EXCHANGE.md`. The full rule table with source files is in [docs/PERSISTENCE.md](https://github.com/ictechgy/pythograph/blob/main/docs/PERSISTENCE.md)
|
|
211
|
+
(Korean).
|
|
212
|
+
|
|
213
|
+
- **Django**: model classes (abstract, proxy, multi-table inheritance, `Meta` inheritance) → tables
|
|
214
|
+
(`<app_label>_<model>` truncated by `truncate_name` for the backend's `max_name_length`, or `Meta.db_table`), fields →
|
|
215
|
+
columns (`db_column`, `<name>_id` for foreign keys, many-to-many tables and their columns), app labels from
|
|
216
|
+
`INSTALLED_APPS`/`AppConfig`, the `DATABASES` backend, and django.contrib models. Uses: managers and QuerySet chains,
|
|
217
|
+
lookups (`author__profile__city`, reverse relations, `attname`, `pk`), `values`/`order_by`/`F`/`Q`/aggregates,
|
|
218
|
+
`create`/`update` keywords, related managers and forward relations on proven instances, `raw()`, `RawSQL`,
|
|
219
|
+
`extra(tables=)`, and cursor SQL.
|
|
220
|
+
- **SQLAlchemy 2.x / Flask-SQLAlchemy 3**: Declarative classes (`DeclarativeBase`, `declarative_base()`, `db.Model`
|
|
221
|
+
with its snake_case names), mixins, single- and joined-table inheritance, `__table_args__`/`MetaData` schemas, Core
|
|
222
|
+
`Table`, `ForeignKey("t.c")`, `relationship(secondary=)`. Uses: statement entities (`select`, `insert`, `update`,
|
|
223
|
+
`delete`, `session.query`, `session.get`, `join`), `Model.column`, `Model.relationship`, `Model.query`, `filter_by`,
|
|
224
|
+
constructors, `table.c.name`, and `text()`.
|
|
225
|
+
- **SQL text**: the family's shared lexical extractor (the same vectors as tsograph, dartograph, cartograph, and
|
|
226
|
+
kartograph) for explicit SQL arguments, and uppercase SQL literals elsewhere (docstrings are skipped).
|
|
227
|
+
- **channel** is the relation name as written or mapped (`schema.table` only when qualified; no default schema is
|
|
228
|
+
guessed), **method** is the column, and **symbol.usr** is the enclosing function, method, or model class with the
|
|
229
|
+
same ids as `routes`. A name that depends on an unknown backend, app label, or Flask-SQLAlchemy version, an
|
|
230
|
+
unresolved model or lookup, and SQL built at runtime become `dynamic` facts with a `dynamic-relation-names:`
|
|
231
|
+
limitation instead of guesses.
|
|
232
|
+
- Test sources (unless `--include-tests`) and migrations (Django `migrations/`, Alembic `versions/`) are not scanned;
|
|
233
|
+
migrations describe past schemas.
|
|
234
|
+
|
|
235
|
+
## `pythograph graph` / `reach` / `impact`
|
|
236
|
+
|
|
237
|
+
```sh
|
|
238
|
+
pythograph graph --project <root> [--include-tests] [--revision <id>] [--generated-at <timestamp>]
|
|
239
|
+
pythograph reach --project <root> [--dispatch direct|bound|candidates] [--max-depth <n>] [--max-reached <n>]
|
|
240
|
+
[--roots-from <file|->] [--include-tests] [--revision <id>] [--generated-at <timestamp>] [--] <id>...
|
|
241
|
+
pythograph impact (same options as reach)
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
Builds the project's Python call graph with the standard-library `ast`. `graph` writes pythograph's own snapshot
|
|
245
|
+
(`pythograph-graph` v1); `reach` writes the symbols the roots depend on (`dependencies`) and `impact` the symbols that
|
|
246
|
+
depend on them (`dependents`) as isthmus
|
|
247
|
+
[`language-traversal` v1](https://github.com/ictechgy/isthmus/blob/main/docs/LANGUAGE-TRAVERSAL.md). Ids are the same
|
|
248
|
+
strings as `symbol.usr` in `routes` and `schema`. The full rules are in [docs/GRAPH.md](https://github.com/ictechgy/pythograph/blob/main/docs/GRAPH.md) (Korean).
|
|
249
|
+
|
|
250
|
+
- **Nodes**: modules (`<path>#<module>`), functions, methods, classes, nested definitions, and inherited members
|
|
251
|
+
(`<registered class>.<member>`, for view handlers and for inherited members called on exact receivers).
|
|
252
|
+
- **Edges**: `call`, `new` (the project `__init__`, else the class), `callback`, `reference`, `decorator`, `attribute`
|
|
253
|
+
(class-body attributes), `inherit`, and `dispatch`/`framework` (framework dispatch). Resolution follows imports
|
|
254
|
+
(absolute, relative, aliases, `__init__` re-exports, `*`), module attributes, constructors, `self` and `super()`
|
|
255
|
+
through the C3 MRO of project classes, annotated and return-annotated receivers, module-level instances, and
|
|
256
|
+
properties.
|
|
257
|
+
- **Evidence tiers**: statically resolved edges are `direct`; overrides in project subclasses for `self`, `cls`, and
|
|
258
|
+
annotated receivers are `candidate`. `bound` edges are not produced yet, so `--dispatch bound` follows the `direct`
|
|
259
|
+
graph.
|
|
260
|
+
- **No guessing**: calls without a known target are counted by reason (`parameter`, `untyped-receiver`,
|
|
261
|
+
`dynamic-attribute`, `getattr`, `dynamic-callee`, `unresolved-import`, `framework-callback`, …) as each symbol's
|
|
262
|
+
`unresolvedCalls`. A method call on an untyped receiver is proven external only when no project class, module, or
|
|
263
|
+
attribute write defines that name.
|
|
264
|
+
- **Framework dispatch**: a table read with `ast` from the installed Django 5.2.17, DRF 3.18.1, and Flask 3.1.3
|
|
265
|
+
sources links the project hooks that the `as_view()` dispatch path (`dispatch`, `initial`, permission checks,
|
|
266
|
+
`__init__`) and framework implementations (`ModelViewSet.retrieve` → `get_object` → `get_queryset`,
|
|
267
|
+
`ModelSerializer.save` → `create`) call. Objects the framework builds from class attributes (`serializer_class`,
|
|
268
|
+
`permission_classes`) count as `framework-callback` unresolved calls.
|
|
269
|
+
- **Traversal documents**: a `dispatch` declaration, per-root lower-bound `evidence`, `unresolvedCalls`, one
|
|
270
|
+
multi-root pass (compared with a per-root oracle on random graphs), `--max-depth`/`--max-reached` truncation, and
|
|
271
|
+
`rootsTruncated`. Roots that are not graph nodes are listed without `symbol`; the document is written and the
|
|
272
|
+
command exits `64`. `revision` is `--revision` or git `HEAD` when the work tree is clean; `graphRevision` is a
|
|
273
|
+
SHA-256 of the graph content.
|
|
274
|
+
|
|
275
|
+
## Validation
|
|
276
|
+
|
|
277
|
+
The oracle harness in `experiments/oracle/` imports the synthetic fixtures in a scratch virtual environment
|
|
278
|
+
and compares pythograph's facts with Django's resolver traversal, DRF routers, and Flask's `url_map`
|
|
279
|
+
(`tests/test_fixtures.py` replays the recorded results offline):
|
|
280
|
+
|
|
281
|
+
| Target | Precision | Recall |
|
|
282
|
+
|---|---|---|
|
|
283
|
+
| `fixtures/django/drf-shop` | 69/69 | 58/59 (one intentional dynamic lookahead pattern) |
|
|
284
|
+
| `fixtures/flask/blog-app` | 28/28 | 27/27 |
|
|
285
|
+
| HackSoftware/Django-Styleguide-Example `a70ef43` (MIT, scratch clone) | 21/21 | 21/22 (the DEBUG-only `static()` route) |
|
|
286
|
+
|
|
287
|
+
Dogfooding on four public apps (Django-Styleguide-Example, babybuddy, microblog, and netbox, cloned only into a scratch
|
|
288
|
+
directory) measured route precision against each framework's resolver, relation-use join rates through schemagraph and
|
|
289
|
+
isthmus, handler-to-relation reachability, unresolved-call reasons, and runtime; the results and the fixed issues are in
|
|
290
|
+
[DOGFOOD.md](https://github.com/ictechgy/pythograph/blob/main/DOGFOOD.md) (Korean).
|
|
291
|
+
|
|
292
|
+
The isthmus shared conformance vectors (`conformance/`, vendored from isthmus `f9dcd1d` and locked in
|
|
293
|
+
`conformance.lock`) pass 100% of the applicable producer cases (78: `template.grammar`, `template.normalize`,
|
|
294
|
+
`scope.validate`, `scope.applies`, `dispatch.validate`); the `dispatch.validate` checker also runs on the routes
|
|
295
|
+
golden output.
|
|
296
|
+
|
|
297
|
+
**Phase 6 exit criterion (Django backend × iOS/Android chain).** `experiments/e2e/` joins a synthetic Django + DRF
|
|
298
|
+
server (`fixtures/e2e/shop-api`), a schemagraph catalog of its Django DDL, and route-calls plus reverse traversals of
|
|
299
|
+
synthetic iOS (cartograph) and Android (kartograph) clients with isthmus `trace` (workspace). The expected paths of the
|
|
300
|
+
three questions match: (a) API → DB tables + DB dependents, (b) API → client call sites → affected client symbols, and
|
|
301
|
+
(c) table → API → client. The Android recording needs kartograph `4c09d91` or later (Retrofit route-call usrs and
|
|
302
|
+
`baseUrl` joins), which attaches the Android order and checkout calls to the chain. `tests/test_e2e_trace.py` re-checks
|
|
303
|
+
the recorded inputs and outputs offline (the table is in
|
|
304
|
+
[docs/GRAPH.md](https://github.com/ictechgy/pythograph/blob/main/docs/GRAPH.md#phase-6-종료-조건-django-백엔드--iosandroid-체인)).
|
|
305
|
+
|
|
306
|
+
Persistence naming vectors (`fixtures/persistence-naming/vectors.json`) are recorded by importing synthetic models
|
|
307
|
+
with the real ORMs in a scratch environment (`experiments/persistence/run_naming.py`): Django 5.2.17 `_meta` names
|
|
308
|
+
quoted by each backend's `connection.ops` (sqlite3, postgresql, mysql, oracle), and SQLAlchemy 2.0.54 /
|
|
309
|
+
Flask-SQLAlchemy 3.1.1 mappers. pythograph matches 100% (Django 136/136 model-backend pairs, SQLAlchemy 9/9 and
|
|
310
|
+
Flask-SQLAlchemy 10/10 classes, all tables and columns). Joining `pythograph schema` output for the two persistence
|
|
311
|
+
fixtures with schemagraph catalogs of the DDL the ORMs create (`experiments/persistence/run_e2e.py`) gives no isthmus
|
|
312
|
+
errors (41 and 20 matches).
|
|
313
|
+
|
|
314
|
+
## isthmus compatibility
|
|
315
|
+
|
|
316
|
+
isthmus `main` (`f9dcd1d`, #128) accepts `platform: "python"`: http `route-decl` facts (Django's `registration-order`
|
|
317
|
+
and `order`, with shadowing diagnostics), persistence `relation-use` facts, and python `forward`/`reverse` analyses
|
|
318
|
+
(`language-traversal` v1) in `trace`. `--dispatch specificity` remains for older isthmus releases.
|
|
319
|
+
|
|
320
|
+
## Development
|
|
321
|
+
|
|
322
|
+
```sh
|
|
323
|
+
uv sync
|
|
324
|
+
uv run ruff check src tests && uv run ruff format --check src tests
|
|
325
|
+
uv run mypy
|
|
326
|
+
uv run pytest --cov # line and branch coverage gate: 90%
|
|
327
|
+
uv run python scripts/verify_cli_contract.py
|
|
328
|
+
```
|
|
329
|
+
|
|
330
|
+
The framework hook table is regenerated from a scratch virtual environment with Django 5.2.17, DRF 3.18.1, and
|
|
331
|
+
Flask 3.1.3 installed: `python experiments/graph/dump_framework_hooks.py --site-packages <path>` (`--check` compares
|
|
332
|
+
only).
|
|
333
|
+
|
|
334
|
+
## License
|
|
335
|
+
|
|
336
|
+
MIT
|