| import sys |
| from notion_client import Client |
| from tasks.utils import notion_utils |
|
|
|
|
| TARGET_PAGE_TITLE = "Python Roadmap" |
| CHAPTER_NAME = "Expert Level" |
| CHAPTER_ICON = "🟣" |
| BRIDGE_TITLE = "Advanced Foundations Review" |
| REQUIRED_SUBITEM_TITLES = ["Decorators", "Calling API"] |
|
|
| LESSON_REQUIREMENTS = [ |
| { |
| "title": "Metaprogramming and AST Manipulation", |
| "status": "To Do", |
| "date": "2025-09-15", |
| "parent_title": BRIDGE_TITLE, |
| }, |
| { |
| "title": "Async Concurrency Patterns", |
| "status": "To Do", |
| "date": "2025-09-20", |
| "parent_title": "Calling API", |
| }, |
| ] |
|
|
|
|
| def _get_database_ids(notion: Client, page_id: str) -> tuple[str | None, str | None]: |
| """Return the block IDs for the Chapters and Steps databases on the page.""" |
| chapters_db_id = None |
| steps_db_id = None |
|
|
| blocks = notion_utils.get_all_blocks_recursively(notion, page_id) |
| for block in blocks: |
| if block.get("type") != "child_database": |
| continue |
| title = block.get("child_database", {}).get("title", "") |
| if "Chapters" in title and not chapters_db_id: |
| chapters_db_id = block["id"] |
| elif "Steps" in title and not steps_db_id: |
| steps_db_id = block["id"] |
|
|
| return chapters_db_id, steps_db_id |
|
|
|
|
| def _query_step_by_title(notion: Client, database_id: str, title: str, *, exact: bool = True): |
| """Return the first step entry matching the given title pattern.""" |
| title_filter = {"equals": title} if exact else {"contains": title} |
| response = notion.databases.query( |
| database_id=database_id, |
| filter={"property": "Lessons", "title": title_filter}, |
| page_size=5, |
| ) |
| results = response.get("results", []) |
| return results[0] if results else None |
|
|
|
|
| def verify(notion: Client, main_id: str | None = None) -> bool: |
| """Verify the simplified Expert Level learning path setup.""" |
| |
| if main_id: |
| page_id, object_type = notion_utils.find_page_or_database_by_id(notion, main_id) |
| if not page_id or object_type != "page": |
| print("Error: Python Roadmap page not found.", file=sys.stderr) |
| return False |
| else: |
| page_id = notion_utils.find_page(notion, TARGET_PAGE_TITLE) |
| if not page_id: |
| print("Error: Python Roadmap page not found.", file=sys.stderr) |
| return False |
|
|
| |
| chapters_db_id, steps_db_id = _get_database_ids(notion, page_id) |
| if not chapters_db_id: |
| print("Error: Chapters database not found on the page.", file=sys.stderr) |
| return False |
| if not steps_db_id: |
| print("Error: Steps database not found on the page.", file=sys.stderr) |
| return False |
|
|
| |
| try: |
| chapter_resp = notion.databases.query( |
| database_id=chapters_db_id, |
| filter={"property": "Name", "title": {"equals": CHAPTER_NAME}}, |
| page_size=1, |
| ) |
| except Exception as exc: |
| print(f"Error querying Chapters database: {exc}", file=sys.stderr) |
| return False |
|
|
| results = chapter_resp.get("results", []) |
| if not results: |
| print("Error: Expert Level chapter not found.", file=sys.stderr) |
| return False |
|
|
| expert_chapter = results[0] |
| expert_chapter_id = expert_chapter["id"] |
| icon = expert_chapter.get("icon") or {} |
| if icon.get("type") != "emoji" or icon.get("emoji") != CHAPTER_ICON: |
| print("Error: Expert Level chapter must use the purple circle emoji icon.", file=sys.stderr) |
| return False |
|
|
| print("✓ Expert Level chapter exists with the correct icon.") |
|
|
| |
| control_lesson = _query_step_by_title(notion, steps_db_id, "Control", exact=False) |
| if not control_lesson: |
| print("Error: Could not find a lesson containing 'Control' in its title.", file=sys.stderr) |
| return False |
| control_lesson_id = control_lesson["id"] |
|
|
| prerequisite_ids = {} |
| for title in REQUIRED_SUBITEM_TITLES: |
| lesson = _query_step_by_title(notion, steps_db_id, title, exact=False) |
| if not lesson: |
| print(f"Error: Required lesson containing '{title}' not found.", file=sys.stderr) |
| return False |
| prerequisite_ids[title] = lesson["id"] |
|
|
| |
| bridge_lesson = _query_step_by_title(notion, steps_db_id, BRIDGE_TITLE, exact=True) |
| if not bridge_lesson: |
| print("Error: Advanced Foundations Review lesson not found.", file=sys.stderr) |
| return False |
|
|
| status = (bridge_lesson["properties"].get("Status", {}).get("status") or {}).get("name") |
| if status != "Done": |
| print("Error: Advanced Foundations Review must have status 'Done'.", file=sys.stderr) |
| return False |
|
|
| |
| chapter_rel = bridge_lesson["properties"].get("Chapters", {}).get("relation", []) |
| if not any(rel["id"] == expert_chapter_id for rel in chapter_rel): |
| print("Error: Advanced Foundations Review must link to the Expert Level chapter.", file=sys.stderr) |
| return False |
|
|
| |
| parent_rel = bridge_lesson["properties"].get("Parent item", {}).get("relation", []) |
| if not parent_rel or parent_rel[0]["id"] != control_lesson_id: |
| print("Error: Advanced Foundations Review should use the control lesson as its Parent item.", file=sys.stderr) |
| return False |
|
|
| |
| sub_rel = bridge_lesson["properties"].get("Sub-item", {}).get("relation", []) |
| sub_ids = {rel["id"] for rel in sub_rel} |
| missing = [title for title, rel_id in prerequisite_ids.items() if rel_id not in sub_ids] |
| if missing: |
| print( |
| f"Error: Advanced Foundations Review must include these lessons as sub-items: {', '.join(missing)}.", |
| file=sys.stderr, |
| ) |
| return False |
|
|
| print("✓ Bridge lesson configured with the correct status, chapter, parent, and sub-items.") |
|
|
| |
| overall_success = True |
| for spec in LESSON_REQUIREMENTS: |
| lesson = _query_step_by_title(notion, steps_db_id, spec["title"], exact=True) |
| if not lesson: |
| print(f"Error: Lesson '{spec['title']}' not found.", file=sys.stderr) |
| overall_success = False |
| continue |
|
|
| lesson_ok = True |
|
|
| |
| status_name = (lesson["properties"].get("Status", {}).get("status") or {}).get("name") |
| if status_name != spec["status"]: |
| print( |
| f"Error: Lesson '{spec['title']}' should have status '{spec['status']}', found '{status_name}'.", |
| file=sys.stderr, |
| ) |
| lesson_ok = False |
|
|
| |
| lesson_chapters = lesson["properties"].get("Chapters", {}).get("relation", []) |
| if not any(rel["id"] == expert_chapter_id for rel in lesson_chapters): |
| print(f"Error: Lesson '{spec['title']}' must link to the Expert Level chapter.", file=sys.stderr) |
| lesson_ok = False |
|
|
| |
| parent_title = spec["parent_title"] |
| if parent_title == BRIDGE_TITLE: |
| expected_parent_id = bridge_lesson["id"] |
| else: |
| expected_parent_id = prerequisite_ids.get(parent_title) |
|
|
| parent_relation = lesson["properties"].get("Parent item", {}).get("relation", []) |
| if not expected_parent_id: |
| print( |
| f"Error: Could not resolve expected parent '{parent_title}' for lesson '{spec['title']}'.", |
| file=sys.stderr, |
| ) |
| lesson_ok = False |
| else: |
| if not parent_relation or parent_relation[0]["id"] != expected_parent_id: |
| print( |
| f"Error: Lesson '{spec['title']}' should have '{parent_title}' as its Parent item.", |
| file=sys.stderr, |
| ) |
| lesson_ok = False |
| |
| date_prop = lesson["properties"].get("Date", {}).get("date") or {} |
| if date_prop.get("start") != spec["date"]: |
| print( |
| f"Error: Lesson '{spec['title']}' should use date {spec['date']}, found {date_prop.get('start')}.", |
| file=sys.stderr, |
| ) |
| lesson_ok = False |
|
|
| if lesson_ok: |
| print(f"✓ Lesson '{spec['title']}' has the expected properties.") |
| else: |
| overall_success = False |
|
|
| if not overall_success: |
| return False |
|
|
| print("Success: Expert Level chapter, bridge lesson, and expert lessons configured correctly.") |
| return True |
|
|
|
|
| def main() -> None: |
| notion = notion_utils.get_notion_client() |
| main_id = sys.argv[1] if len(sys.argv) > 1 else None |
| if verify(notion, main_id): |
| sys.exit(0) |
| sys.exit(1) |
|
|
|
|
| if __name__ == "__main__": |
| main() |
|
|