Ludek Matyska commited on
Commit
044a142
Β·
1 Parent(s): 0a9b724

feat: add error messages

Browse files
Files changed (1) hide show
  1. theory_tools.py +112 -75
theory_tools.py CHANGED
@@ -2,48 +2,76 @@ import re
2
  import requests
3
  import nbformat
4
 
5
- RAW_BASE = "https://raw.githubusercontent.com/Qiskit/textbook/main/notebooks/intro/"
6
- README_URL = RAW_BASE + "README.md"
7
- FALLBACK = [
8
- "what-is-quantum.ipynb",
9
- "entangled-states.ipynb",
10
- "superdense-coding.ipynb",
11
- "teleportation.ipynb",
12
- ]
13
-
14
-
15
- # ────────────────────────────────────────────────────────────────────
16
- # Internal helpers
17
- # ────────────────────────────────────────────────────────────────────
18
- def _discover_notebooks() -> list[str]:
19
- """Scrape notebooks/intro/README.md for *.ipynb links; fallback if offline."""
20
  try:
21
- md = requests.get(README_URL, timeout=10).text
22
- found = re.findall(r"\(([^)]+?\.ipynb)\)", md)
23
- if found:
24
- return found
25
  except requests.RequestException:
26
- pass
27
- return FALLBACK
28
 
29
 
30
- def _pretty(name: str) -> str:
31
- """'superdense-coding.ipynb' ➜ 'Superdense Coding'."""
32
- return name.replace("-", " ").replace(".ipynb", "").title()
 
 
 
 
 
 
 
 
33
 
34
 
35
- # ────────────────────────────────────────────────────────────────────
36
- # Public API
37
- # ────────────────────────────────────────────────────────────────────
38
- def get_theory_topics() -> dict[str, str]:
39
- """
40
- Return a mapping of *friendly topic name* β†’ *notebook filename*.
41
 
42
- Example
43
- -------
44
- {'What Is Quantum?': 'what-is-quantum.ipynb', ...}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
45
  """
46
- return {_pretty(f): f for f in _discover_notebooks()}
 
 
 
 
 
 
47
 
48
 
49
  def get_theory(
@@ -51,59 +79,68 @@ def get_theory(
51
  markdown_only: bool = True,
52
  include_headers: bool = True,
53
  ) -> str:
54
- """
55
- Download **one** intro notebook and return its content as text.
56
-
57
- Parameters
58
- ----------
59
- topic
60
- Accepts pretty title (β€œTeleportation”), slug (β€œteleportation”)
61
- or exact filename (β€œteleportation.ipynb”).
62
- markdown_only
63
- True (default) ➜ keep only Markdown cells;
64
- False ➜ include code cells fenced as ```python.
65
- include_headers
66
- Prepend an H1 title for readability.
67
-
68
- Raises
69
- ------
70
- ValueError
71
- If *topic* cannot be resolved.
72
-
73
- Returns
74
- -------
75
- str
76
- Concatenated notebook text.
 
 
 
 
 
77
  """
78
  topics = get_theory_topics()
79
 
80
- # Build a lenient lookup table
81
  lookup: dict[str, str] = {}
82
- for pretty, fname in topics.items():
83
- slug = fname.removesuffix(".ipynb")
84
- lookup[pretty.lower()] = fname
85
- lookup[slug.lower()] = fname
86
- lookup[fname.lower()] = fname
87
 
88
  key = topic.lower()
89
  if key not in lookup:
90
- raise ValueError(
91
- f"Unknown topic '{topic}'. "
92
- f"Known: {', '.join(topics.keys())}."
93
- )
94
 
95
- fname = lookup[key]
96
- raw_json = requests.get(RAW_BASE + fname, timeout=20).text
97
- nb = nbformat.reads(raw_json, as_version=4)
 
 
 
 
98
 
99
- parts: list[str] = []
100
  if include_headers:
101
- parts.append(f"# {_pretty(fname)}\n")
102
 
103
  for cell in nb.cells:
104
  if cell.cell_type == "markdown":
105
- parts.append(cell.source)
106
  elif cell.cell_type == "code" and not markdown_only:
107
- parts.append(f"```python\n{cell.source}\n```")
108
 
109
- return "\n\n".join(parts)
 
2
  import requests
3
  import nbformat
4
 
5
+
6
+ RAW_ROOT = "https://raw.githubusercontent.com/Qiskit/textbook/main/notebooks/"
7
+ # README locations we now support
8
+ _SECTIONS: dict[str, str] = {
9
+ "intro": "intro/README.md",
10
+ "ch-states": "ch-states/README.md",
11
+ "ch-gates": "ch-gates/README.md",
12
+ "ch-algorithms":"ch-algorithms/README.md",
13
+ }
14
+
15
+ # ───────────────────────────────────────────────────────────────────
16
+ # internals
17
+ # ───────────────────────────────────────────────────────────────────
18
+ def _scrape_readme(rel_path: str) -> list[str]:
19
+ """Return *.ipynb files mentioned in a README; empty list on failure."""
20
  try:
21
+ md = requests.get(f"{RAW_ROOT}{rel_path}", timeout=10).text
22
+ # markdown link target: (...filename.ipynb)
23
+ return re.findall(r"\(([^)]+?\.ipynb)\)", md)
 
24
  except requests.RequestException:
25
+ return []
 
26
 
27
 
28
+ def _discover_files() -> list[str]:
29
+ """Aggregate notebooks from all configured READMEs (no fallback)."""
30
+ files: list[str] = []
31
+ for dir_key, readme in _SECTIONS.items():
32
+ found = _scrape_readme(readme)
33
+ # Prepend the directory path if the README gives bare filenames
34
+ prefixed = [
35
+ name if "/" in name else f"{dir_key}/{name}" for name in found
36
+ ]
37
+ files.extend(prefixed)
38
+ return files
39
 
40
 
41
+ def _pretty(path: str) -> str:
42
+ """'ch-states/bloch_sphere.ipynb' β†’ 'Bloch Sphere'."""
43
+ fname = path.rsplit("/", 1)[-1]
44
+ return fname.replace("-", " ").replace(".ipynb", "").title()
45
+
 
46
 
47
+ # ───────────────────────────────────────────────────────────────────
48
+ # public tools
49
+ # ───────────────────────────────────────────────────────────────────
50
+ def get_theory_topics() -> dict[str, str]:
51
+ """Return a mapping of friendly topic names to notebook file paths.
52
+
53
+ Discovers available Jupyter notebooks from the Qiskit textbook across all
54
+ four main chapters (intro, ch-states, ch-gates, ch-algorithms) by scraping
55
+ their respective README files.
56
+
57
+ Returns:
58
+ dict[str, str]: A dictionary mapping human-readable topic names to their
59
+ corresponding notebook file paths. For example:
60
+ {'What Is Quantum': 'intro/what-is-quantum.ipynb',
61
+ 'Bloch Sphere': 'ch-states/bloch_sphere.ipynb'}
62
+ Returns an empty dictionary if network requests fail.
63
+
64
+ Note:
65
+ If network requests fail, returns an empty dictionary instead of
66
+ falling back to hardcoded content.
67
  """
68
+ try:
69
+ discovered_files = _discover_files()
70
+ if not discovered_files:
71
+ return {}
72
+ return {_pretty(p): p for p in discovered_files}
73
+ except Exception:
74
+ return {}
75
 
76
 
77
  def get_theory(
 
79
  markdown_only: bool = True,
80
  include_headers: bool = True,
81
  ) -> str:
82
+ """Download and parse a Qiskit textbook notebook, returning its content as text.
83
+
84
+ Accepts flexible topic identification: pretty names ("Teleportation"),
85
+ slugs ("teleportation"), or full paths ("intro/teleportation.ipynb").
86
+ Downloads the notebook from GitHub and extracts its content.
87
+
88
+ Args:
89
+ topic (str): The quantum topic to fetch. Can be:
90
+ - Pretty name: "Teleportation", "What Is Quantum"
91
+ - Slug: "teleportation", "what-is-quantum"
92
+ - Full path: "intro/teleportation.ipynb"
93
+ markdown_only (bool, optional): If True, include only markdown cells.
94
+ If False, also include code cells wrapped in ```python blocks.
95
+ Defaults to True.
96
+ include_headers (bool, optional): If True, prepend an H1 header with
97
+ the topic name for better readability. Defaults to True.
98
+
99
+ Returns:
100
+ str: The concatenated content of the notebook as formatted text,
101
+ with cells separated by double newlines. Returns error messages
102
+ if the topic is not found or if network requests fail.
103
+
104
+ Example:
105
+ >>> content = get_theory("teleportation")
106
+ >>> print(content[:100])
107
+ # Teleportation
108
+
109
+ Quantum teleportation is a process by which quantum information...
110
  """
111
  topics = get_theory_topics()
112
 
113
+ # Build lenient lookup table
114
  lookup: dict[str, str] = {}
115
+ for nice, path in topics.items():
116
+ slug = path.rsplit("/", 1)[-1].removesuffix(".ipynb")
117
+ lookup[nice.lower()] = path
118
+ lookup[slug.lower()] = path
119
+ lookup[path.lower()] = path
120
 
121
  key = topic.lower()
122
  if key not in lookup:
123
+ if not topics:
124
+ return "Unable to get theory - no topics available (network may be down)"
125
+ available_topics = ', '.join(topics.keys())
126
+ return f"Topic unknown: '{topic}'. Available topics: {available_topics}"
127
 
128
+ path = lookup[key]
129
+
130
+ try:
131
+ raw_json = requests.get(f"{RAW_ROOT}{path}", timeout=20).text
132
+ nb = nbformat.reads(raw_json, as_version=4)
133
+ except Exception:
134
+ return "Unable to get theory - failed to download or parse notebook content"
135
 
136
+ chunks: list[str] = []
137
  if include_headers:
138
+ chunks.append(f"# {_pretty(path)}\n")
139
 
140
  for cell in nb.cells:
141
  if cell.cell_type == "markdown":
142
+ chunks.append(cell.source)
143
  elif cell.cell_type == "code" and not markdown_only:
144
+ chunks.append(f"```python\n{cell.source}\n```")
145
 
146
+ return "\n\n".join(chunks)