Spaces:
Running
on
Zero
Running
on
Zero
Commit
·
d1ed09d
0
Parent(s):
first commit
Browse filesThis view is limited to 50 files because it contains too many changes.
See raw diff
- README.md +1 -0
- app.py +557 -0
- data_setup.py +117 -0
- openalex_env_map/lib/python3.10/site-packages/IPython/__init__.py +163 -0
- openalex_env_map/lib/python3.10/site-packages/IPython/__main__.py +15 -0
- openalex_env_map/lib/python3.10/site-packages/IPython/conftest.py +87 -0
- openalex_env_map/lib/python3.10/site-packages/IPython/consoleapp.py +12 -0
- openalex_env_map/lib/python3.10/site-packages/IPython/core/__init__.py +0 -0
- openalex_env_map/lib/python3.10/site-packages/IPython/core/alias.py +267 -0
- openalex_env_map/lib/python3.10/site-packages/IPython/core/application.py +492 -0
- openalex_env_map/lib/python3.10/site-packages/IPython/core/async_helpers.py +155 -0
- openalex_env_map/lib/python3.10/site-packages/IPython/core/autocall.py +70 -0
- openalex_env_map/lib/python3.10/site-packages/IPython/core/builtin_trap.py +86 -0
- openalex_env_map/lib/python3.10/site-packages/IPython/core/compilerop.py +214 -0
- openalex_env_map/lib/python3.10/site-packages/IPython/core/completer.py +0 -0
- openalex_env_map/lib/python3.10/site-packages/IPython/core/completerlib.py +382 -0
- openalex_env_map/lib/python3.10/site-packages/IPython/core/crashhandler.py +248 -0
- openalex_env_map/lib/python3.10/site-packages/IPython/core/debugger.py +1136 -0
- openalex_env_map/lib/python3.10/site-packages/IPython/core/display.py +1373 -0
- openalex_env_map/lib/python3.10/site-packages/IPython/core/display_functions.py +391 -0
- openalex_env_map/lib/python3.10/site-packages/IPython/core/display_trap.py +70 -0
- openalex_env_map/lib/python3.10/site-packages/IPython/core/displayhook.py +336 -0
- openalex_env_map/lib/python3.10/site-packages/IPython/core/displaypub.py +149 -0
- openalex_env_map/lib/python3.10/site-packages/IPython/core/error.py +60 -0
- openalex_env_map/lib/python3.10/site-packages/IPython/core/events.py +158 -0
- openalex_env_map/lib/python3.10/site-packages/IPython/core/excolors.py +192 -0
- openalex_env_map/lib/python3.10/site-packages/IPython/core/extensions.py +135 -0
- openalex_env_map/lib/python3.10/site-packages/IPython/core/formatters.py +1090 -0
- openalex_env_map/lib/python3.10/site-packages/IPython/core/getipython.py +24 -0
- openalex_env_map/lib/python3.10/site-packages/IPython/core/guarded_eval.py +895 -0
- openalex_env_map/lib/python3.10/site-packages/IPython/core/history.py +989 -0
- openalex_env_map/lib/python3.10/site-packages/IPython/core/historyapp.py +158 -0
- openalex_env_map/lib/python3.10/site-packages/IPython/core/hooks.py +173 -0
- openalex_env_map/lib/python3.10/site-packages/IPython/core/inputsplitter.py +799 -0
- openalex_env_map/lib/python3.10/site-packages/IPython/core/inputtransformer.py +577 -0
- openalex_env_map/lib/python3.10/site-packages/IPython/core/inputtransformer2.py +830 -0
- openalex_env_map/lib/python3.10/site-packages/IPython/core/interactiveshell.py +0 -0
- openalex_env_map/lib/python3.10/site-packages/IPython/core/latex_symbols.py +1301 -0
- openalex_env_map/lib/python3.10/site-packages/IPython/core/logger.py +231 -0
- openalex_env_map/lib/python3.10/site-packages/IPython/core/macro.py +53 -0
- openalex_env_map/lib/python3.10/site-packages/IPython/core/magic.py +759 -0
- openalex_env_map/lib/python3.10/site-packages/IPython/core/magic_arguments.py +310 -0
- openalex_env_map/lib/python3.10/site-packages/IPython/core/magics/__init__.py +42 -0
- openalex_env_map/lib/python3.10/site-packages/IPython/core/magics/ast_mod.py +330 -0
- openalex_env_map/lib/python3.10/site-packages/IPython/core/magics/auto.py +144 -0
- openalex_env_map/lib/python3.10/site-packages/IPython/core/magics/basic.py +666 -0
- openalex_env_map/lib/python3.10/site-packages/IPython/core/magics/code.py +757 -0
- openalex_env_map/lib/python3.10/site-packages/IPython/core/magics/config.py +140 -0
- openalex_env_map/lib/python3.10/site-packages/IPython/core/magics/display.py +93 -0
- openalex_env_map/lib/python3.10/site-packages/IPython/core/magics/execution.py +1624 -0
README.md
ADDED
@@ -0,0 +1 @@
|
|
|
|
|
1 |
+
...
|
app.py
ADDED
@@ -0,0 +1,557 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import time
|
2 |
+
print(f"Starting up: {time.strftime('%Y-%m-%d %H:%M:%S')}")
|
3 |
+
|
4 |
+
# Standard library imports
|
5 |
+
import os
|
6 |
+
from pathlib import Path
|
7 |
+
from datetime import datetime
|
8 |
+
from itertools import chain
|
9 |
+
|
10 |
+
# Third-party imports
|
11 |
+
import numpy as np
|
12 |
+
import pandas as pd
|
13 |
+
import torch
|
14 |
+
import gradio as gr
|
15 |
+
from fastapi import FastAPI
|
16 |
+
from fastapi.staticfiles import StaticFiles
|
17 |
+
import uvicorn
|
18 |
+
import matplotlib.pyplot as plt
|
19 |
+
import tqdm
|
20 |
+
import colormaps
|
21 |
+
import matplotlib.colors as mcolors
|
22 |
+
|
23 |
+
|
24 |
+
import opinionated # for fonts
|
25 |
+
plt.style.use("opinionated_rc")
|
26 |
+
|
27 |
+
from sklearn.neighbors import NearestNeighbors
|
28 |
+
|
29 |
+
|
30 |
+
def is_running_in_hf_space():
|
31 |
+
return "SPACE_ID" in os.environ
|
32 |
+
|
33 |
+
if is_running_in_hf_space():
|
34 |
+
import spaces # necessary to run on Zero.
|
35 |
+
|
36 |
+
import datamapplot
|
37 |
+
import pyalex
|
38 |
+
|
39 |
+
# Local imports
|
40 |
+
from openalex_utils import (
|
41 |
+
openalex_url_to_pyalex_query,
|
42 |
+
get_field,
|
43 |
+
process_records_to_df,
|
44 |
+
openalex_url_to_filename
|
45 |
+
)
|
46 |
+
from styles import DATAMAP_CUSTOM_CSS
|
47 |
+
from data_setup import (
|
48 |
+
download_required_files,
|
49 |
+
setup_basemap_data,
|
50 |
+
setup_mapper,
|
51 |
+
setup_embedding_model,
|
52 |
+
|
53 |
+
)
|
54 |
+
|
55 |
+
# Configure OpenAlex
|
56 |
+
pyalex.config.email = "[email protected]"
|
57 |
+
|
58 |
+
print(f"Imports completed: {time.strftime('%Y-%m-%d %H:%M:%S')}")
|
59 |
+
|
60 |
+
# FastAPI setup
|
61 |
+
app = FastAPI()
|
62 |
+
static_dir = Path('./static')
|
63 |
+
static_dir.mkdir(parents=True, exist_ok=True)
|
64 |
+
app.mount("/static", StaticFiles(directory=static_dir), name="static")
|
65 |
+
|
66 |
+
# Gradio configuration
|
67 |
+
gr.set_static_paths(paths=["static/"])
|
68 |
+
|
69 |
+
# Resource configuration
|
70 |
+
REQUIRED_FILES = {
|
71 |
+
"100k_filtered_OA_sample_cluster_and_positions_supervised.pkl":
|
72 |
+
"https://huggingface.co/datasets/m7n/intermediate_sci_pickle/resolve/main/100k_filtered_OA_sample_cluster_and_positions_supervised.pkl",
|
73 |
+
"umap_mapper_250k_random_OA_discipline_tuned_specter_2_params.pkl":
|
74 |
+
"https://huggingface.co/datasets/m7n/intermediate_sci_pickle/resolve/main/umap_mapper_250k_random_OA_discipline_tuned_specter_2_params.pkl"
|
75 |
+
}
|
76 |
+
BASEMAP_PATH = "100k_filtered_OA_sample_cluster_and_positions_supervised.pkl"
|
77 |
+
MAPPER_PARAMS_PATH = "umap_mapper_250k_random_OA_discipline_tuned_specter_2_params.pkl"
|
78 |
+
MODEL_NAME = "m7n/discipline-tuned_specter_2_024"
|
79 |
+
|
80 |
+
# Initialize models and data
|
81 |
+
start_time = time.time()
|
82 |
+
print("Initializing resources...")
|
83 |
+
|
84 |
+
download_required_files(REQUIRED_FILES)
|
85 |
+
basedata_df = setup_basemap_data(BASEMAP_PATH)
|
86 |
+
mapper = setup_mapper(MAPPER_PARAMS_PATH)
|
87 |
+
model = setup_embedding_model(MODEL_NAME)
|
88 |
+
|
89 |
+
print(f"Resources initialized in {time.time() - start_time:.2f} seconds")
|
90 |
+
|
91 |
+
|
92 |
+
|
93 |
+
# Setting up decorators for embedding on HF-Zero:
|
94 |
+
def no_op_decorator(func):
|
95 |
+
"""A no-op (no operation) decorator that simply returns the function."""
|
96 |
+
def wrapper(*args, **kwargs):
|
97 |
+
# Do nothing special
|
98 |
+
return func(*args, **kwargs)
|
99 |
+
return wrapper
|
100 |
+
|
101 |
+
# Decide which decorator to use based on environment
|
102 |
+
decorator_to_use = spaces.GPU(duration=60) if is_running_in_hf_space() else no_op_decorator
|
103 |
+
|
104 |
+
|
105 |
+
@decorator_to_use
|
106 |
+
def create_embeddings(texts_to_embedd):
|
107 |
+
"""Create embeddings for the input texts using the loaded model."""
|
108 |
+
return model.encode(texts_to_embedd, show_progress_bar=True, batch_size=192)
|
109 |
+
|
110 |
+
def predict(text_input, sample_size_slider, reduce_sample_checkbox, sample_reduction_method,
|
111 |
+
plot_time_checkbox, locally_approximate_publication_date_checkbox,
|
112 |
+
download_csv_checkbox, download_png_checkbox, progress=gr.Progress()):
|
113 |
+
"""
|
114 |
+
Main prediction pipeline that processes OpenAlex queries and creates visualizations.
|
115 |
+
|
116 |
+
Args:
|
117 |
+
text_input (str): OpenAlex query URL
|
118 |
+
sample_size_slider (int): Maximum number of samples to process
|
119 |
+
reduce_sample_checkbox (bool): Whether to reduce sample size
|
120 |
+
sample_reduction_method (str): Method for sample reduction ("Random" or "Order of Results")
|
121 |
+
plot_time_checkbox (bool): Whether to color points by publication date
|
122 |
+
locally_approximate_publication_date_checkbox (bool): Whether to approximate publication date locally before plotting.
|
123 |
+
progress (gr.Progress): Gradio progress tracker
|
124 |
+
|
125 |
+
Returns:
|
126 |
+
tuple: (link to visualization, iframe HTML)
|
127 |
+
"""
|
128 |
+
# Check if input is empty or whitespace
|
129 |
+
print(f"Input: {text_input}")
|
130 |
+
if not text_input or text_input.isspace():
|
131 |
+
return "Error: Please enter a valid OpenAlex URL in the 'OpenAlex-search URL'-field", gr.DownloadButton(label=f"Download Interactive Visualization", value='html_file_path', visible=False)
|
132 |
+
|
133 |
+
|
134 |
+
# Check if the input is a valid OpenAlex URL
|
135 |
+
|
136 |
+
|
137 |
+
|
138 |
+
start_time = time.time()
|
139 |
+
print('Starting data projection pipeline')
|
140 |
+
progress(0.1, desc="Starting...")
|
141 |
+
|
142 |
+
# Query OpenAlex
|
143 |
+
query_start = time.time()
|
144 |
+
query, params = openalex_url_to_pyalex_query(text_input)
|
145 |
+
|
146 |
+
filename = openalex_url_to_filename(text_input)
|
147 |
+
print(f"Filename: {filename}")
|
148 |
+
|
149 |
+
query_length = query.count()
|
150 |
+
print(f'Requesting {query_length} entries...')
|
151 |
+
|
152 |
+
records = []
|
153 |
+
target_size = sample_size_slider if reduce_sample_checkbox and sample_reduction_method == "First n samples" else query_length
|
154 |
+
|
155 |
+
|
156 |
+
should_break = False
|
157 |
+
for page in query.paginate(per_page=200,n_max=None):
|
158 |
+
for record in page:
|
159 |
+
records.append(record)
|
160 |
+
progress(0.1 + (0.2 * len(records) / target_size), desc="Getting queried data...")
|
161 |
+
# print(len(records))
|
162 |
+
if reduce_sample_checkbox and sample_reduction_method == "First n samples" and len(records) >= target_size:
|
163 |
+
should_break = True
|
164 |
+
break
|
165 |
+
if should_break:
|
166 |
+
break
|
167 |
+
|
168 |
+
print(f"Query completed in {time.time() - query_start:.2f} seconds")
|
169 |
+
|
170 |
+
# Process records
|
171 |
+
processing_start = time.time()
|
172 |
+
records_df = process_records_to_df(records)
|
173 |
+
|
174 |
+
if reduce_sample_checkbox and sample_reduction_method != "All":
|
175 |
+
sample_size = min(sample_size_slider, len(records_df))
|
176 |
+
if sample_reduction_method == "n random samples":
|
177 |
+
records_df = records_df.sample(sample_size)
|
178 |
+
elif sample_reduction_method == "First n samples":
|
179 |
+
records_df = records_df.iloc[:sample_size]
|
180 |
+
print(f"Records processed in {time.time() - processing_start:.2f} seconds")
|
181 |
+
|
182 |
+
# Create embeddings
|
183 |
+
embedding_start = time.time()
|
184 |
+
progress(0.3, desc="Embedding Data...")
|
185 |
+
texts_to_embedd = [f"{title} {abstract}" for title, abstract
|
186 |
+
in zip(records_df['title'], records_df['abstract'])]
|
187 |
+
embeddings = create_embeddings(texts_to_embedd)
|
188 |
+
print(f"Embeddings created in {time.time() - embedding_start:.2f} seconds")
|
189 |
+
|
190 |
+
# Project embeddings
|
191 |
+
projection_start = time.time()
|
192 |
+
progress(0.5, desc="Project into UMAP-embedding...")
|
193 |
+
umap_embeddings = mapper.transform(embeddings)
|
194 |
+
records_df[['x','y']] = umap_embeddings
|
195 |
+
print(f"Projection completed in {time.time() - projection_start:.2f} seconds")
|
196 |
+
|
197 |
+
# Prepare visualization data
|
198 |
+
viz_prep_start = time.time()
|
199 |
+
progress(0.6, desc="Preparing visualization data...")
|
200 |
+
|
201 |
+
basedata_df['color'] = '#ced4d211'
|
202 |
+
|
203 |
+
if not plot_time_checkbox:
|
204 |
+
records_df['color'] = '#5e2784'
|
205 |
+
else:
|
206 |
+
cmap = colormaps.haline
|
207 |
+
if not locally_approximate_publication_date_checkbox:
|
208 |
+
# Create color mapping based on publication years
|
209 |
+
years = pd.to_numeric(records_df['publication_year'])
|
210 |
+
norm = mcolors.Normalize(vmin=years.min(), vmax=years.max())
|
211 |
+
records_df['color'] = [mcolors.to_hex(cmap(norm(year))) for year in years]
|
212 |
+
|
213 |
+
else:
|
214 |
+
n_neighbors = 10 # Adjust this value to control smoothing
|
215 |
+
nn = NearestNeighbors(n_neighbors=n_neighbors)
|
216 |
+
nn.fit(umap_embeddings)
|
217 |
+
distances, indices = nn.kneighbors(umap_embeddings)
|
218 |
+
|
219 |
+
# Calculate local average publication year for each point
|
220 |
+
local_years = np.array([
|
221 |
+
np.mean(records_df['publication_year'].iloc[idx])
|
222 |
+
for idx in indices
|
223 |
+
])
|
224 |
+
norm = mcolors.Normalize(vmin=local_years.min(), vmax=local_years.max())
|
225 |
+
records_df['color'] = [mcolors.to_hex(cmap(norm(year))) for year in local_years]
|
226 |
+
|
227 |
+
|
228 |
+
|
229 |
+
stacked_df = pd.concat([basedata_df, records_df], axis=0, ignore_index=True)
|
230 |
+
stacked_df = stacked_df.fillna("Unlabelled")
|
231 |
+
stacked_df['parsed_field'] = [get_field(row) for ix, row in stacked_df.iterrows()]
|
232 |
+
extra_data = pd.DataFrame(stacked_df['doi'])
|
233 |
+
print(f"Visualization data prepared in {time.time() - viz_prep_start:.2f} seconds")
|
234 |
+
|
235 |
+
# Create and save plot
|
236 |
+
plot_start = time.time()
|
237 |
+
progress(0.7, desc="Creating plot...")
|
238 |
+
|
239 |
+
|
240 |
+
plot = datamapplot.create_interactive_plot(
|
241 |
+
stacked_df[['x','y']].values,
|
242 |
+
np.array(stacked_df['cluster_2_labels']),
|
243 |
+
np.array(['Unlabelled' if pd.isna(x) else x for x in stacked_df['parsed_field']]),
|
244 |
+
|
245 |
+
hover_text=[str(row['title']) for ix, row in stacked_df.iterrows()],
|
246 |
+
marker_color_array=stacked_df['color'],
|
247 |
+
use_medoids=True,
|
248 |
+
width=1000,
|
249 |
+
height=1000,
|
250 |
+
point_radius_min_pixels=1,
|
251 |
+
text_outline_width=5,
|
252 |
+
point_hover_color='#5e2784',
|
253 |
+
point_radius_max_pixels=7,
|
254 |
+
color_label_text=False,
|
255 |
+
font_family="Roboto Condensed",
|
256 |
+
font_weight=700,
|
257 |
+
tooltip_font_weight=600,
|
258 |
+
tooltip_font_family="Roboto Condensed",
|
259 |
+
extra_point_data=extra_data,
|
260 |
+
on_click="window.open(`{doi}`)",
|
261 |
+
custom_css=DATAMAP_CUSTOM_CSS,
|
262 |
+
initial_zoom_fraction=.8,
|
263 |
+
enable_search=False
|
264 |
+
)
|
265 |
+
|
266 |
+
# Save plot
|
267 |
+
html_file_name = f"{filename}.html"
|
268 |
+
html_file_path = static_dir / html_file_name
|
269 |
+
plot.save(html_file_path)
|
270 |
+
print(f"Plot created and saved in {time.time() - plot_start:.2f} seconds")
|
271 |
+
|
272 |
+
|
273 |
+
|
274 |
+
# Save additional files if requested
|
275 |
+
csv_file_path = static_dir / f"{filename}.csv"
|
276 |
+
png_file_path = static_dir / f"{filename}.png"
|
277 |
+
|
278 |
+
if download_csv_checkbox:
|
279 |
+
# Export relevant columns
|
280 |
+
export_df = records_df[['title', 'abstract', 'doi', 'publication_year', 'x', 'y']]
|
281 |
+
export_df.to_csv(csv_file_path, index=False)
|
282 |
+
|
283 |
+
if download_png_checkbox:
|
284 |
+
png_start_time = time.time()
|
285 |
+
print("Starting PNG generation...")
|
286 |
+
|
287 |
+
# Sample and prepare data
|
288 |
+
sample_prep_start = time.time()
|
289 |
+
sample_to_plot = basedata_df#.sample(20000)
|
290 |
+
labels1 = np.array(sample_to_plot['cluster_2_labels'])
|
291 |
+
labels2 = np.array(['Unlabelled' if pd.isna(x) else x for x in sample_to_plot['parsed_field']])
|
292 |
+
|
293 |
+
ratio = 0.6
|
294 |
+
mask = np.random.random(size=len(labels1)) < ratio
|
295 |
+
combined_labels = np.where(mask, labels1, labels2)
|
296 |
+
|
297 |
+
# Get the 30 most common labels
|
298 |
+
unique_labels, counts = np.unique(combined_labels, return_counts=True)
|
299 |
+
top_30_labels = set(unique_labels[np.argsort(counts)[-50:]])
|
300 |
+
|
301 |
+
# Replace less common labels with 'Unlabelled'
|
302 |
+
combined_labels = np.array(['Unlabelled' if label not in top_30_labels else label for label in combined_labels])
|
303 |
+
|
304 |
+
colors_base = ['#536878' for _ in range(len(labels1))]
|
305 |
+
print(f"Sample preparation completed in {time.time() - sample_prep_start:.2f} seconds")
|
306 |
+
|
307 |
+
# Create main plot
|
308 |
+
print(sample_to_plot[['x','y']].values)
|
309 |
+
print(combined_labels)
|
310 |
+
|
311 |
+
main_plot_start = time.time()
|
312 |
+
fig, ax = datamapplot.create_plot(
|
313 |
+
sample_to_plot[['x','y']].values,
|
314 |
+
combined_labels,
|
315 |
+
label_wrap_width=12,
|
316 |
+
label_over_points=True,
|
317 |
+
dynamic_label_size=True,
|
318 |
+
use_medoids=True,
|
319 |
+
point_size=2,
|
320 |
+
marker_color_array=colors_base,
|
321 |
+
force_matplotlib=True,
|
322 |
+
max_font_size=12,
|
323 |
+
min_font_size=4,
|
324 |
+
min_font_weight=100,
|
325 |
+
max_font_weight=300,
|
326 |
+
font_family="Roboto Condensed",
|
327 |
+
color_label_text=False, add_glow=False,
|
328 |
+
highlight_labels=list(np.unique(labels1)),
|
329 |
+
label_font_size=8,
|
330 |
+
highlight_label_keywords={"fontsize": 12, "fontweight": "bold", "bbox":{"boxstyle":"circle", "pad":0.75,'alpha':0.}},
|
331 |
+
)
|
332 |
+
print(f"Main plot creation completed in {time.time() - main_plot_start:.2f} seconds")
|
333 |
+
|
334 |
+
# Time-based visualization
|
335 |
+
scatter_start = time.time()
|
336 |
+
if plot_time_checkbox:
|
337 |
+
if locally_approximate_publication_date_checkbox:
|
338 |
+
scatter = plt.scatter(
|
339 |
+
umap_embeddings[:,0],
|
340 |
+
umap_embeddings[:,1],
|
341 |
+
c=local_years,
|
342 |
+
cmap=colormaps.haline,
|
343 |
+
alpha=0.8,
|
344 |
+
s=5
|
345 |
+
)
|
346 |
+
else:
|
347 |
+
years = pd.to_numeric(records_df['publication_year'])
|
348 |
+
scatter = plt.scatter(
|
349 |
+
umap_embeddings[:,0],
|
350 |
+
umap_embeddings[:,1],
|
351 |
+
c=years,
|
352 |
+
cmap=colormaps.haline,
|
353 |
+
alpha=0.8,
|
354 |
+
s=5
|
355 |
+
)
|
356 |
+
plt.colorbar(scatter, shrink=0.5, format='%d')
|
357 |
+
else:
|
358 |
+
scatter = plt.scatter(
|
359 |
+
umap_embeddings[:,0],
|
360 |
+
umap_embeddings[:,1],
|
361 |
+
c=records_df['color'],
|
362 |
+
alpha=0.8,
|
363 |
+
s=5
|
364 |
+
)
|
365 |
+
print(f"Scatter plot creation completed in {time.time() - scatter_start:.2f} seconds")
|
366 |
+
|
367 |
+
# Save plot
|
368 |
+
save_start = time.time()
|
369 |
+
plt.axis('off')
|
370 |
+
png_file_path = static_dir / f"{filename}.png"
|
371 |
+
plt.savefig(png_file_path, dpi=300, bbox_inches='tight')
|
372 |
+
plt.close()
|
373 |
+
print(f"Plot saving completed in {time.time() - save_start:.2f} seconds")
|
374 |
+
|
375 |
+
print(f"Total PNG generation completed in {time.time() - png_start_time:.2f} seconds")
|
376 |
+
|
377 |
+
|
378 |
+
|
379 |
+
|
380 |
+
|
381 |
+
|
382 |
+
|
383 |
+
progress(1.0, desc="Done!")
|
384 |
+
print(f"Total pipeline completed in {time.time() - start_time:.2f} seconds")
|
385 |
+
|
386 |
+
iframe = f"""<iframe src="/static/{html_file_name}" width="100%" height="1000px"></iframe>"""
|
387 |
+
|
388 |
+
# Return iframe and download buttons with appropriate visibility
|
389 |
+
return [
|
390 |
+
iframe,
|
391 |
+
gr.DownloadButton(label="Download Interactive Visualization", value=html_file_path, visible=True),
|
392 |
+
gr.DownloadButton(label="Download CSV Data", value=csv_file_path, visible=download_csv_checkbox),
|
393 |
+
gr.DownloadButton(label="Download Static Plot", value=png_file_path, visible=download_png_checkbox),
|
394 |
+
gr.Button(visible=False) # Return hidden state for cancel button
|
395 |
+
]
|
396 |
+
|
397 |
+
|
398 |
+
theme = gr.themes.Monochrome(
|
399 |
+
font=[gr.themes.GoogleFont("Roboto Condensed"), "ui-sans-serif", "system-ui", "sans-serif"],
|
400 |
+
|
401 |
+
text_size="lg",
|
402 |
+
|
403 |
+
)
|
404 |
+
|
405 |
+
|
406 |
+
# Gradio interface setup
|
407 |
+
with gr.Blocks(theme=theme) as demo:
|
408 |
+
gr.Markdown("""
|
409 |
+
<div style="max-width: 100%; margin: 0 auto;">
|
410 |
+
<br>
|
411 |
+
|
412 |
+
# OpenAlex Mapper
|
413 |
+
|
414 |
+
OpenAlex Mapper is a way of projecting search queries from the amazing OpenAlex database on a background map of randomly sampled papers from OpenAlex, which allows you to easily investigate interdisciplinary connections. OpenAlex Mapper was developed by Maximilian Noichl and Andrea Loettgers at the Possible Life project.
|
415 |
+
|
416 |
+
To use OpenAlex Mapper, first head over to [OpenAlex](https://openalex.org/) and search for something that interests you. For example, you could search for all the papers that make use of the [Kuramoto model](https://openalex.org/works?page=1&filter=default.search%3A%22Kuramoto%20Model%22), for all the papers that were published by researchers at [Utrecht University in 2019](https://openalex.org/works?page=1&filter=authorships.institutions.lineage%3Ai193662353,publication_year%3A2019), or for all the papers that cite Wittgenstein's [Philosophical Investigations](https://openalex.org/works?page=1&filter=cites%3Aw4251395411). Then you copy the URL to that search query into the OpenAlex search URL box below and click "Run Query." It will take a moment to download all of these records from OpenAlex and embed them on our interactive map. After a little time, that map will appear and be available for you to interact with and download. You can find more explanations in the FAQs below.
|
417 |
+
</div>
|
418 |
+
""")
|
419 |
+
|
420 |
+
with gr.Row():
|
421 |
+
with gr.Column(scale=1):
|
422 |
+
with gr.Row():
|
423 |
+
run_btn = gr.Button("Run Query", variant='primary')
|
424 |
+
cancel_btn = gr.Button("Cancel", visible=False, variant='secondary')
|
425 |
+
|
426 |
+
# Create separate download buttons
|
427 |
+
html_download = gr.DownloadButton("Download Interactive Visualization", visible=False)
|
428 |
+
csv_download = gr.DownloadButton("Download CSV Data", visible=False)
|
429 |
+
png_download = gr.DownloadButton("Download Static Plot", visible=False)
|
430 |
+
|
431 |
+
text_input = gr.Textbox(label="OpenAlex-search URL",
|
432 |
+
info="Enter the URL to an OpenAlex-search.")
|
433 |
+
|
434 |
+
gr.Markdown("### Sample Settings")
|
435 |
+
reduce_sample_checkbox = gr.Checkbox(
|
436 |
+
label="Reduce Sample Size",
|
437 |
+
value=True,
|
438 |
+
info="Reduce sample size."
|
439 |
+
)
|
440 |
+
sample_reduction_method = gr.Dropdown(
|
441 |
+
["All", "First n samples", "n random samples"],
|
442 |
+
label="Sample Selection Method",
|
443 |
+
value="First n samples",
|
444 |
+
info="How to choose the samples to keep."
|
445 |
+
)
|
446 |
+
sample_size_slider = gr.Slider(
|
447 |
+
label="Sample Size",
|
448 |
+
minimum=500,
|
449 |
+
maximum=20000,
|
450 |
+
step=10,
|
451 |
+
value=1000,
|
452 |
+
info="How many samples to keep.",
|
453 |
+
visible=True
|
454 |
+
)
|
455 |
+
|
456 |
+
gr.Markdown("### Plot Settings")
|
457 |
+
plot_time_checkbox = gr.Checkbox(
|
458 |
+
label="Plot Time",
|
459 |
+
value=True,
|
460 |
+
info="Colour points by their publication date."
|
461 |
+
)
|
462 |
+
locally_approximate_publication_date_checkbox = gr.Checkbox(
|
463 |
+
label="Locally Approximate Publication Date",
|
464 |
+
value=True,
|
465 |
+
info="Colour points by the average publicaion date in their area."
|
466 |
+
)
|
467 |
+
|
468 |
+
gr.Markdown("### Download Options")
|
469 |
+
download_csv_checkbox = gr.Checkbox(
|
470 |
+
label="Generate CSV Export",
|
471 |
+
value=False,
|
472 |
+
info="Export the data as CSV file"
|
473 |
+
)
|
474 |
+
download_png_checkbox = gr.Checkbox(
|
475 |
+
label="Generate Static PNG Plot",
|
476 |
+
value=False,
|
477 |
+
info="Export a static PNG visualization. This will make things slower!"
|
478 |
+
)
|
479 |
+
|
480 |
+
|
481 |
+
|
482 |
+
|
483 |
+
with gr.Column(scale=2):
|
484 |
+
html = gr.HTML(
|
485 |
+
value='<div style="width: 100%; height: 1000px; display: flex; justify-content: center; align-items: center; border: 1px solid #ccc; background-color: #f8f9fa;"><p style="font-size: 1.2em; color: #666;">The visualization map will appear here after running a query</p></div>',
|
486 |
+
label="HTML preview",
|
487 |
+
show_label=True
|
488 |
+
)
|
489 |
+
gr.Markdown("""
|
490 |
+
<div style="max-width: 100%; margin: 0 auto;">
|
491 |
+
|
492 |
+
# FAQs
|
493 |
+
|
494 |
+
## Who made this?
|
495 |
+
|
496 |
+
This project was developed by [Maximilian Noichl](https://maxnoichl.eu) (Utrecht University), in cooperation with Andrea Loettger and Tarja Knuuttila at the [Possible Life project](http://www.possiblelife.eu/), at the University of Vienna. If this project is useful in any way for your research, we would appreciate citation of **...**
|
497 |
+
|
498 |
+
This project received funding from the European Research Council under the European Union's Horizon 2020 research and innovation programme (LIFEMODE project, grant agreement No. 818772).
|
499 |
+
|
500 |
+
## How does it work?
|
501 |
+
|
502 |
+
The base map for this project is developed by randomly downloading 250,000 articles from OpenAlex, then embedding their abstracts using our [fine-tuned](https://huggingface.co/m7n/discipline-tuned_specter_2_024) version of the [specter-2](https://huggingface.co/allenai/specter2_aug2023refresh_base) language model, running these embeddings through [UMAP](https://umap-learn.readthedocs.io/en/latest/) to give us a two-dimensional representation, and displaying that in an interactive window using [datamapplot](https://datamapplot.readthedocs.io/en/latest/index.html). After the data for your query is downloaded from OpenAlex, it then undergoes the exact same process, but the pre-trained UMAP model from earlier is used to project your new data points onto this original map, showing where they would show up if they were included in the original sample. For more details, you can take a look at the method section of this paper: **...**
|
503 |
+
|
504 |
+
## I think I found a mistake in the map.
|
505 |
+
|
506 |
+
There are various considerations to take into account when working with this map:
|
507 |
+
|
508 |
+
1. The language model we use is fine-tuned to separate disciplines from each other, but of course, disciplines are weird, partially subjective social categories, so what the model has learned might not always correspond perfectly to what you would expect to see.
|
509 |
+
|
510 |
+
2. When pressing down a really high-dimensional space into a low-dimensional one, there will be trade-offs. For example, we see this big ring structure of the sciences on the map, but in the middle of the map there is a overly stretchedstring of bioinformaticsthat stretches from computer science at the bottom up to the life sciences clusters at the top. This is one of the areas where the UMAP algorithm had trouble pressing our high-dimensional dataset into a low-dimensional space. For more information on how to read a UMAP plot, I recommend looking into ["Understanding UMAP"](https://pair-code.github.io/understanding-umap/) by Andy Coenen & Adam Pearce.
|
511 |
+
|
512 |
+
3. Finally, the labels we're using for the regions of this plot are created from OpenAlex's own labels of sub-disciplines. They give a rough indication of the papers that could be expected in this broad area of the map, but they are not necessarily the perfect label for the articles that are precisely below them. They are just located at the median point of a usually much larger, much broader, and fuzzier category, so they should always be taken with quite a big grain of salt.
|
513 |
+
</div>
|
514 |
+
""")
|
515 |
+
|
516 |
+
def update_slider_visibility(method):
|
517 |
+
return gr.Slider(visible=(method != "All"))
|
518 |
+
|
519 |
+
sample_reduction_method.change(
|
520 |
+
fn=update_slider_visibility,
|
521 |
+
inputs=[sample_reduction_method],
|
522 |
+
outputs=[sample_size_slider]
|
523 |
+
)
|
524 |
+
|
525 |
+
def show_cancel_button():
|
526 |
+
return gr.Button(visible=True)
|
527 |
+
|
528 |
+
def hide_cancel_button():
|
529 |
+
return gr.Button(visible=False)
|
530 |
+
|
531 |
+
# Update the run button click event
|
532 |
+
run_event = run_btn.click(
|
533 |
+
fn=show_cancel_button,
|
534 |
+
outputs=cancel_btn,
|
535 |
+
queue=False
|
536 |
+
).then(
|
537 |
+
fn=predict,
|
538 |
+
inputs=[text_input, sample_size_slider, reduce_sample_checkbox,
|
539 |
+
sample_reduction_method, plot_time_checkbox,
|
540 |
+
locally_approximate_publication_date_checkbox,
|
541 |
+
download_csv_checkbox, download_png_checkbox],
|
542 |
+
outputs=[html, html_download, csv_download, png_download, cancel_btn]
|
543 |
+
)
|
544 |
+
|
545 |
+
# Add cancel button click event
|
546 |
+
cancel_btn.click(
|
547 |
+
fn=hide_cancel_button,
|
548 |
+
outputs=cancel_btn,
|
549 |
+
cancels=[run_event],
|
550 |
+
queue=False # Important to make the button hide immediately
|
551 |
+
)
|
552 |
+
|
553 |
+
# Mount and run app
|
554 |
+
app = gr.mount_gradio_app(app, demo, path="/")
|
555 |
+
|
556 |
+
if __name__ == "__main__":
|
557 |
+
uvicorn.run(app, host="0.0.0.0", port=7860)
|
data_setup.py
ADDED
@@ -0,0 +1,117 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import pickle
|
2 |
+
import requests
|
3 |
+
import umap
|
4 |
+
from numba.typed import List
|
5 |
+
import torch
|
6 |
+
from sentence_transformers import SentenceTransformer
|
7 |
+
import time
|
8 |
+
from pathlib import Path
|
9 |
+
|
10 |
+
def check_resources(files_dict, basemap_path, mapper_params_path):
|
11 |
+
"""
|
12 |
+
Check if all required resources are present.
|
13 |
+
|
14 |
+
Args:
|
15 |
+
files_dict (dict): Dictionary mapping filenames to their download URLs
|
16 |
+
basemap_path (str): Path to the basemap pickle file
|
17 |
+
mapper_params_path (str): Path to the UMAP mapper parameters pickle file
|
18 |
+
|
19 |
+
Returns:
|
20 |
+
bool: True if all resources are present, False otherwise
|
21 |
+
"""
|
22 |
+
all_files_present = True
|
23 |
+
|
24 |
+
# Check downloaded files
|
25 |
+
for filename in files_dict.keys():
|
26 |
+
if not Path(filename).exists():
|
27 |
+
print(f"Missing file: {filename}")
|
28 |
+
all_files_present = False
|
29 |
+
|
30 |
+
# Check basemap
|
31 |
+
if not Path(basemap_path).exists():
|
32 |
+
print(f"Missing basemap file: {basemap_path}")
|
33 |
+
all_files_present = False
|
34 |
+
|
35 |
+
# Check mapper params
|
36 |
+
if not Path(mapper_params_path).exists():
|
37 |
+
print(f"Missing mapper params file: {mapper_params_path}")
|
38 |
+
all_files_present = False
|
39 |
+
|
40 |
+
return all_files_present
|
41 |
+
|
42 |
+
def download_required_files(files_dict):
|
43 |
+
"""
|
44 |
+
Download required files from URLs only if they don't exist.
|
45 |
+
|
46 |
+
Args:
|
47 |
+
files_dict (dict): Dictionary mapping filenames to their download URLs
|
48 |
+
"""
|
49 |
+
print(f"Checking required files: {time.strftime('%Y-%m-%d %H:%M:%S')}")
|
50 |
+
|
51 |
+
files_to_download = {
|
52 |
+
filename: url
|
53 |
+
for filename, url in files_dict.items()
|
54 |
+
if not Path(filename).exists()
|
55 |
+
}
|
56 |
+
|
57 |
+
if not files_to_download:
|
58 |
+
print("All files already present, skipping downloads")
|
59 |
+
return
|
60 |
+
|
61 |
+
print(f"Downloading missing files: {list(files_to_download.keys())}")
|
62 |
+
for filename, url in files_to_download.items():
|
63 |
+
print(f"Downloading {filename}...")
|
64 |
+
response = requests.get(url)
|
65 |
+
with open(filename, "wb") as f:
|
66 |
+
f.write(response.content)
|
67 |
+
|
68 |
+
def setup_basemap_data(basemap_path):
|
69 |
+
"""
|
70 |
+
Load and setup the base map data.
|
71 |
+
|
72 |
+
Args:
|
73 |
+
basemap_path (str): Path to the basemap pickle file
|
74 |
+
"""
|
75 |
+
print(f"Getting basemap data: {time.strftime('%Y-%m-%d %H:%M:%S')}")
|
76 |
+
basedata_df = pickle.load(open(basemap_path, 'rb'))
|
77 |
+
return basedata_df
|
78 |
+
|
79 |
+
def setup_mapper(mapper_params_path):
|
80 |
+
"""
|
81 |
+
Setup and configure the UMAP mapper.
|
82 |
+
|
83 |
+
Args:
|
84 |
+
mapper_params_path (str): Path to the UMAP mapper parameters pickle file
|
85 |
+
"""
|
86 |
+
print(f"Getting Mapper: {time.strftime('%Y-%m-%d %H:%M:%S')}")
|
87 |
+
|
88 |
+
params_new = pickle.load(open(mapper_params_path, 'rb'))
|
89 |
+
print("setting up mapper...")
|
90 |
+
mapper = umap.UMAP()
|
91 |
+
|
92 |
+
umap_params = {k: v for k, v in params_new.get('umap_params', {}).items()
|
93 |
+
if k != 'target_backend'}
|
94 |
+
mapper.set_params(**umap_params)
|
95 |
+
|
96 |
+
for attr, value in params_new.get('umap_attributes', {}).items():
|
97 |
+
if attr != 'embedding_':
|
98 |
+
setattr(mapper, attr, value)
|
99 |
+
|
100 |
+
if 'embedding_' in params_new.get('umap_attributes', {}):
|
101 |
+
mapper.embedding_ = List(params_new['umap_attributes']['embedding_'])
|
102 |
+
|
103 |
+
return mapper
|
104 |
+
|
105 |
+
def setup_embedding_model(model_name):
|
106 |
+
"""
|
107 |
+
Setup the SentenceTransformer model.
|
108 |
+
|
109 |
+
Args:
|
110 |
+
model_name (str): Name or path of the SentenceTransformer model
|
111 |
+
"""
|
112 |
+
print(f"Setting up language model: {time.strftime('%Y-%m-%d %H:%M:%S')}")
|
113 |
+
device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
|
114 |
+
print(f"Using device: {device}")
|
115 |
+
|
116 |
+
model = SentenceTransformer(model_name)
|
117 |
+
return model
|
openalex_env_map/lib/python3.10/site-packages/IPython/__init__.py
ADDED
@@ -0,0 +1,163 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# PYTHON_ARGCOMPLETE_OK
|
2 |
+
"""
|
3 |
+
IPython: tools for interactive and parallel computing in Python.
|
4 |
+
|
5 |
+
https://ipython.org
|
6 |
+
"""
|
7 |
+
#-----------------------------------------------------------------------------
|
8 |
+
# Copyright (c) 2008-2011, IPython Development Team.
|
9 |
+
# Copyright (c) 2001-2007, Fernando Perez <[email protected]>
|
10 |
+
# Copyright (c) 2001, Janko Hauser <[email protected]>
|
11 |
+
# Copyright (c) 2001, Nathaniel Gray <[email protected]>
|
12 |
+
#
|
13 |
+
# Distributed under the terms of the Modified BSD License.
|
14 |
+
#
|
15 |
+
# The full license is in the file COPYING.txt, distributed with this software.
|
16 |
+
#-----------------------------------------------------------------------------
|
17 |
+
|
18 |
+
#-----------------------------------------------------------------------------
|
19 |
+
# Imports
|
20 |
+
#-----------------------------------------------------------------------------
|
21 |
+
|
22 |
+
import sys
|
23 |
+
|
24 |
+
#-----------------------------------------------------------------------------
|
25 |
+
# Setup everything
|
26 |
+
#-----------------------------------------------------------------------------
|
27 |
+
|
28 |
+
# Don't forget to also update setup.py when this changes!
|
29 |
+
if sys.version_info < (3, 10):
|
30 |
+
raise ImportError(
|
31 |
+
"""
|
32 |
+
IPython 8.19+ supports Python 3.10 and above, following SPEC0.
|
33 |
+
IPython 8.13+ supports Python 3.9 and above, following NEP 29.
|
34 |
+
IPython 8.0-8.12 supports Python 3.8 and above, following NEP 29.
|
35 |
+
When using Python 2.7, please install IPython 5.x LTS Long Term Support version.
|
36 |
+
Python 3.3 and 3.4 were supported up to IPython 6.x.
|
37 |
+
Python 3.5 was supported with IPython 7.0 to 7.9.
|
38 |
+
Python 3.6 was supported with IPython up to 7.16.
|
39 |
+
Python 3.7 was still supported with the 7.x branch.
|
40 |
+
|
41 |
+
See IPython `README.rst` file for more information:
|
42 |
+
|
43 |
+
https://github.com/ipython/ipython/blob/main/README.rst
|
44 |
+
|
45 |
+
"""
|
46 |
+
)
|
47 |
+
|
48 |
+
#-----------------------------------------------------------------------------
|
49 |
+
# Setup the top level names
|
50 |
+
#-----------------------------------------------------------------------------
|
51 |
+
|
52 |
+
from .core.getipython import get_ipython
|
53 |
+
from .core import release
|
54 |
+
from .core.application import Application
|
55 |
+
from .terminal.embed import embed
|
56 |
+
|
57 |
+
from .core.interactiveshell import InteractiveShell
|
58 |
+
from .utils.sysinfo import sys_info
|
59 |
+
from .utils.frame import extract_module_locals
|
60 |
+
|
61 |
+
__all__ = ["start_ipython", "embed", "start_kernel", "embed_kernel"]
|
62 |
+
|
63 |
+
# Release data
|
64 |
+
__author__ = '%s <%s>' % (release.author, release.author_email)
|
65 |
+
__license__ = release.license
|
66 |
+
__version__ = release.version
|
67 |
+
version_info = release.version_info
|
68 |
+
# list of CVEs that should have been patched in this release.
|
69 |
+
# this is informational and should not be relied upon.
|
70 |
+
__patched_cves__ = {"CVE-2022-21699", "CVE-2023-24816"}
|
71 |
+
|
72 |
+
|
73 |
+
def embed_kernel(module=None, local_ns=None, **kwargs):
|
74 |
+
"""Embed and start an IPython kernel in a given scope.
|
75 |
+
|
76 |
+
If you don't want the kernel to initialize the namespace
|
77 |
+
from the scope of the surrounding function,
|
78 |
+
and/or you want to load full IPython configuration,
|
79 |
+
you probably want `IPython.start_kernel()` instead.
|
80 |
+
|
81 |
+
Parameters
|
82 |
+
----------
|
83 |
+
module : types.ModuleType, optional
|
84 |
+
The module to load into IPython globals (default: caller)
|
85 |
+
local_ns : dict, optional
|
86 |
+
The namespace to load into IPython user namespace (default: caller)
|
87 |
+
**kwargs : various, optional
|
88 |
+
Further keyword args are relayed to the IPKernelApp constructor,
|
89 |
+
such as `config`, a traitlets :class:`Config` object (see :ref:`configure_start_ipython`),
|
90 |
+
allowing configuration of the kernel (see :ref:`kernel_options`). Will only have an effect
|
91 |
+
on the first embed_kernel call for a given process.
|
92 |
+
"""
|
93 |
+
|
94 |
+
(caller_module, caller_locals) = extract_module_locals(1)
|
95 |
+
if module is None:
|
96 |
+
module = caller_module
|
97 |
+
if local_ns is None:
|
98 |
+
local_ns = caller_locals
|
99 |
+
|
100 |
+
# Only import .zmq when we really need it
|
101 |
+
from ipykernel.embed import embed_kernel as real_embed_kernel
|
102 |
+
real_embed_kernel(module=module, local_ns=local_ns, **kwargs)
|
103 |
+
|
104 |
+
def start_ipython(argv=None, **kwargs):
|
105 |
+
"""Launch a normal IPython instance (as opposed to embedded)
|
106 |
+
|
107 |
+
`IPython.embed()` puts a shell in a particular calling scope,
|
108 |
+
such as a function or method for debugging purposes,
|
109 |
+
which is often not desirable.
|
110 |
+
|
111 |
+
`start_ipython()` does full, regular IPython initialization,
|
112 |
+
including loading startup files, configuration, etc.
|
113 |
+
much of which is skipped by `embed()`.
|
114 |
+
|
115 |
+
This is a public API method, and will survive implementation changes.
|
116 |
+
|
117 |
+
Parameters
|
118 |
+
----------
|
119 |
+
argv : list or None, optional
|
120 |
+
If unspecified or None, IPython will parse command-line options from sys.argv.
|
121 |
+
To prevent any command-line parsing, pass an empty list: `argv=[]`.
|
122 |
+
user_ns : dict, optional
|
123 |
+
specify this dictionary to initialize the IPython user namespace with particular values.
|
124 |
+
**kwargs : various, optional
|
125 |
+
Any other kwargs will be passed to the Application constructor,
|
126 |
+
such as `config`, a traitlets :class:`Config` object (see :ref:`configure_start_ipython`),
|
127 |
+
allowing configuration of the instance (see :ref:`terminal_options`).
|
128 |
+
"""
|
129 |
+
from IPython.terminal.ipapp import launch_new_instance
|
130 |
+
return launch_new_instance(argv=argv, **kwargs)
|
131 |
+
|
132 |
+
def start_kernel(argv=None, **kwargs):
|
133 |
+
"""Launch a normal IPython kernel instance (as opposed to embedded)
|
134 |
+
|
135 |
+
`IPython.embed_kernel()` puts a shell in a particular calling scope,
|
136 |
+
such as a function or method for debugging purposes,
|
137 |
+
which is often not desirable.
|
138 |
+
|
139 |
+
`start_kernel()` does full, regular IPython initialization,
|
140 |
+
including loading startup files, configuration, etc.
|
141 |
+
much of which is skipped by `embed_kernel()`.
|
142 |
+
|
143 |
+
Parameters
|
144 |
+
----------
|
145 |
+
argv : list or None, optional
|
146 |
+
If unspecified or None, IPython will parse command-line options from sys.argv.
|
147 |
+
To prevent any command-line parsing, pass an empty list: `argv=[]`.
|
148 |
+
user_ns : dict, optional
|
149 |
+
specify this dictionary to initialize the IPython user namespace with particular values.
|
150 |
+
**kwargs : various, optional
|
151 |
+
Any other kwargs will be passed to the Application constructor,
|
152 |
+
such as `config`, a traitlets :class:`Config` object (see :ref:`configure_start_ipython`),
|
153 |
+
allowing configuration of the kernel (see :ref:`kernel_options`).
|
154 |
+
"""
|
155 |
+
import warnings
|
156 |
+
|
157 |
+
warnings.warn(
|
158 |
+
"start_kernel is deprecated since IPython 8.0, use from `ipykernel.kernelapp.launch_new_instance`",
|
159 |
+
DeprecationWarning,
|
160 |
+
stacklevel=2,
|
161 |
+
)
|
162 |
+
from ipykernel.kernelapp import launch_new_instance
|
163 |
+
return launch_new_instance(argv=argv, **kwargs)
|
openalex_env_map/lib/python3.10/site-packages/IPython/__main__.py
ADDED
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# PYTHON_ARGCOMPLETE_OK
|
2 |
+
# encoding: utf-8
|
3 |
+
"""Terminal-based IPython entry point.
|
4 |
+
"""
|
5 |
+
# -----------------------------------------------------------------------------
|
6 |
+
# Copyright (c) 2012, IPython Development Team.
|
7 |
+
#
|
8 |
+
# Distributed under the terms of the Modified BSD License.
|
9 |
+
#
|
10 |
+
# The full license is in the file COPYING.txt, distributed with this software.
|
11 |
+
# -----------------------------------------------------------------------------
|
12 |
+
|
13 |
+
from IPython import start_ipython
|
14 |
+
|
15 |
+
start_ipython()
|
openalex_env_map/lib/python3.10/site-packages/IPython/conftest.py
ADDED
@@ -0,0 +1,87 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import builtins
|
2 |
+
import inspect
|
3 |
+
import os
|
4 |
+
import pathlib
|
5 |
+
import shutil
|
6 |
+
import sys
|
7 |
+
import types
|
8 |
+
|
9 |
+
import pytest
|
10 |
+
|
11 |
+
# Must register before it gets imported
|
12 |
+
pytest.register_assert_rewrite("IPython.testing.tools")
|
13 |
+
|
14 |
+
from .testing import tools
|
15 |
+
|
16 |
+
|
17 |
+
def pytest_collection_modifyitems(items):
|
18 |
+
"""This function is automatically run by pytest passing all collected test
|
19 |
+
functions.
|
20 |
+
|
21 |
+
We use it to add asyncio marker to all async tests and assert we don't use
|
22 |
+
test functions that are async generators which wouldn't make sense.
|
23 |
+
"""
|
24 |
+
for item in items:
|
25 |
+
if inspect.iscoroutinefunction(item.obj):
|
26 |
+
item.add_marker("asyncio")
|
27 |
+
assert not inspect.isasyncgenfunction(item.obj)
|
28 |
+
|
29 |
+
|
30 |
+
def get_ipython():
|
31 |
+
from .terminal.interactiveshell import TerminalInteractiveShell
|
32 |
+
if TerminalInteractiveShell._instance:
|
33 |
+
return TerminalInteractiveShell.instance()
|
34 |
+
|
35 |
+
config = tools.default_config()
|
36 |
+
config.TerminalInteractiveShell.simple_prompt = True
|
37 |
+
|
38 |
+
# Create and initialize our test-friendly IPython instance.
|
39 |
+
shell = TerminalInteractiveShell.instance(config=config)
|
40 |
+
return shell
|
41 |
+
|
42 |
+
|
43 |
+
@pytest.fixture(scope='session', autouse=True)
|
44 |
+
def work_path():
|
45 |
+
path = pathlib.Path("./tmp-ipython-pytest-profiledir")
|
46 |
+
os.environ["IPYTHONDIR"] = str(path.absolute())
|
47 |
+
if path.exists():
|
48 |
+
raise ValueError('IPython dir temporary path already exists ! Did previous test run exit successfully ?')
|
49 |
+
path.mkdir()
|
50 |
+
yield
|
51 |
+
shutil.rmtree(str(path.resolve()))
|
52 |
+
|
53 |
+
|
54 |
+
def nopage(strng, start=0, screen_lines=0, pager_cmd=None):
|
55 |
+
if isinstance(strng, dict):
|
56 |
+
strng = strng.get("text/plain", "")
|
57 |
+
print(strng)
|
58 |
+
|
59 |
+
|
60 |
+
def xsys(self, cmd):
|
61 |
+
"""Replace the default system call with a capturing one for doctest.
|
62 |
+
"""
|
63 |
+
# We use getoutput, but we need to strip it because pexpect captures
|
64 |
+
# the trailing newline differently from commands.getoutput
|
65 |
+
print(self.getoutput(cmd, split=False, depth=1).rstrip(), end="", file=sys.stdout)
|
66 |
+
sys.stdout.flush()
|
67 |
+
|
68 |
+
|
69 |
+
# for things to work correctly we would need this as a session fixture;
|
70 |
+
# unfortunately this will fail on some test that get executed as _collection_
|
71 |
+
# time (before the fixture run), in particular parametrized test that contain
|
72 |
+
# yields. so for now execute at import time.
|
73 |
+
#@pytest.fixture(autouse=True, scope='session')
|
74 |
+
def inject():
|
75 |
+
|
76 |
+
builtins.get_ipython = get_ipython
|
77 |
+
builtins._ip = get_ipython()
|
78 |
+
builtins.ip = get_ipython()
|
79 |
+
builtins.ip.system = types.MethodType(xsys, ip)
|
80 |
+
builtins.ip.builtin_trap.activate()
|
81 |
+
from .core import page
|
82 |
+
|
83 |
+
page.pager_page = nopage
|
84 |
+
# yield
|
85 |
+
|
86 |
+
|
87 |
+
inject()
|
openalex_env_map/lib/python3.10/site-packages/IPython/consoleapp.py
ADDED
@@ -0,0 +1,12 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"""
|
2 |
+
Shim to maintain backwards compatibility with old IPython.consoleapp imports.
|
3 |
+
"""
|
4 |
+
# Copyright (c) IPython Development Team.
|
5 |
+
# Distributed under the terms of the Modified BSD License.
|
6 |
+
|
7 |
+
from warnings import warn
|
8 |
+
|
9 |
+
warn("The `IPython.consoleapp` package has been deprecated since IPython 4.0."
|
10 |
+
"You should import from jupyter_client.consoleapp instead.", stacklevel=2)
|
11 |
+
|
12 |
+
from jupyter_client.consoleapp import *
|
openalex_env_map/lib/python3.10/site-packages/IPython/core/__init__.py
ADDED
File without changes
|
openalex_env_map/lib/python3.10/site-packages/IPython/core/alias.py
ADDED
@@ -0,0 +1,267 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# encoding: utf-8
|
2 |
+
"""
|
3 |
+
System command aliases.
|
4 |
+
|
5 |
+
Authors:
|
6 |
+
|
7 |
+
* Fernando Perez
|
8 |
+
* Brian Granger
|
9 |
+
"""
|
10 |
+
|
11 |
+
#-----------------------------------------------------------------------------
|
12 |
+
# Copyright (C) 2008-2011 The IPython Development Team
|
13 |
+
#
|
14 |
+
# Distributed under the terms of the BSD License.
|
15 |
+
#
|
16 |
+
# The full license is in the file COPYING.txt, distributed with this software.
|
17 |
+
#-----------------------------------------------------------------------------
|
18 |
+
|
19 |
+
#-----------------------------------------------------------------------------
|
20 |
+
# Imports
|
21 |
+
#-----------------------------------------------------------------------------
|
22 |
+
|
23 |
+
import os
|
24 |
+
import re
|
25 |
+
import sys
|
26 |
+
|
27 |
+
from traitlets.config.configurable import Configurable
|
28 |
+
from .error import UsageError
|
29 |
+
|
30 |
+
from traitlets import List, Instance
|
31 |
+
from logging import error
|
32 |
+
|
33 |
+
import typing as t
|
34 |
+
|
35 |
+
|
36 |
+
#-----------------------------------------------------------------------------
|
37 |
+
# Utilities
|
38 |
+
#-----------------------------------------------------------------------------
|
39 |
+
|
40 |
+
# This is used as the pattern for calls to split_user_input.
|
41 |
+
shell_line_split = re.compile(r'^(\s*)()(\S+)(.*$)')
|
42 |
+
|
43 |
+
def default_aliases() -> t.List[t.Tuple[str, str]]:
|
44 |
+
"""Return list of shell aliases to auto-define.
|
45 |
+
"""
|
46 |
+
# Note: the aliases defined here should be safe to use on a kernel
|
47 |
+
# regardless of what frontend it is attached to. Frontends that use a
|
48 |
+
# kernel in-process can define additional aliases that will only work in
|
49 |
+
# their case. For example, things like 'less' or 'clear' that manipulate
|
50 |
+
# the terminal should NOT be declared here, as they will only work if the
|
51 |
+
# kernel is running inside a true terminal, and not over the network.
|
52 |
+
|
53 |
+
if os.name == 'posix':
|
54 |
+
default_aliases = [('mkdir', 'mkdir'), ('rmdir', 'rmdir'),
|
55 |
+
('mv', 'mv'), ('rm', 'rm'), ('cp', 'cp'),
|
56 |
+
('cat', 'cat'),
|
57 |
+
]
|
58 |
+
# Useful set of ls aliases. The GNU and BSD options are a little
|
59 |
+
# different, so we make aliases that provide as similar as possible
|
60 |
+
# behavior in ipython, by passing the right flags for each platform
|
61 |
+
if sys.platform.startswith('linux'):
|
62 |
+
ls_aliases = [('ls', 'ls -F --color'),
|
63 |
+
# long ls
|
64 |
+
('ll', 'ls -F -o --color'),
|
65 |
+
# ls normal files only
|
66 |
+
('lf', 'ls -F -o --color %l | grep ^-'),
|
67 |
+
# ls symbolic links
|
68 |
+
('lk', 'ls -F -o --color %l | grep ^l'),
|
69 |
+
# directories or links to directories,
|
70 |
+
('ldir', 'ls -F -o --color %l | grep /$'),
|
71 |
+
# things which are executable
|
72 |
+
('lx', 'ls -F -o --color %l | grep ^-..x'),
|
73 |
+
]
|
74 |
+
elif sys.platform.startswith('openbsd') or sys.platform.startswith('netbsd'):
|
75 |
+
# OpenBSD, NetBSD. The ls implementation on these platforms do not support
|
76 |
+
# the -G switch and lack the ability to use colorized output.
|
77 |
+
ls_aliases = [('ls', 'ls -F'),
|
78 |
+
# long ls
|
79 |
+
('ll', 'ls -F -l'),
|
80 |
+
# ls normal files only
|
81 |
+
('lf', 'ls -F -l %l | grep ^-'),
|
82 |
+
# ls symbolic links
|
83 |
+
('lk', 'ls -F -l %l | grep ^l'),
|
84 |
+
# directories or links to directories,
|
85 |
+
('ldir', 'ls -F -l %l | grep /$'),
|
86 |
+
# things which are executable
|
87 |
+
('lx', 'ls -F -l %l | grep ^-..x'),
|
88 |
+
]
|
89 |
+
else:
|
90 |
+
# BSD, OSX, etc.
|
91 |
+
ls_aliases = [('ls', 'ls -F -G'),
|
92 |
+
# long ls
|
93 |
+
('ll', 'ls -F -l -G'),
|
94 |
+
# ls normal files only
|
95 |
+
('lf', 'ls -F -l -G %l | grep ^-'),
|
96 |
+
# ls symbolic links
|
97 |
+
('lk', 'ls -F -l -G %l | grep ^l'),
|
98 |
+
# directories or links to directories,
|
99 |
+
('ldir', 'ls -F -G -l %l | grep /$'),
|
100 |
+
# things which are executable
|
101 |
+
('lx', 'ls -F -l -G %l | grep ^-..x'),
|
102 |
+
]
|
103 |
+
default_aliases = default_aliases + ls_aliases
|
104 |
+
elif os.name in ['nt', 'dos']:
|
105 |
+
default_aliases = [('ls', 'dir /on'),
|
106 |
+
('ddir', 'dir /ad /on'), ('ldir', 'dir /ad /on'),
|
107 |
+
('mkdir', 'mkdir'), ('rmdir', 'rmdir'),
|
108 |
+
('echo', 'echo'), ('ren', 'ren'), ('copy', 'copy'),
|
109 |
+
]
|
110 |
+
else:
|
111 |
+
default_aliases = []
|
112 |
+
|
113 |
+
return default_aliases
|
114 |
+
|
115 |
+
|
116 |
+
class AliasError(Exception):
|
117 |
+
pass
|
118 |
+
|
119 |
+
|
120 |
+
class InvalidAliasError(AliasError):
|
121 |
+
pass
|
122 |
+
|
123 |
+
class Alias(object):
|
124 |
+
"""Callable object storing the details of one alias.
|
125 |
+
|
126 |
+
Instances are registered as magic functions to allow use of aliases.
|
127 |
+
"""
|
128 |
+
|
129 |
+
# Prepare blacklist
|
130 |
+
blacklist = {'cd','popd','pushd','dhist','alias','unalias'}
|
131 |
+
|
132 |
+
def __init__(self, shell, name, cmd):
|
133 |
+
self.shell = shell
|
134 |
+
self.name = name
|
135 |
+
self.cmd = cmd
|
136 |
+
self.__doc__ = "Alias for `!{}`".format(cmd)
|
137 |
+
self.nargs = self.validate()
|
138 |
+
|
139 |
+
def validate(self):
|
140 |
+
"""Validate the alias, and return the number of arguments."""
|
141 |
+
if self.name in self.blacklist:
|
142 |
+
raise InvalidAliasError("The name %s can't be aliased "
|
143 |
+
"because it is a keyword or builtin." % self.name)
|
144 |
+
try:
|
145 |
+
caller = self.shell.magics_manager.magics['line'][self.name]
|
146 |
+
except KeyError:
|
147 |
+
pass
|
148 |
+
else:
|
149 |
+
if not isinstance(caller, Alias):
|
150 |
+
raise InvalidAliasError("The name %s can't be aliased "
|
151 |
+
"because it is another magic command." % self.name)
|
152 |
+
|
153 |
+
if not (isinstance(self.cmd, str)):
|
154 |
+
raise InvalidAliasError("An alias command must be a string, "
|
155 |
+
"got: %r" % self.cmd)
|
156 |
+
|
157 |
+
nargs = self.cmd.count('%s') - self.cmd.count('%%s')
|
158 |
+
|
159 |
+
if (nargs > 0) and (self.cmd.find('%l') >= 0):
|
160 |
+
raise InvalidAliasError('The %s and %l specifiers are mutually '
|
161 |
+
'exclusive in alias definitions.')
|
162 |
+
|
163 |
+
return nargs
|
164 |
+
|
165 |
+
def __repr__(self):
|
166 |
+
return "<alias {} for {!r}>".format(self.name, self.cmd)
|
167 |
+
|
168 |
+
def __call__(self, rest=''):
|
169 |
+
cmd = self.cmd
|
170 |
+
nargs = self.nargs
|
171 |
+
# Expand the %l special to be the user's input line
|
172 |
+
if cmd.find('%l') >= 0:
|
173 |
+
cmd = cmd.replace('%l', rest)
|
174 |
+
rest = ''
|
175 |
+
|
176 |
+
if nargs==0:
|
177 |
+
if cmd.find('%%s') >= 1:
|
178 |
+
cmd = cmd.replace('%%s', '%s')
|
179 |
+
# Simple, argument-less aliases
|
180 |
+
cmd = '%s %s' % (cmd, rest)
|
181 |
+
else:
|
182 |
+
# Handle aliases with positional arguments
|
183 |
+
args = rest.split(None, nargs)
|
184 |
+
if len(args) < nargs:
|
185 |
+
raise UsageError('Alias <%s> requires %s arguments, %s given.' %
|
186 |
+
(self.name, nargs, len(args)))
|
187 |
+
cmd = '%s %s' % (cmd % tuple(args[:nargs]),' '.join(args[nargs:]))
|
188 |
+
|
189 |
+
self.shell.system(cmd)
|
190 |
+
|
191 |
+
#-----------------------------------------------------------------------------
|
192 |
+
# Main AliasManager class
|
193 |
+
#-----------------------------------------------------------------------------
|
194 |
+
|
195 |
+
class AliasManager(Configurable):
|
196 |
+
default_aliases: List = List(default_aliases()).tag(config=True)
|
197 |
+
user_aliases: List = List(default_value=[]).tag(config=True)
|
198 |
+
shell = Instance(
|
199 |
+
"IPython.core.interactiveshell.InteractiveShellABC", allow_none=True
|
200 |
+
)
|
201 |
+
|
202 |
+
def __init__(self, shell=None, **kwargs):
|
203 |
+
super(AliasManager, self).__init__(shell=shell, **kwargs)
|
204 |
+
# For convenient access
|
205 |
+
if self.shell is not None:
|
206 |
+
self.linemagics = self.shell.magics_manager.magics["line"]
|
207 |
+
self.init_aliases()
|
208 |
+
|
209 |
+
def init_aliases(self):
|
210 |
+
# Load default & user aliases
|
211 |
+
for name, cmd in self.default_aliases + self.user_aliases:
|
212 |
+
if (
|
213 |
+
cmd.startswith("ls ")
|
214 |
+
and self.shell is not None
|
215 |
+
and self.shell.colors == "NoColor"
|
216 |
+
):
|
217 |
+
cmd = cmd.replace(" --color", "")
|
218 |
+
self.soft_define_alias(name, cmd)
|
219 |
+
|
220 |
+
@property
|
221 |
+
def aliases(self):
|
222 |
+
return [(n, func.cmd) for (n, func) in self.linemagics.items()
|
223 |
+
if isinstance(func, Alias)]
|
224 |
+
|
225 |
+
def soft_define_alias(self, name, cmd):
|
226 |
+
"""Define an alias, but don't raise on an AliasError."""
|
227 |
+
try:
|
228 |
+
self.define_alias(name, cmd)
|
229 |
+
except AliasError as e:
|
230 |
+
error("Invalid alias: %s" % e)
|
231 |
+
|
232 |
+
def define_alias(self, name, cmd):
|
233 |
+
"""Define a new alias after validating it.
|
234 |
+
|
235 |
+
This will raise an :exc:`AliasError` if there are validation
|
236 |
+
problems.
|
237 |
+
"""
|
238 |
+
caller = Alias(shell=self.shell, name=name, cmd=cmd)
|
239 |
+
self.shell.magics_manager.register_function(caller, magic_kind='line',
|
240 |
+
magic_name=name)
|
241 |
+
|
242 |
+
def get_alias(self, name):
|
243 |
+
"""Return an alias, or None if no alias by that name exists."""
|
244 |
+
aname = self.linemagics.get(name, None)
|
245 |
+
return aname if isinstance(aname, Alias) else None
|
246 |
+
|
247 |
+
def is_alias(self, name):
|
248 |
+
"""Return whether or not a given name has been defined as an alias"""
|
249 |
+
return self.get_alias(name) is not None
|
250 |
+
|
251 |
+
def undefine_alias(self, name):
|
252 |
+
if self.is_alias(name):
|
253 |
+
del self.linemagics[name]
|
254 |
+
else:
|
255 |
+
raise ValueError('%s is not an alias' % name)
|
256 |
+
|
257 |
+
def clear_aliases(self):
|
258 |
+
for name, _ in self.aliases:
|
259 |
+
self.undefine_alias(name)
|
260 |
+
|
261 |
+
def retrieve_alias(self, name):
|
262 |
+
"""Retrieve the command to which an alias expands."""
|
263 |
+
caller = self.get_alias(name)
|
264 |
+
if caller:
|
265 |
+
return caller.cmd
|
266 |
+
else:
|
267 |
+
raise ValueError('%s is not an alias' % name)
|
openalex_env_map/lib/python3.10/site-packages/IPython/core/application.py
ADDED
@@ -0,0 +1,492 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# encoding: utf-8
|
2 |
+
"""
|
3 |
+
An application for IPython.
|
4 |
+
|
5 |
+
All top-level applications should use the classes in this module for
|
6 |
+
handling configuration and creating configurables.
|
7 |
+
|
8 |
+
The job of an :class:`Application` is to create the master configuration
|
9 |
+
object and then create the configurable objects, passing the config to them.
|
10 |
+
"""
|
11 |
+
|
12 |
+
# Copyright (c) IPython Development Team.
|
13 |
+
# Distributed under the terms of the Modified BSD License.
|
14 |
+
|
15 |
+
import atexit
|
16 |
+
from copy import deepcopy
|
17 |
+
import logging
|
18 |
+
import os
|
19 |
+
import shutil
|
20 |
+
import sys
|
21 |
+
|
22 |
+
from pathlib import Path
|
23 |
+
|
24 |
+
from traitlets.config.application import Application, catch_config_error
|
25 |
+
from traitlets.config.loader import ConfigFileNotFound, PyFileConfigLoader
|
26 |
+
from IPython.core import release, crashhandler
|
27 |
+
from IPython.core.profiledir import ProfileDir, ProfileDirError
|
28 |
+
from IPython.paths import get_ipython_dir, get_ipython_package_dir
|
29 |
+
from IPython.utils.path import ensure_dir_exists
|
30 |
+
from traitlets import (
|
31 |
+
List, Unicode, Type, Bool, Set, Instance, Undefined,
|
32 |
+
default, observe,
|
33 |
+
)
|
34 |
+
|
35 |
+
if os.name == "nt":
|
36 |
+
programdata = os.environ.get("PROGRAMDATA", None)
|
37 |
+
if programdata is not None:
|
38 |
+
SYSTEM_CONFIG_DIRS = [str(Path(programdata) / "ipython")]
|
39 |
+
else: # PROGRAMDATA is not defined by default on XP.
|
40 |
+
SYSTEM_CONFIG_DIRS = []
|
41 |
+
else:
|
42 |
+
SYSTEM_CONFIG_DIRS = [
|
43 |
+
"/usr/local/etc/ipython",
|
44 |
+
"/etc/ipython",
|
45 |
+
]
|
46 |
+
|
47 |
+
|
48 |
+
ENV_CONFIG_DIRS = []
|
49 |
+
_env_config_dir = os.path.join(sys.prefix, 'etc', 'ipython')
|
50 |
+
if _env_config_dir not in SYSTEM_CONFIG_DIRS:
|
51 |
+
# only add ENV_CONFIG if sys.prefix is not already included
|
52 |
+
ENV_CONFIG_DIRS.append(_env_config_dir)
|
53 |
+
|
54 |
+
|
55 |
+
_envvar = os.environ.get('IPYTHON_SUPPRESS_CONFIG_ERRORS')
|
56 |
+
if _envvar in {None, ''}:
|
57 |
+
IPYTHON_SUPPRESS_CONFIG_ERRORS = None
|
58 |
+
else:
|
59 |
+
if _envvar.lower() in {'1','true'}:
|
60 |
+
IPYTHON_SUPPRESS_CONFIG_ERRORS = True
|
61 |
+
elif _envvar.lower() in {'0','false'} :
|
62 |
+
IPYTHON_SUPPRESS_CONFIG_ERRORS = False
|
63 |
+
else:
|
64 |
+
sys.exit("Unsupported value for environment variable: 'IPYTHON_SUPPRESS_CONFIG_ERRORS' is set to '%s' which is none of {'0', '1', 'false', 'true', ''}."% _envvar )
|
65 |
+
|
66 |
+
# aliases and flags
|
67 |
+
|
68 |
+
base_aliases = {}
|
69 |
+
if isinstance(Application.aliases, dict):
|
70 |
+
# traitlets 5
|
71 |
+
base_aliases.update(Application.aliases)
|
72 |
+
base_aliases.update(
|
73 |
+
{
|
74 |
+
"profile-dir": "ProfileDir.location",
|
75 |
+
"profile": "BaseIPythonApplication.profile",
|
76 |
+
"ipython-dir": "BaseIPythonApplication.ipython_dir",
|
77 |
+
"log-level": "Application.log_level",
|
78 |
+
"config": "BaseIPythonApplication.extra_config_file",
|
79 |
+
}
|
80 |
+
)
|
81 |
+
|
82 |
+
base_flags = dict()
|
83 |
+
if isinstance(Application.flags, dict):
|
84 |
+
# traitlets 5
|
85 |
+
base_flags.update(Application.flags)
|
86 |
+
base_flags.update(
|
87 |
+
dict(
|
88 |
+
debug=(
|
89 |
+
{"Application": {"log_level": logging.DEBUG}},
|
90 |
+
"set log level to logging.DEBUG (maximize logging output)",
|
91 |
+
),
|
92 |
+
quiet=(
|
93 |
+
{"Application": {"log_level": logging.CRITICAL}},
|
94 |
+
"set log level to logging.CRITICAL (minimize logging output)",
|
95 |
+
),
|
96 |
+
init=(
|
97 |
+
{
|
98 |
+
"BaseIPythonApplication": {
|
99 |
+
"copy_config_files": True,
|
100 |
+
"auto_create": True,
|
101 |
+
}
|
102 |
+
},
|
103 |
+
"""Initialize profile with default config files. This is equivalent
|
104 |
+
to running `ipython profile create <profile>` prior to startup.
|
105 |
+
""",
|
106 |
+
),
|
107 |
+
)
|
108 |
+
)
|
109 |
+
|
110 |
+
|
111 |
+
class ProfileAwareConfigLoader(PyFileConfigLoader):
|
112 |
+
"""A Python file config loader that is aware of IPython profiles."""
|
113 |
+
def load_subconfig(self, fname, path=None, profile=None):
|
114 |
+
if profile is not None:
|
115 |
+
try:
|
116 |
+
profile_dir = ProfileDir.find_profile_dir_by_name(
|
117 |
+
get_ipython_dir(),
|
118 |
+
profile,
|
119 |
+
)
|
120 |
+
except ProfileDirError:
|
121 |
+
return
|
122 |
+
path = profile_dir.location
|
123 |
+
return super(ProfileAwareConfigLoader, self).load_subconfig(fname, path=path)
|
124 |
+
|
125 |
+
class BaseIPythonApplication(Application):
|
126 |
+
name = "ipython"
|
127 |
+
description = "IPython: an enhanced interactive Python shell."
|
128 |
+
version = Unicode(release.version)
|
129 |
+
|
130 |
+
aliases = base_aliases
|
131 |
+
flags = base_flags
|
132 |
+
classes = List([ProfileDir])
|
133 |
+
|
134 |
+
# enable `load_subconfig('cfg.py', profile='name')`
|
135 |
+
python_config_loader_class = ProfileAwareConfigLoader
|
136 |
+
|
137 |
+
# Track whether the config_file has changed,
|
138 |
+
# because some logic happens only if we aren't using the default.
|
139 |
+
config_file_specified = Set()
|
140 |
+
|
141 |
+
config_file_name = Unicode()
|
142 |
+
@default('config_file_name')
|
143 |
+
def _config_file_name_default(self):
|
144 |
+
return self.name.replace('-','_') + u'_config.py'
|
145 |
+
@observe('config_file_name')
|
146 |
+
def _config_file_name_changed(self, change):
|
147 |
+
if change['new'] != change['old']:
|
148 |
+
self.config_file_specified.add(change['new'])
|
149 |
+
|
150 |
+
# The directory that contains IPython's builtin profiles.
|
151 |
+
builtin_profile_dir = Unicode(
|
152 |
+
os.path.join(get_ipython_package_dir(), u'config', u'profile', u'default')
|
153 |
+
)
|
154 |
+
|
155 |
+
config_file_paths = List(Unicode())
|
156 |
+
@default('config_file_paths')
|
157 |
+
def _config_file_paths_default(self):
|
158 |
+
return []
|
159 |
+
|
160 |
+
extra_config_file = Unicode(
|
161 |
+
help="""Path to an extra config file to load.
|
162 |
+
|
163 |
+
If specified, load this config file in addition to any other IPython config.
|
164 |
+
""").tag(config=True)
|
165 |
+
@observe('extra_config_file')
|
166 |
+
def _extra_config_file_changed(self, change):
|
167 |
+
old = change['old']
|
168 |
+
new = change['new']
|
169 |
+
try:
|
170 |
+
self.config_files.remove(old)
|
171 |
+
except ValueError:
|
172 |
+
pass
|
173 |
+
self.config_file_specified.add(new)
|
174 |
+
self.config_files.append(new)
|
175 |
+
|
176 |
+
profile = Unicode(u'default',
|
177 |
+
help="""The IPython profile to use."""
|
178 |
+
).tag(config=True)
|
179 |
+
|
180 |
+
@observe('profile')
|
181 |
+
def _profile_changed(self, change):
|
182 |
+
self.builtin_profile_dir = os.path.join(
|
183 |
+
get_ipython_package_dir(), u'config', u'profile', change['new']
|
184 |
+
)
|
185 |
+
|
186 |
+
add_ipython_dir_to_sys_path = Bool(
|
187 |
+
False,
|
188 |
+
"""Should the IPython profile directory be added to sys path ?
|
189 |
+
|
190 |
+
This option was non-existing before IPython 8.0, and ipython_dir was added to
|
191 |
+
sys path to allow import of extensions present there. This was historical
|
192 |
+
baggage from when pip did not exist. This now default to false,
|
193 |
+
but can be set to true for legacy reasons.
|
194 |
+
""",
|
195 |
+
).tag(config=True)
|
196 |
+
|
197 |
+
ipython_dir = Unicode(
|
198 |
+
help="""
|
199 |
+
The name of the IPython directory. This directory is used for logging
|
200 |
+
configuration (through profiles), history storage, etc. The default
|
201 |
+
is usually $HOME/.ipython. This option can also be specified through
|
202 |
+
the environment variable IPYTHONDIR.
|
203 |
+
"""
|
204 |
+
).tag(config=True)
|
205 |
+
@default('ipython_dir')
|
206 |
+
def _ipython_dir_default(self):
|
207 |
+
d = get_ipython_dir()
|
208 |
+
self._ipython_dir_changed({
|
209 |
+
'name': 'ipython_dir',
|
210 |
+
'old': d,
|
211 |
+
'new': d,
|
212 |
+
})
|
213 |
+
return d
|
214 |
+
|
215 |
+
_in_init_profile_dir = False
|
216 |
+
|
217 |
+
profile_dir = Instance(ProfileDir, allow_none=True)
|
218 |
+
|
219 |
+
@default('profile_dir')
|
220 |
+
def _profile_dir_default(self):
|
221 |
+
# avoid recursion
|
222 |
+
if self._in_init_profile_dir:
|
223 |
+
return
|
224 |
+
# profile_dir requested early, force initialization
|
225 |
+
self.init_profile_dir()
|
226 |
+
return self.profile_dir
|
227 |
+
|
228 |
+
overwrite = Bool(False,
|
229 |
+
help="""Whether to overwrite existing config files when copying"""
|
230 |
+
).tag(config=True)
|
231 |
+
|
232 |
+
auto_create = Bool(False,
|
233 |
+
help="""Whether to create profile dir if it doesn't exist"""
|
234 |
+
).tag(config=True)
|
235 |
+
|
236 |
+
config_files = List(Unicode())
|
237 |
+
|
238 |
+
@default('config_files')
|
239 |
+
def _config_files_default(self):
|
240 |
+
return [self.config_file_name]
|
241 |
+
|
242 |
+
copy_config_files = Bool(False,
|
243 |
+
help="""Whether to install the default config files into the profile dir.
|
244 |
+
If a new profile is being created, and IPython contains config files for that
|
245 |
+
profile, then they will be staged into the new directory. Otherwise,
|
246 |
+
default config files will be automatically generated.
|
247 |
+
""").tag(config=True)
|
248 |
+
|
249 |
+
verbose_crash = Bool(False,
|
250 |
+
help="""Create a massive crash report when IPython encounters what may be an
|
251 |
+
internal error. The default is to append a short message to the
|
252 |
+
usual traceback""").tag(config=True)
|
253 |
+
|
254 |
+
# The class to use as the crash handler.
|
255 |
+
crash_handler_class = Type(crashhandler.CrashHandler)
|
256 |
+
|
257 |
+
@catch_config_error
|
258 |
+
def __init__(self, **kwargs):
|
259 |
+
super(BaseIPythonApplication, self).__init__(**kwargs)
|
260 |
+
# ensure current working directory exists
|
261 |
+
try:
|
262 |
+
os.getcwd()
|
263 |
+
except:
|
264 |
+
# exit if cwd doesn't exist
|
265 |
+
self.log.error("Current working directory doesn't exist.")
|
266 |
+
self.exit(1)
|
267 |
+
|
268 |
+
#-------------------------------------------------------------------------
|
269 |
+
# Various stages of Application creation
|
270 |
+
#-------------------------------------------------------------------------
|
271 |
+
|
272 |
+
def init_crash_handler(self):
|
273 |
+
"""Create a crash handler, typically setting sys.excepthook to it."""
|
274 |
+
self.crash_handler = self.crash_handler_class(self)
|
275 |
+
sys.excepthook = self.excepthook
|
276 |
+
def unset_crashhandler():
|
277 |
+
sys.excepthook = sys.__excepthook__
|
278 |
+
atexit.register(unset_crashhandler)
|
279 |
+
|
280 |
+
def excepthook(self, etype, evalue, tb):
|
281 |
+
"""this is sys.excepthook after init_crashhandler
|
282 |
+
|
283 |
+
set self.verbose_crash=True to use our full crashhandler, instead of
|
284 |
+
a regular traceback with a short message (crash_handler_lite)
|
285 |
+
"""
|
286 |
+
|
287 |
+
if self.verbose_crash:
|
288 |
+
return self.crash_handler(etype, evalue, tb)
|
289 |
+
else:
|
290 |
+
return crashhandler.crash_handler_lite(etype, evalue, tb)
|
291 |
+
|
292 |
+
@observe('ipython_dir')
|
293 |
+
def _ipython_dir_changed(self, change):
|
294 |
+
old = change['old']
|
295 |
+
new = change['new']
|
296 |
+
if old is not Undefined:
|
297 |
+
str_old = os.path.abspath(old)
|
298 |
+
if str_old in sys.path:
|
299 |
+
sys.path.remove(str_old)
|
300 |
+
if self.add_ipython_dir_to_sys_path:
|
301 |
+
str_path = os.path.abspath(new)
|
302 |
+
sys.path.append(str_path)
|
303 |
+
ensure_dir_exists(new)
|
304 |
+
readme = os.path.join(new, "README")
|
305 |
+
readme_src = os.path.join(
|
306 |
+
get_ipython_package_dir(), "config", "profile", "README"
|
307 |
+
)
|
308 |
+
if not os.path.exists(readme) and os.path.exists(readme_src):
|
309 |
+
shutil.copy(readme_src, readme)
|
310 |
+
for d in ("extensions", "nbextensions"):
|
311 |
+
path = os.path.join(new, d)
|
312 |
+
try:
|
313 |
+
ensure_dir_exists(path)
|
314 |
+
except OSError as e:
|
315 |
+
# this will not be EEXIST
|
316 |
+
self.log.error("couldn't create path %s: %s", path, e)
|
317 |
+
self.log.debug("IPYTHONDIR set to: %s", new)
|
318 |
+
|
319 |
+
def load_config_file(self, suppress_errors=IPYTHON_SUPPRESS_CONFIG_ERRORS):
|
320 |
+
"""Load the config file.
|
321 |
+
|
322 |
+
By default, errors in loading config are handled, and a warning
|
323 |
+
printed on screen. For testing, the suppress_errors option is set
|
324 |
+
to False, so errors will make tests fail.
|
325 |
+
|
326 |
+
`suppress_errors` default value is to be `None` in which case the
|
327 |
+
behavior default to the one of `traitlets.Application`.
|
328 |
+
|
329 |
+
The default value can be set :
|
330 |
+
- to `False` by setting 'IPYTHON_SUPPRESS_CONFIG_ERRORS' environment variable to '0', or 'false' (case insensitive).
|
331 |
+
- to `True` by setting 'IPYTHON_SUPPRESS_CONFIG_ERRORS' environment variable to '1' or 'true' (case insensitive).
|
332 |
+
- to `None` by setting 'IPYTHON_SUPPRESS_CONFIG_ERRORS' environment variable to '' (empty string) or leaving it unset.
|
333 |
+
|
334 |
+
Any other value are invalid, and will make IPython exit with a non-zero return code.
|
335 |
+
"""
|
336 |
+
|
337 |
+
|
338 |
+
self.log.debug("Searching path %s for config files", self.config_file_paths)
|
339 |
+
base_config = 'ipython_config.py'
|
340 |
+
self.log.debug("Attempting to load config file: %s" %
|
341 |
+
base_config)
|
342 |
+
try:
|
343 |
+
if suppress_errors is not None:
|
344 |
+
old_value = Application.raise_config_file_errors
|
345 |
+
Application.raise_config_file_errors = not suppress_errors;
|
346 |
+
Application.load_config_file(
|
347 |
+
self,
|
348 |
+
base_config,
|
349 |
+
path=self.config_file_paths
|
350 |
+
)
|
351 |
+
except ConfigFileNotFound:
|
352 |
+
# ignore errors loading parent
|
353 |
+
self.log.debug("Config file %s not found", base_config)
|
354 |
+
pass
|
355 |
+
if suppress_errors is not None:
|
356 |
+
Application.raise_config_file_errors = old_value
|
357 |
+
|
358 |
+
for config_file_name in self.config_files:
|
359 |
+
if not config_file_name or config_file_name == base_config:
|
360 |
+
continue
|
361 |
+
self.log.debug("Attempting to load config file: %s" %
|
362 |
+
self.config_file_name)
|
363 |
+
try:
|
364 |
+
Application.load_config_file(
|
365 |
+
self,
|
366 |
+
config_file_name,
|
367 |
+
path=self.config_file_paths
|
368 |
+
)
|
369 |
+
except ConfigFileNotFound:
|
370 |
+
# Only warn if the default config file was NOT being used.
|
371 |
+
if config_file_name in self.config_file_specified:
|
372 |
+
msg = self.log.warning
|
373 |
+
else:
|
374 |
+
msg = self.log.debug
|
375 |
+
msg("Config file not found, skipping: %s", config_file_name)
|
376 |
+
except Exception:
|
377 |
+
# For testing purposes.
|
378 |
+
if not suppress_errors:
|
379 |
+
raise
|
380 |
+
self.log.warning("Error loading config file: %s" %
|
381 |
+
self.config_file_name, exc_info=True)
|
382 |
+
|
383 |
+
def init_profile_dir(self):
|
384 |
+
"""initialize the profile dir"""
|
385 |
+
self._in_init_profile_dir = True
|
386 |
+
if self.profile_dir is not None:
|
387 |
+
# already ran
|
388 |
+
return
|
389 |
+
if 'ProfileDir.location' not in self.config:
|
390 |
+
# location not specified, find by profile name
|
391 |
+
try:
|
392 |
+
p = ProfileDir.find_profile_dir_by_name(self.ipython_dir, self.profile, self.config)
|
393 |
+
except ProfileDirError:
|
394 |
+
# not found, maybe create it (always create default profile)
|
395 |
+
if self.auto_create or self.profile == 'default':
|
396 |
+
try:
|
397 |
+
p = ProfileDir.create_profile_dir_by_name(self.ipython_dir, self.profile, self.config)
|
398 |
+
except ProfileDirError:
|
399 |
+
self.log.fatal("Could not create profile: %r"%self.profile)
|
400 |
+
self.exit(1)
|
401 |
+
else:
|
402 |
+
self.log.info("Created profile dir: %r"%p.location)
|
403 |
+
else:
|
404 |
+
self.log.fatal("Profile %r not found."%self.profile)
|
405 |
+
self.exit(1)
|
406 |
+
else:
|
407 |
+
self.log.debug("Using existing profile dir: %r", p.location)
|
408 |
+
else:
|
409 |
+
location = self.config.ProfileDir.location
|
410 |
+
# location is fully specified
|
411 |
+
try:
|
412 |
+
p = ProfileDir.find_profile_dir(location, self.config)
|
413 |
+
except ProfileDirError:
|
414 |
+
# not found, maybe create it
|
415 |
+
if self.auto_create:
|
416 |
+
try:
|
417 |
+
p = ProfileDir.create_profile_dir(location, self.config)
|
418 |
+
except ProfileDirError:
|
419 |
+
self.log.fatal("Could not create profile directory: %r"%location)
|
420 |
+
self.exit(1)
|
421 |
+
else:
|
422 |
+
self.log.debug("Creating new profile dir: %r"%location)
|
423 |
+
else:
|
424 |
+
self.log.fatal("Profile directory %r not found."%location)
|
425 |
+
self.exit(1)
|
426 |
+
else:
|
427 |
+
self.log.debug("Using existing profile dir: %r", p.location)
|
428 |
+
# if profile_dir is specified explicitly, set profile name
|
429 |
+
dir_name = os.path.basename(p.location)
|
430 |
+
if dir_name.startswith('profile_'):
|
431 |
+
self.profile = dir_name[8:]
|
432 |
+
|
433 |
+
self.profile_dir = p
|
434 |
+
self.config_file_paths.append(p.location)
|
435 |
+
self._in_init_profile_dir = False
|
436 |
+
|
437 |
+
def init_config_files(self):
|
438 |
+
"""[optionally] copy default config files into profile dir."""
|
439 |
+
self.config_file_paths.extend(ENV_CONFIG_DIRS)
|
440 |
+
self.config_file_paths.extend(SYSTEM_CONFIG_DIRS)
|
441 |
+
# copy config files
|
442 |
+
path = Path(self.builtin_profile_dir)
|
443 |
+
if self.copy_config_files:
|
444 |
+
src = self.profile
|
445 |
+
|
446 |
+
cfg = self.config_file_name
|
447 |
+
if path and (path / cfg).exists():
|
448 |
+
self.log.warning(
|
449 |
+
"Staging %r from %s into %r [overwrite=%s]"
|
450 |
+
% (cfg, src, self.profile_dir.location, self.overwrite)
|
451 |
+
)
|
452 |
+
self.profile_dir.copy_config_file(cfg, path=path, overwrite=self.overwrite)
|
453 |
+
else:
|
454 |
+
self.stage_default_config_file()
|
455 |
+
else:
|
456 |
+
# Still stage *bundled* config files, but not generated ones
|
457 |
+
# This is necessary for `ipython profile=sympy` to load the profile
|
458 |
+
# on the first go
|
459 |
+
files = path.glob("*.py")
|
460 |
+
for fullpath in files:
|
461 |
+
cfg = fullpath.name
|
462 |
+
if self.profile_dir.copy_config_file(cfg, path=path, overwrite=False):
|
463 |
+
# file was copied
|
464 |
+
self.log.warning("Staging bundled %s from %s into %r"%(
|
465 |
+
cfg, self.profile, self.profile_dir.location)
|
466 |
+
)
|
467 |
+
|
468 |
+
|
469 |
+
def stage_default_config_file(self):
|
470 |
+
"""auto generate default config file, and stage it into the profile."""
|
471 |
+
s = self.generate_config_file()
|
472 |
+
config_file = Path(self.profile_dir.location) / self.config_file_name
|
473 |
+
if self.overwrite or not config_file.exists():
|
474 |
+
self.log.warning("Generating default config file: %r", (config_file))
|
475 |
+
config_file.write_text(s, encoding="utf-8")
|
476 |
+
|
477 |
+
@catch_config_error
|
478 |
+
def initialize(self, argv=None):
|
479 |
+
# don't hook up crash handler before parsing command-line
|
480 |
+
self.parse_command_line(argv)
|
481 |
+
self.init_crash_handler()
|
482 |
+
if self.subapp is not None:
|
483 |
+
# stop here if subapp is taking over
|
484 |
+
return
|
485 |
+
# save a copy of CLI config to re-load after config files
|
486 |
+
# so that it has highest priority
|
487 |
+
cl_config = deepcopy(self.config)
|
488 |
+
self.init_profile_dir()
|
489 |
+
self.init_config_files()
|
490 |
+
self.load_config_file()
|
491 |
+
# enforce cl-opts override configfile opts:
|
492 |
+
self.update_config(cl_config)
|
openalex_env_map/lib/python3.10/site-packages/IPython/core/async_helpers.py
ADDED
@@ -0,0 +1,155 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"""
|
2 |
+
Async helper function that are invalid syntax on Python 3.5 and below.
|
3 |
+
|
4 |
+
This code is best effort, and may have edge cases not behaving as expected. In
|
5 |
+
particular it contain a number of heuristics to detect whether code is
|
6 |
+
effectively async and need to run in an event loop or not.
|
7 |
+
|
8 |
+
Some constructs (like top-level `return`, or `yield`) are taken care of
|
9 |
+
explicitly to actually raise a SyntaxError and stay as close as possible to
|
10 |
+
Python semantics.
|
11 |
+
"""
|
12 |
+
|
13 |
+
import ast
|
14 |
+
import asyncio
|
15 |
+
import inspect
|
16 |
+
from functools import wraps
|
17 |
+
|
18 |
+
_asyncio_event_loop = None
|
19 |
+
|
20 |
+
|
21 |
+
def get_asyncio_loop():
|
22 |
+
"""asyncio has deprecated get_event_loop
|
23 |
+
|
24 |
+
Replicate it here, with our desired semantics:
|
25 |
+
|
26 |
+
- always returns a valid, not-closed loop
|
27 |
+
- not thread-local like asyncio's,
|
28 |
+
because we only want one loop for IPython
|
29 |
+
- if called from inside a coroutine (e.g. in ipykernel),
|
30 |
+
return the running loop
|
31 |
+
|
32 |
+
.. versionadded:: 8.0
|
33 |
+
"""
|
34 |
+
try:
|
35 |
+
return asyncio.get_running_loop()
|
36 |
+
except RuntimeError:
|
37 |
+
# not inside a coroutine,
|
38 |
+
# track our own global
|
39 |
+
pass
|
40 |
+
|
41 |
+
# not thread-local like asyncio's,
|
42 |
+
# because we only track one event loop to run for IPython itself,
|
43 |
+
# always in the main thread.
|
44 |
+
global _asyncio_event_loop
|
45 |
+
if _asyncio_event_loop is None or _asyncio_event_loop.is_closed():
|
46 |
+
_asyncio_event_loop = asyncio.new_event_loop()
|
47 |
+
return _asyncio_event_loop
|
48 |
+
|
49 |
+
|
50 |
+
class _AsyncIORunner:
|
51 |
+
def __call__(self, coro):
|
52 |
+
"""
|
53 |
+
Handler for asyncio autoawait
|
54 |
+
"""
|
55 |
+
return get_asyncio_loop().run_until_complete(coro)
|
56 |
+
|
57 |
+
def __str__(self):
|
58 |
+
return "asyncio"
|
59 |
+
|
60 |
+
|
61 |
+
_asyncio_runner = _AsyncIORunner()
|
62 |
+
|
63 |
+
|
64 |
+
class _AsyncIOProxy:
|
65 |
+
"""Proxy-object for an asyncio
|
66 |
+
|
67 |
+
Any coroutine methods will be wrapped in event_loop.run_
|
68 |
+
"""
|
69 |
+
|
70 |
+
def __init__(self, obj, event_loop):
|
71 |
+
self._obj = obj
|
72 |
+
self._event_loop = event_loop
|
73 |
+
|
74 |
+
def __repr__(self):
|
75 |
+
return f"<_AsyncIOProxy({self._obj!r})>"
|
76 |
+
|
77 |
+
def __getattr__(self, key):
|
78 |
+
attr = getattr(self._obj, key)
|
79 |
+
if inspect.iscoroutinefunction(attr):
|
80 |
+
# if it's a coroutine method,
|
81 |
+
# return a threadsafe wrapper onto the _current_ asyncio loop
|
82 |
+
@wraps(attr)
|
83 |
+
def _wrapped(*args, **kwargs):
|
84 |
+
concurrent_future = asyncio.run_coroutine_threadsafe(
|
85 |
+
attr(*args, **kwargs), self._event_loop
|
86 |
+
)
|
87 |
+
return asyncio.wrap_future(concurrent_future)
|
88 |
+
|
89 |
+
return _wrapped
|
90 |
+
else:
|
91 |
+
return attr
|
92 |
+
|
93 |
+
def __dir__(self):
|
94 |
+
return dir(self._obj)
|
95 |
+
|
96 |
+
|
97 |
+
def _curio_runner(coroutine):
|
98 |
+
"""
|
99 |
+
handler for curio autoawait
|
100 |
+
"""
|
101 |
+
import curio
|
102 |
+
|
103 |
+
return curio.run(coroutine)
|
104 |
+
|
105 |
+
|
106 |
+
def _trio_runner(async_fn):
|
107 |
+
import trio
|
108 |
+
|
109 |
+
async def loc(coro):
|
110 |
+
"""
|
111 |
+
We need the dummy no-op async def to protect from
|
112 |
+
trio's internal. See https://github.com/python-trio/trio/issues/89
|
113 |
+
"""
|
114 |
+
return await coro
|
115 |
+
|
116 |
+
return trio.run(loc, async_fn)
|
117 |
+
|
118 |
+
|
119 |
+
def _pseudo_sync_runner(coro):
|
120 |
+
"""
|
121 |
+
A runner that does not really allow async execution, and just advance the coroutine.
|
122 |
+
|
123 |
+
See discussion in https://github.com/python-trio/trio/issues/608,
|
124 |
+
|
125 |
+
Credit to Nathaniel Smith
|
126 |
+
"""
|
127 |
+
try:
|
128 |
+
coro.send(None)
|
129 |
+
except StopIteration as exc:
|
130 |
+
return exc.value
|
131 |
+
else:
|
132 |
+
# TODO: do not raise but return an execution result with the right info.
|
133 |
+
raise RuntimeError(
|
134 |
+
"{coro_name!r} needs a real async loop".format(coro_name=coro.__name__)
|
135 |
+
)
|
136 |
+
|
137 |
+
|
138 |
+
def _should_be_async(cell: str) -> bool:
|
139 |
+
"""Detect if a block of code need to be wrapped in an `async def`
|
140 |
+
|
141 |
+
Attempt to parse the block of code, it it compile we're fine.
|
142 |
+
Otherwise we wrap if and try to compile.
|
143 |
+
|
144 |
+
If it works, assume it should be async. Otherwise Return False.
|
145 |
+
|
146 |
+
Not handled yet: If the block of code has a return statement as the top
|
147 |
+
level, it will be seen as async. This is a know limitation.
|
148 |
+
"""
|
149 |
+
try:
|
150 |
+
code = compile(
|
151 |
+
cell, "<>", "exec", flags=getattr(ast, "PyCF_ALLOW_TOP_LEVEL_AWAIT", 0x0)
|
152 |
+
)
|
153 |
+
return inspect.CO_COROUTINE & code.co_flags == inspect.CO_COROUTINE
|
154 |
+
except (SyntaxError, MemoryError):
|
155 |
+
return False
|
openalex_env_map/lib/python3.10/site-packages/IPython/core/autocall.py
ADDED
@@ -0,0 +1,70 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# encoding: utf-8
|
2 |
+
"""
|
3 |
+
Autocall capabilities for IPython.core.
|
4 |
+
|
5 |
+
Authors:
|
6 |
+
|
7 |
+
* Brian Granger
|
8 |
+
* Fernando Perez
|
9 |
+
* Thomas Kluyver
|
10 |
+
|
11 |
+
Notes
|
12 |
+
-----
|
13 |
+
"""
|
14 |
+
|
15 |
+
#-----------------------------------------------------------------------------
|
16 |
+
# Copyright (C) 2008-2011 The IPython Development Team
|
17 |
+
#
|
18 |
+
# Distributed under the terms of the BSD License. The full license is in
|
19 |
+
# the file COPYING, distributed as part of this software.
|
20 |
+
#-----------------------------------------------------------------------------
|
21 |
+
|
22 |
+
#-----------------------------------------------------------------------------
|
23 |
+
# Imports
|
24 |
+
#-----------------------------------------------------------------------------
|
25 |
+
|
26 |
+
|
27 |
+
#-----------------------------------------------------------------------------
|
28 |
+
# Code
|
29 |
+
#-----------------------------------------------------------------------------
|
30 |
+
|
31 |
+
class IPyAutocall(object):
|
32 |
+
""" Instances of this class are always autocalled
|
33 |
+
|
34 |
+
This happens regardless of 'autocall' variable state. Use this to
|
35 |
+
develop macro-like mechanisms.
|
36 |
+
"""
|
37 |
+
_ip = None
|
38 |
+
rewrite = True
|
39 |
+
def __init__(self, ip=None):
|
40 |
+
self._ip = ip
|
41 |
+
|
42 |
+
def set_ip(self, ip):
|
43 |
+
"""Will be used to set _ip point to current ipython instance b/f call
|
44 |
+
|
45 |
+
Override this method if you don't want this to happen.
|
46 |
+
|
47 |
+
"""
|
48 |
+
self._ip = ip
|
49 |
+
|
50 |
+
|
51 |
+
class ExitAutocall(IPyAutocall):
|
52 |
+
"""An autocallable object which will be added to the user namespace so that
|
53 |
+
exit, exit(), quit or quit() are all valid ways to close the shell."""
|
54 |
+
rewrite = False
|
55 |
+
|
56 |
+
def __call__(self):
|
57 |
+
self._ip.ask_exit()
|
58 |
+
|
59 |
+
class ZMQExitAutocall(ExitAutocall):
|
60 |
+
"""Exit IPython. Autocallable, so it needn't be explicitly called.
|
61 |
+
|
62 |
+
Parameters
|
63 |
+
----------
|
64 |
+
keep_kernel : bool
|
65 |
+
If True, leave the kernel alive. Otherwise, tell the kernel to exit too
|
66 |
+
(default).
|
67 |
+
"""
|
68 |
+
def __call__(self, keep_kernel=False):
|
69 |
+
self._ip.keepkernel_on_exit = keep_kernel
|
70 |
+
self._ip.ask_exit()
|
openalex_env_map/lib/python3.10/site-packages/IPython/core/builtin_trap.py
ADDED
@@ -0,0 +1,86 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"""
|
2 |
+
A context manager for managing things injected into :mod:`builtins`.
|
3 |
+
"""
|
4 |
+
# Copyright (c) IPython Development Team.
|
5 |
+
# Distributed under the terms of the Modified BSD License.
|
6 |
+
import builtins as builtin_mod
|
7 |
+
|
8 |
+
from traitlets.config.configurable import Configurable
|
9 |
+
|
10 |
+
from traitlets import Instance
|
11 |
+
|
12 |
+
|
13 |
+
class __BuiltinUndefined(object): pass
|
14 |
+
BuiltinUndefined = __BuiltinUndefined()
|
15 |
+
|
16 |
+
class __HideBuiltin(object): pass
|
17 |
+
HideBuiltin = __HideBuiltin()
|
18 |
+
|
19 |
+
|
20 |
+
class BuiltinTrap(Configurable):
|
21 |
+
|
22 |
+
shell = Instance('IPython.core.interactiveshell.InteractiveShellABC',
|
23 |
+
allow_none=True)
|
24 |
+
|
25 |
+
def __init__(self, shell=None):
|
26 |
+
super(BuiltinTrap, self).__init__(shell=shell, config=None)
|
27 |
+
self._orig_builtins = {}
|
28 |
+
# We define this to track if a single BuiltinTrap is nested.
|
29 |
+
# Only turn off the trap when the outermost call to __exit__ is made.
|
30 |
+
self._nested_level = 0
|
31 |
+
self.shell = shell
|
32 |
+
# builtins we always add - if set to HideBuiltin, they will just
|
33 |
+
# be removed instead of being replaced by something else
|
34 |
+
self.auto_builtins = {'exit': HideBuiltin,
|
35 |
+
'quit': HideBuiltin,
|
36 |
+
'get_ipython': self.shell.get_ipython,
|
37 |
+
}
|
38 |
+
|
39 |
+
def __enter__(self):
|
40 |
+
if self._nested_level == 0:
|
41 |
+
self.activate()
|
42 |
+
self._nested_level += 1
|
43 |
+
# I return self, so callers can use add_builtin in a with clause.
|
44 |
+
return self
|
45 |
+
|
46 |
+
def __exit__(self, type, value, traceback):
|
47 |
+
if self._nested_level == 1:
|
48 |
+
self.deactivate()
|
49 |
+
self._nested_level -= 1
|
50 |
+
# Returning False will cause exceptions to propagate
|
51 |
+
return False
|
52 |
+
|
53 |
+
def add_builtin(self, key, value):
|
54 |
+
"""Add a builtin and save the original."""
|
55 |
+
bdict = builtin_mod.__dict__
|
56 |
+
orig = bdict.get(key, BuiltinUndefined)
|
57 |
+
if value is HideBuiltin:
|
58 |
+
if orig is not BuiltinUndefined: #same as 'key in bdict'
|
59 |
+
self._orig_builtins[key] = orig
|
60 |
+
del bdict[key]
|
61 |
+
else:
|
62 |
+
self._orig_builtins[key] = orig
|
63 |
+
bdict[key] = value
|
64 |
+
|
65 |
+
def remove_builtin(self, key, orig):
|
66 |
+
"""Remove an added builtin and re-set the original."""
|
67 |
+
if orig is BuiltinUndefined:
|
68 |
+
del builtin_mod.__dict__[key]
|
69 |
+
else:
|
70 |
+
builtin_mod.__dict__[key] = orig
|
71 |
+
|
72 |
+
def activate(self):
|
73 |
+
"""Store ipython references in the __builtin__ namespace."""
|
74 |
+
|
75 |
+
add_builtin = self.add_builtin
|
76 |
+
for name, func in self.auto_builtins.items():
|
77 |
+
add_builtin(name, func)
|
78 |
+
|
79 |
+
def deactivate(self):
|
80 |
+
"""Remove any builtins which might have been added by add_builtins, or
|
81 |
+
restore overwritten ones to their previous values."""
|
82 |
+
remove_builtin = self.remove_builtin
|
83 |
+
for key, val in self._orig_builtins.items():
|
84 |
+
remove_builtin(key, val)
|
85 |
+
self._orig_builtins.clear()
|
86 |
+
self._builtins_added = False
|
openalex_env_map/lib/python3.10/site-packages/IPython/core/compilerop.py
ADDED
@@ -0,0 +1,214 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"""Compiler tools with improved interactive support.
|
2 |
+
|
3 |
+
Provides compilation machinery similar to codeop, but with caching support so
|
4 |
+
we can provide interactive tracebacks.
|
5 |
+
|
6 |
+
Authors
|
7 |
+
-------
|
8 |
+
* Robert Kern
|
9 |
+
* Fernando Perez
|
10 |
+
* Thomas Kluyver
|
11 |
+
"""
|
12 |
+
|
13 |
+
# Note: though it might be more natural to name this module 'compiler', that
|
14 |
+
# name is in the stdlib and name collisions with the stdlib tend to produce
|
15 |
+
# weird problems (often with third-party tools).
|
16 |
+
|
17 |
+
#-----------------------------------------------------------------------------
|
18 |
+
# Copyright (C) 2010-2011 The IPython Development Team.
|
19 |
+
#
|
20 |
+
# Distributed under the terms of the BSD License.
|
21 |
+
#
|
22 |
+
# The full license is in the file COPYING.txt, distributed with this software.
|
23 |
+
#-----------------------------------------------------------------------------
|
24 |
+
|
25 |
+
#-----------------------------------------------------------------------------
|
26 |
+
# Imports
|
27 |
+
#-----------------------------------------------------------------------------
|
28 |
+
|
29 |
+
# Stdlib imports
|
30 |
+
import __future__
|
31 |
+
from ast import PyCF_ONLY_AST
|
32 |
+
import codeop
|
33 |
+
import functools
|
34 |
+
import hashlib
|
35 |
+
import linecache
|
36 |
+
import operator
|
37 |
+
import time
|
38 |
+
from contextlib import contextmanager
|
39 |
+
|
40 |
+
#-----------------------------------------------------------------------------
|
41 |
+
# Constants
|
42 |
+
#-----------------------------------------------------------------------------
|
43 |
+
|
44 |
+
# Roughly equal to PyCF_MASK | PyCF_MASK_OBSOLETE as defined in pythonrun.h,
|
45 |
+
# this is used as a bitmask to extract future-related code flags.
|
46 |
+
PyCF_MASK = functools.reduce(operator.or_,
|
47 |
+
(getattr(__future__, fname).compiler_flag
|
48 |
+
for fname in __future__.all_feature_names))
|
49 |
+
|
50 |
+
#-----------------------------------------------------------------------------
|
51 |
+
# Local utilities
|
52 |
+
#-----------------------------------------------------------------------------
|
53 |
+
|
54 |
+
def code_name(code, number=0):
|
55 |
+
""" Compute a (probably) unique name for code for caching.
|
56 |
+
|
57 |
+
This now expects code to be unicode.
|
58 |
+
"""
|
59 |
+
hash_digest = hashlib.sha1(code.encode("utf-8")).hexdigest()
|
60 |
+
# Include the number and 12 characters of the hash in the name. It's
|
61 |
+
# pretty much impossible that in a single session we'll have collisions
|
62 |
+
# even with truncated hashes, and the full one makes tracebacks too long
|
63 |
+
return '<ipython-input-{0}-{1}>'.format(number, hash_digest[:12])
|
64 |
+
|
65 |
+
#-----------------------------------------------------------------------------
|
66 |
+
# Classes and functions
|
67 |
+
#-----------------------------------------------------------------------------
|
68 |
+
|
69 |
+
class CachingCompiler(codeop.Compile):
|
70 |
+
"""A compiler that caches code compiled from interactive statements.
|
71 |
+
"""
|
72 |
+
|
73 |
+
def __init__(self):
|
74 |
+
codeop.Compile.__init__(self)
|
75 |
+
|
76 |
+
# Caching a dictionary { filename: execution_count } for nicely
|
77 |
+
# rendered tracebacks. The filename corresponds to the filename
|
78 |
+
# argument used for the builtins.compile function.
|
79 |
+
self._filename_map = {}
|
80 |
+
|
81 |
+
def ast_parse(self, source, filename='<unknown>', symbol='exec'):
|
82 |
+
"""Parse code to an AST with the current compiler flags active.
|
83 |
+
|
84 |
+
Arguments are exactly the same as ast.parse (in the standard library),
|
85 |
+
and are passed to the built-in compile function."""
|
86 |
+
return compile(source, filename, symbol, self.flags | PyCF_ONLY_AST, 1)
|
87 |
+
|
88 |
+
def reset_compiler_flags(self):
|
89 |
+
"""Reset compiler flags to default state."""
|
90 |
+
# This value is copied from codeop.Compile.__init__, so if that ever
|
91 |
+
# changes, it will need to be updated.
|
92 |
+
self.flags = codeop.PyCF_DONT_IMPLY_DEDENT
|
93 |
+
|
94 |
+
@property
|
95 |
+
def compiler_flags(self):
|
96 |
+
"""Flags currently active in the compilation process.
|
97 |
+
"""
|
98 |
+
return self.flags
|
99 |
+
|
100 |
+
def get_code_name(self, raw_code, transformed_code, number):
|
101 |
+
"""Compute filename given the code, and the cell number.
|
102 |
+
|
103 |
+
Parameters
|
104 |
+
----------
|
105 |
+
raw_code : str
|
106 |
+
The raw cell code.
|
107 |
+
transformed_code : str
|
108 |
+
The executable Python source code to cache and compile.
|
109 |
+
number : int
|
110 |
+
A number which forms part of the code's name. Used for the execution
|
111 |
+
counter.
|
112 |
+
|
113 |
+
Returns
|
114 |
+
-------
|
115 |
+
The computed filename.
|
116 |
+
"""
|
117 |
+
return code_name(transformed_code, number)
|
118 |
+
|
119 |
+
def format_code_name(self, name):
|
120 |
+
"""Return a user-friendly label and name for a code block.
|
121 |
+
|
122 |
+
Parameters
|
123 |
+
----------
|
124 |
+
name : str
|
125 |
+
The name for the code block returned from get_code_name
|
126 |
+
|
127 |
+
Returns
|
128 |
+
-------
|
129 |
+
A (label, name) pair that can be used in tracebacks, or None if the default formatting should be used.
|
130 |
+
"""
|
131 |
+
if name in self._filename_map:
|
132 |
+
return "Cell", "In[%s]" % self._filename_map[name]
|
133 |
+
|
134 |
+
def cache(self, transformed_code, number=0, raw_code=None):
|
135 |
+
"""Make a name for a block of code, and cache the code.
|
136 |
+
|
137 |
+
Parameters
|
138 |
+
----------
|
139 |
+
transformed_code : str
|
140 |
+
The executable Python source code to cache and compile.
|
141 |
+
number : int
|
142 |
+
A number which forms part of the code's name. Used for the execution
|
143 |
+
counter.
|
144 |
+
raw_code : str
|
145 |
+
The raw code before transformation, if None, set to `transformed_code`.
|
146 |
+
|
147 |
+
Returns
|
148 |
+
-------
|
149 |
+
The name of the cached code (as a string). Pass this as the filename
|
150 |
+
argument to compilation, so that tracebacks are correctly hooked up.
|
151 |
+
"""
|
152 |
+
if raw_code is None:
|
153 |
+
raw_code = transformed_code
|
154 |
+
|
155 |
+
name = self.get_code_name(raw_code, transformed_code, number)
|
156 |
+
|
157 |
+
# Save the execution count
|
158 |
+
self._filename_map[name] = number
|
159 |
+
|
160 |
+
# Since Python 2.5, setting mtime to `None` means the lines will
|
161 |
+
# never be removed by `linecache.checkcache`. This means all the
|
162 |
+
# monkeypatching has *never* been necessary, since this code was
|
163 |
+
# only added in 2010, at which point IPython had already stopped
|
164 |
+
# supporting Python 2.4.
|
165 |
+
#
|
166 |
+
# Note that `linecache.clearcache` and `linecache.updatecache` may
|
167 |
+
# still remove our code from the cache, but those show explicit
|
168 |
+
# intent, and we should not try to interfere. Normally the former
|
169 |
+
# is never called except when out of memory, and the latter is only
|
170 |
+
# called for lines *not* in the cache.
|
171 |
+
entry = (
|
172 |
+
len(transformed_code),
|
173 |
+
None,
|
174 |
+
[line + "\n" for line in transformed_code.splitlines()],
|
175 |
+
name,
|
176 |
+
)
|
177 |
+
linecache.cache[name] = entry
|
178 |
+
return name
|
179 |
+
|
180 |
+
@contextmanager
|
181 |
+
def extra_flags(self, flags):
|
182 |
+
## bits that we'll set to 1
|
183 |
+
turn_on_bits = ~self.flags & flags
|
184 |
+
|
185 |
+
|
186 |
+
self.flags = self.flags | flags
|
187 |
+
try:
|
188 |
+
yield
|
189 |
+
finally:
|
190 |
+
# turn off only the bits we turned on so that something like
|
191 |
+
# __future__ that set flags stays.
|
192 |
+
self.flags &= ~turn_on_bits
|
193 |
+
|
194 |
+
|
195 |
+
def check_linecache_ipython(*args):
|
196 |
+
"""Deprecated since IPython 8.6. Call linecache.checkcache() directly.
|
197 |
+
|
198 |
+
It was already not necessary to call this function directly. If no
|
199 |
+
CachingCompiler had been created, this function would fail badly. If
|
200 |
+
an instance had been created, this function would've been monkeypatched
|
201 |
+
into place.
|
202 |
+
|
203 |
+
As of IPython 8.6, the monkeypatching has gone away entirely. But there
|
204 |
+
were still internal callers of this function, so maybe external callers
|
205 |
+
also existed?
|
206 |
+
"""
|
207 |
+
import warnings
|
208 |
+
|
209 |
+
warnings.warn(
|
210 |
+
"Deprecated Since IPython 8.6, Just call linecache.checkcache() directly.",
|
211 |
+
DeprecationWarning,
|
212 |
+
stacklevel=2,
|
213 |
+
)
|
214 |
+
linecache.checkcache()
|
openalex_env_map/lib/python3.10/site-packages/IPython/core/completer.py
ADDED
The diff for this file is too large to render.
See raw diff
|
|
openalex_env_map/lib/python3.10/site-packages/IPython/core/completerlib.py
ADDED
@@ -0,0 +1,382 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# encoding: utf-8
|
2 |
+
"""Implementations for various useful completers.
|
3 |
+
|
4 |
+
These are all loaded by default by IPython.
|
5 |
+
"""
|
6 |
+
#-----------------------------------------------------------------------------
|
7 |
+
# Copyright (C) 2010-2011 The IPython Development Team.
|
8 |
+
#
|
9 |
+
# Distributed under the terms of the BSD License.
|
10 |
+
#
|
11 |
+
# The full license is in the file COPYING.txt, distributed with this software.
|
12 |
+
#-----------------------------------------------------------------------------
|
13 |
+
|
14 |
+
#-----------------------------------------------------------------------------
|
15 |
+
# Imports
|
16 |
+
#-----------------------------------------------------------------------------
|
17 |
+
|
18 |
+
# Stdlib imports
|
19 |
+
import glob
|
20 |
+
import inspect
|
21 |
+
import os
|
22 |
+
import re
|
23 |
+
import sys
|
24 |
+
from importlib import import_module
|
25 |
+
from importlib.machinery import all_suffixes
|
26 |
+
|
27 |
+
|
28 |
+
# Third-party imports
|
29 |
+
from time import time
|
30 |
+
from zipimport import zipimporter
|
31 |
+
|
32 |
+
# Our own imports
|
33 |
+
from .completer import expand_user, compress_user
|
34 |
+
from .error import TryNext
|
35 |
+
from ..utils._process_common import arg_split
|
36 |
+
|
37 |
+
# FIXME: this should be pulled in with the right call via the component system
|
38 |
+
from IPython import get_ipython
|
39 |
+
|
40 |
+
from typing import List
|
41 |
+
|
42 |
+
#-----------------------------------------------------------------------------
|
43 |
+
# Globals and constants
|
44 |
+
#-----------------------------------------------------------------------------
|
45 |
+
_suffixes = all_suffixes()
|
46 |
+
|
47 |
+
# Time in seconds after which the rootmodules will be stored permanently in the
|
48 |
+
# ipython ip.db database (kept in the user's .ipython dir).
|
49 |
+
TIMEOUT_STORAGE = 2
|
50 |
+
|
51 |
+
# Time in seconds after which we give up
|
52 |
+
TIMEOUT_GIVEUP = 20
|
53 |
+
|
54 |
+
# Regular expression for the python import statement
|
55 |
+
import_re = re.compile(r'(?P<name>[^\W\d]\w*?)'
|
56 |
+
r'(?P<package>[/\\]__init__)?'
|
57 |
+
r'(?P<suffix>%s)$' %
|
58 |
+
r'|'.join(re.escape(s) for s in _suffixes))
|
59 |
+
|
60 |
+
# RE for the ipython %run command (python + ipython scripts)
|
61 |
+
magic_run_re = re.compile(r'.*(\.ipy|\.ipynb|\.py[w]?)$')
|
62 |
+
|
63 |
+
#-----------------------------------------------------------------------------
|
64 |
+
# Local utilities
|
65 |
+
#-----------------------------------------------------------------------------
|
66 |
+
|
67 |
+
|
68 |
+
def module_list(path: str) -> List[str]:
|
69 |
+
"""
|
70 |
+
Return the list containing the names of the modules available in the given
|
71 |
+
folder.
|
72 |
+
"""
|
73 |
+
# sys.path has the cwd as an empty string, but isdir/listdir need it as '.'
|
74 |
+
if path == '':
|
75 |
+
path = '.'
|
76 |
+
|
77 |
+
# A few local constants to be used in loops below
|
78 |
+
pjoin = os.path.join
|
79 |
+
|
80 |
+
if os.path.isdir(path):
|
81 |
+
# Build a list of all files in the directory and all files
|
82 |
+
# in its subdirectories. For performance reasons, do not
|
83 |
+
# recurse more than one level into subdirectories.
|
84 |
+
files: List[str] = []
|
85 |
+
for root, dirs, nondirs in os.walk(path, followlinks=True):
|
86 |
+
subdir = root[len(path)+1:]
|
87 |
+
if subdir:
|
88 |
+
files.extend(pjoin(subdir, f) for f in nondirs)
|
89 |
+
dirs[:] = [] # Do not recurse into additional subdirectories.
|
90 |
+
else:
|
91 |
+
files.extend(nondirs)
|
92 |
+
|
93 |
+
else:
|
94 |
+
try:
|
95 |
+
files = list(zipimporter(path)._files.keys()) # type: ignore
|
96 |
+
except Exception:
|
97 |
+
files = []
|
98 |
+
|
99 |
+
# Build a list of modules which match the import_re regex.
|
100 |
+
modules = []
|
101 |
+
for f in files:
|
102 |
+
m = import_re.match(f)
|
103 |
+
if m:
|
104 |
+
modules.append(m.group('name'))
|
105 |
+
return list(set(modules))
|
106 |
+
|
107 |
+
|
108 |
+
def get_root_modules():
|
109 |
+
"""
|
110 |
+
Returns a list containing the names of all the modules available in the
|
111 |
+
folders of the pythonpath.
|
112 |
+
|
113 |
+
ip.db['rootmodules_cache'] maps sys.path entries to list of modules.
|
114 |
+
"""
|
115 |
+
ip = get_ipython()
|
116 |
+
if ip is None:
|
117 |
+
# No global shell instance to store cached list of modules.
|
118 |
+
# Don't try to scan for modules every time.
|
119 |
+
return list(sys.builtin_module_names)
|
120 |
+
|
121 |
+
if getattr(ip.db, "_mock", False):
|
122 |
+
rootmodules_cache = {}
|
123 |
+
else:
|
124 |
+
rootmodules_cache = ip.db.get("rootmodules_cache", {})
|
125 |
+
rootmodules = list(sys.builtin_module_names)
|
126 |
+
start_time = time()
|
127 |
+
store = False
|
128 |
+
for path in sys.path:
|
129 |
+
try:
|
130 |
+
modules = rootmodules_cache[path]
|
131 |
+
except KeyError:
|
132 |
+
modules = module_list(path)
|
133 |
+
try:
|
134 |
+
modules.remove('__init__')
|
135 |
+
except ValueError:
|
136 |
+
pass
|
137 |
+
if path not in ('', '.'): # cwd modules should not be cached
|
138 |
+
rootmodules_cache[path] = modules
|
139 |
+
if time() - start_time > TIMEOUT_STORAGE and not store:
|
140 |
+
store = True
|
141 |
+
print("\nCaching the list of root modules, please wait!")
|
142 |
+
print("(This will only be done once - type '%rehashx' to "
|
143 |
+
"reset cache!)\n")
|
144 |
+
sys.stdout.flush()
|
145 |
+
if time() - start_time > TIMEOUT_GIVEUP:
|
146 |
+
print("This is taking too long, we give up.\n")
|
147 |
+
return []
|
148 |
+
rootmodules.extend(modules)
|
149 |
+
if store:
|
150 |
+
ip.db['rootmodules_cache'] = rootmodules_cache
|
151 |
+
rootmodules = list(set(rootmodules))
|
152 |
+
return rootmodules
|
153 |
+
|
154 |
+
|
155 |
+
def is_importable(module, attr: str, only_modules) -> bool:
|
156 |
+
if only_modules:
|
157 |
+
try:
|
158 |
+
mod = getattr(module, attr)
|
159 |
+
except ModuleNotFoundError:
|
160 |
+
# See gh-14434
|
161 |
+
return False
|
162 |
+
return inspect.ismodule(mod)
|
163 |
+
else:
|
164 |
+
return not(attr[:2] == '__' and attr[-2:] == '__')
|
165 |
+
|
166 |
+
def is_possible_submodule(module, attr):
|
167 |
+
try:
|
168 |
+
obj = getattr(module, attr)
|
169 |
+
except AttributeError:
|
170 |
+
# Is possibly an unimported submodule
|
171 |
+
return True
|
172 |
+
except TypeError:
|
173 |
+
# https://github.com/ipython/ipython/issues/9678
|
174 |
+
return False
|
175 |
+
return inspect.ismodule(obj)
|
176 |
+
|
177 |
+
|
178 |
+
def try_import(mod: str, only_modules=False) -> List[str]:
|
179 |
+
"""
|
180 |
+
Try to import given module and return list of potential completions.
|
181 |
+
"""
|
182 |
+
mod = mod.rstrip('.')
|
183 |
+
try:
|
184 |
+
m = import_module(mod)
|
185 |
+
except:
|
186 |
+
return []
|
187 |
+
|
188 |
+
m_is_init = '__init__' in (getattr(m, '__file__', '') or '')
|
189 |
+
|
190 |
+
completions = []
|
191 |
+
if (not hasattr(m, '__file__')) or (not only_modules) or m_is_init:
|
192 |
+
completions.extend( [attr for attr in dir(m) if
|
193 |
+
is_importable(m, attr, only_modules)])
|
194 |
+
|
195 |
+
m_all = getattr(m, "__all__", [])
|
196 |
+
if only_modules:
|
197 |
+
completions.extend(attr for attr in m_all if is_possible_submodule(m, attr))
|
198 |
+
else:
|
199 |
+
completions.extend(m_all)
|
200 |
+
|
201 |
+
if m_is_init:
|
202 |
+
file_ = m.__file__
|
203 |
+
file_path = os.path.dirname(file_) # type: ignore
|
204 |
+
if file_path is not None:
|
205 |
+
completions.extend(module_list(file_path))
|
206 |
+
completions_set = {c for c in completions if isinstance(c, str)}
|
207 |
+
completions_set.discard('__init__')
|
208 |
+
return list(completions_set)
|
209 |
+
|
210 |
+
|
211 |
+
#-----------------------------------------------------------------------------
|
212 |
+
# Completion-related functions.
|
213 |
+
#-----------------------------------------------------------------------------
|
214 |
+
|
215 |
+
def quick_completer(cmd, completions):
|
216 |
+
r""" Easily create a trivial completer for a command.
|
217 |
+
|
218 |
+
Takes either a list of completions, or all completions in string (that will
|
219 |
+
be split on whitespace).
|
220 |
+
|
221 |
+
Example::
|
222 |
+
|
223 |
+
[d:\ipython]|1> import ipy_completers
|
224 |
+
[d:\ipython]|2> ipy_completers.quick_completer('foo', ['bar','baz'])
|
225 |
+
[d:\ipython]|3> foo b<TAB>
|
226 |
+
bar baz
|
227 |
+
[d:\ipython]|3> foo ba
|
228 |
+
"""
|
229 |
+
|
230 |
+
if isinstance(completions, str):
|
231 |
+
completions = completions.split()
|
232 |
+
|
233 |
+
def do_complete(self, event):
|
234 |
+
return completions
|
235 |
+
|
236 |
+
get_ipython().set_hook('complete_command',do_complete, str_key = cmd)
|
237 |
+
|
238 |
+
def module_completion(line):
|
239 |
+
"""
|
240 |
+
Returns a list containing the completion possibilities for an import line.
|
241 |
+
|
242 |
+
The line looks like this :
|
243 |
+
'import xml.d'
|
244 |
+
'from xml.dom import'
|
245 |
+
"""
|
246 |
+
|
247 |
+
words = line.split(' ')
|
248 |
+
nwords = len(words)
|
249 |
+
|
250 |
+
# from whatever <tab> -> 'import '
|
251 |
+
if nwords == 3 and words[0] == 'from':
|
252 |
+
return ['import ']
|
253 |
+
|
254 |
+
# 'from xy<tab>' or 'import xy<tab>'
|
255 |
+
if nwords < 3 and (words[0] in {'%aimport', 'import', 'from'}) :
|
256 |
+
if nwords == 1:
|
257 |
+
return get_root_modules()
|
258 |
+
mod = words[1].split('.')
|
259 |
+
if len(mod) < 2:
|
260 |
+
return get_root_modules()
|
261 |
+
completion_list = try_import('.'.join(mod[:-1]), True)
|
262 |
+
return ['.'.join(mod[:-1] + [el]) for el in completion_list]
|
263 |
+
|
264 |
+
# 'from xyz import abc<tab>'
|
265 |
+
if nwords >= 3 and words[0] == 'from':
|
266 |
+
mod = words[1]
|
267 |
+
return try_import(mod)
|
268 |
+
|
269 |
+
#-----------------------------------------------------------------------------
|
270 |
+
# Completers
|
271 |
+
#-----------------------------------------------------------------------------
|
272 |
+
# These all have the func(self, event) signature to be used as custom
|
273 |
+
# completers
|
274 |
+
|
275 |
+
def module_completer(self,event):
|
276 |
+
"""Give completions after user has typed 'import ...' or 'from ...'"""
|
277 |
+
|
278 |
+
# This works in all versions of python. While 2.5 has
|
279 |
+
# pkgutil.walk_packages(), that particular routine is fairly dangerous,
|
280 |
+
# since it imports *EVERYTHING* on sys.path. That is: a) very slow b) full
|
281 |
+
# of possibly problematic side effects.
|
282 |
+
# This search the folders in the sys.path for available modules.
|
283 |
+
|
284 |
+
return module_completion(event.line)
|
285 |
+
|
286 |
+
# FIXME: there's a lot of logic common to the run, cd and builtin file
|
287 |
+
# completers, that is currently reimplemented in each.
|
288 |
+
|
289 |
+
def magic_run_completer(self, event):
|
290 |
+
"""Complete files that end in .py or .ipy or .ipynb for the %run command.
|
291 |
+
"""
|
292 |
+
comps = arg_split(event.line, strict=False)
|
293 |
+
# relpath should be the current token that we need to complete.
|
294 |
+
if (len(comps) > 1) and (not event.line.endswith(' ')):
|
295 |
+
relpath = comps[-1].strip("'\"")
|
296 |
+
else:
|
297 |
+
relpath = ''
|
298 |
+
|
299 |
+
#print("\nev=", event) # dbg
|
300 |
+
#print("rp=", relpath) # dbg
|
301 |
+
#print('comps=', comps) # dbg
|
302 |
+
|
303 |
+
lglob = glob.glob
|
304 |
+
isdir = os.path.isdir
|
305 |
+
relpath, tilde_expand, tilde_val = expand_user(relpath)
|
306 |
+
|
307 |
+
# Find if the user has already typed the first filename, after which we
|
308 |
+
# should complete on all files, since after the first one other files may
|
309 |
+
# be arguments to the input script.
|
310 |
+
|
311 |
+
if any(magic_run_re.match(c) for c in comps):
|
312 |
+
matches = [f.replace('\\','/') + ('/' if isdir(f) else '')
|
313 |
+
for f in lglob(relpath+'*')]
|
314 |
+
else:
|
315 |
+
dirs = [f.replace('\\','/') + "/" for f in lglob(relpath+'*') if isdir(f)]
|
316 |
+
pys = [f.replace('\\','/')
|
317 |
+
for f in lglob(relpath+'*.py') + lglob(relpath+'*.ipy') +
|
318 |
+
lglob(relpath+'*.ipynb') + lglob(relpath + '*.pyw')]
|
319 |
+
|
320 |
+
matches = dirs + pys
|
321 |
+
|
322 |
+
#print('run comp:', dirs+pys) # dbg
|
323 |
+
return [compress_user(p, tilde_expand, tilde_val) for p in matches]
|
324 |
+
|
325 |
+
|
326 |
+
def cd_completer(self, event):
|
327 |
+
"""Completer function for cd, which only returns directories."""
|
328 |
+
ip = get_ipython()
|
329 |
+
relpath = event.symbol
|
330 |
+
|
331 |
+
#print(event) # dbg
|
332 |
+
if event.line.endswith('-b') or ' -b ' in event.line:
|
333 |
+
# return only bookmark completions
|
334 |
+
bkms = self.db.get('bookmarks', None)
|
335 |
+
if bkms:
|
336 |
+
return bkms.keys()
|
337 |
+
else:
|
338 |
+
return []
|
339 |
+
|
340 |
+
if event.symbol == '-':
|
341 |
+
width_dh = str(len(str(len(ip.user_ns['_dh']) + 1)))
|
342 |
+
# jump in directory history by number
|
343 |
+
fmt = '-%0' + width_dh +'d [%s]'
|
344 |
+
ents = [ fmt % (i,s) for i,s in enumerate(ip.user_ns['_dh'])]
|
345 |
+
if len(ents) > 1:
|
346 |
+
return ents
|
347 |
+
return []
|
348 |
+
|
349 |
+
if event.symbol.startswith('--'):
|
350 |
+
return ["--" + os.path.basename(d) for d in ip.user_ns['_dh']]
|
351 |
+
|
352 |
+
# Expand ~ in path and normalize directory separators.
|
353 |
+
relpath, tilde_expand, tilde_val = expand_user(relpath)
|
354 |
+
relpath = relpath.replace('\\','/')
|
355 |
+
|
356 |
+
found = []
|
357 |
+
for d in [f.replace('\\','/') + '/' for f in glob.glob(relpath+'*')
|
358 |
+
if os.path.isdir(f)]:
|
359 |
+
if ' ' in d:
|
360 |
+
# we don't want to deal with any of that, complex code
|
361 |
+
# for this is elsewhere
|
362 |
+
raise TryNext
|
363 |
+
|
364 |
+
found.append(d)
|
365 |
+
|
366 |
+
if not found:
|
367 |
+
if os.path.isdir(relpath):
|
368 |
+
return [compress_user(relpath, tilde_expand, tilde_val)]
|
369 |
+
|
370 |
+
# if no completions so far, try bookmarks
|
371 |
+
bks = self.db.get('bookmarks',{})
|
372 |
+
bkmatches = [s for s in bks if s.startswith(event.symbol)]
|
373 |
+
if bkmatches:
|
374 |
+
return bkmatches
|
375 |
+
|
376 |
+
raise TryNext
|
377 |
+
|
378 |
+
return [compress_user(p, tilde_expand, tilde_val) for p in found]
|
379 |
+
|
380 |
+
def reset_completer(self, event):
|
381 |
+
"A completer for %reset magic"
|
382 |
+
return '-f -s in out array dhist'.split()
|
openalex_env_map/lib/python3.10/site-packages/IPython/core/crashhandler.py
ADDED
@@ -0,0 +1,248 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# encoding: utf-8
|
2 |
+
"""sys.excepthook for IPython itself, leaves a detailed report on disk.
|
3 |
+
|
4 |
+
Authors:
|
5 |
+
|
6 |
+
* Fernando Perez
|
7 |
+
* Brian E. Granger
|
8 |
+
"""
|
9 |
+
|
10 |
+
#-----------------------------------------------------------------------------
|
11 |
+
# Copyright (C) 2001-2007 Fernando Perez. <[email protected]>
|
12 |
+
# Copyright (C) 2008-2011 The IPython Development Team
|
13 |
+
#
|
14 |
+
# Distributed under the terms of the BSD License. The full license is in
|
15 |
+
# the file COPYING, distributed as part of this software.
|
16 |
+
#-----------------------------------------------------------------------------
|
17 |
+
|
18 |
+
#-----------------------------------------------------------------------------
|
19 |
+
# Imports
|
20 |
+
#-----------------------------------------------------------------------------
|
21 |
+
|
22 |
+
import sys
|
23 |
+
import traceback
|
24 |
+
from pprint import pformat
|
25 |
+
from pathlib import Path
|
26 |
+
|
27 |
+
import builtins as builtin_mod
|
28 |
+
|
29 |
+
from IPython.core import ultratb
|
30 |
+
from IPython.core.application import Application
|
31 |
+
from IPython.core.release import author_email
|
32 |
+
from IPython.utils.sysinfo import sys_info
|
33 |
+
|
34 |
+
from IPython.core.release import __version__ as version
|
35 |
+
|
36 |
+
from typing import Optional, Dict
|
37 |
+
import types
|
38 |
+
|
39 |
+
#-----------------------------------------------------------------------------
|
40 |
+
# Code
|
41 |
+
#-----------------------------------------------------------------------------
|
42 |
+
|
43 |
+
# Template for the user message.
|
44 |
+
_default_message_template = """\
|
45 |
+
Oops, {app_name} crashed. We do our best to make it stable, but...
|
46 |
+
|
47 |
+
A crash report was automatically generated with the following information:
|
48 |
+
- A verbatim copy of the crash traceback.
|
49 |
+
- A copy of your input history during this session.
|
50 |
+
- Data on your current {app_name} configuration.
|
51 |
+
|
52 |
+
It was left in the file named:
|
53 |
+
\t'{crash_report_fname}'
|
54 |
+
If you can email this file to the developers, the information in it will help
|
55 |
+
them in understanding and correcting the problem.
|
56 |
+
|
57 |
+
You can mail it to: {contact_name} at {contact_email}
|
58 |
+
with the subject '{app_name} Crash Report'.
|
59 |
+
|
60 |
+
If you want to do it now, the following command will work (under Unix):
|
61 |
+
mail -s '{app_name} Crash Report' {contact_email} < {crash_report_fname}
|
62 |
+
|
63 |
+
In your email, please also include information about:
|
64 |
+
- The operating system under which the crash happened: Linux, macOS, Windows,
|
65 |
+
other, and which exact version (for example: Ubuntu 16.04.3, macOS 10.13.2,
|
66 |
+
Windows 10 Pro), and whether it is 32-bit or 64-bit;
|
67 |
+
- How {app_name} was installed: using pip or conda, from GitHub, as part of
|
68 |
+
a Docker container, or other, providing more detail if possible;
|
69 |
+
- How to reproduce the crash: what exact sequence of instructions can one
|
70 |
+
input to get the same crash? Ideally, find a minimal yet complete sequence
|
71 |
+
of instructions that yields the crash.
|
72 |
+
|
73 |
+
To ensure accurate tracking of this issue, please file a report about it at:
|
74 |
+
{bug_tracker}
|
75 |
+
"""
|
76 |
+
|
77 |
+
_lite_message_template = """
|
78 |
+
If you suspect this is an IPython {version} bug, please report it at:
|
79 |
+
https://github.com/ipython/ipython/issues
|
80 |
+
or send an email to the mailing list at {email}
|
81 |
+
|
82 |
+
You can print a more detailed traceback right now with "%tb", or use "%debug"
|
83 |
+
to interactively debug it.
|
84 |
+
|
85 |
+
Extra-detailed tracebacks for bug-reporting purposes can be enabled via:
|
86 |
+
{config}Application.verbose_crash=True
|
87 |
+
"""
|
88 |
+
|
89 |
+
|
90 |
+
class CrashHandler:
|
91 |
+
"""Customizable crash handlers for IPython applications.
|
92 |
+
|
93 |
+
Instances of this class provide a :meth:`__call__` method which can be
|
94 |
+
used as a ``sys.excepthook``. The :meth:`__call__` signature is::
|
95 |
+
|
96 |
+
def __call__(self, etype, evalue, etb)
|
97 |
+
"""
|
98 |
+
|
99 |
+
message_template = _default_message_template
|
100 |
+
section_sep = '\n\n'+'*'*75+'\n\n'
|
101 |
+
info: Dict[str, Optional[str]]
|
102 |
+
|
103 |
+
def __init__(
|
104 |
+
self,
|
105 |
+
app: Application,
|
106 |
+
contact_name: Optional[str] = None,
|
107 |
+
contact_email: Optional[str] = None,
|
108 |
+
bug_tracker: Optional[str] = None,
|
109 |
+
show_crash_traceback: bool = True,
|
110 |
+
call_pdb: bool = False,
|
111 |
+
):
|
112 |
+
"""Create a new crash handler
|
113 |
+
|
114 |
+
Parameters
|
115 |
+
----------
|
116 |
+
app : Application
|
117 |
+
A running :class:`Application` instance, which will be queried at
|
118 |
+
crash time for internal information.
|
119 |
+
contact_name : str
|
120 |
+
A string with the name of the person to contact.
|
121 |
+
contact_email : str
|
122 |
+
A string with the email address of the contact.
|
123 |
+
bug_tracker : str
|
124 |
+
A string with the URL for your project's bug tracker.
|
125 |
+
show_crash_traceback : bool
|
126 |
+
If false, don't print the crash traceback on stderr, only generate
|
127 |
+
the on-disk report
|
128 |
+
call_pdb
|
129 |
+
Whether to call pdb on crash
|
130 |
+
|
131 |
+
Attributes
|
132 |
+
----------
|
133 |
+
These instances contain some non-argument attributes which allow for
|
134 |
+
further customization of the crash handler's behavior. Please see the
|
135 |
+
source for further details.
|
136 |
+
|
137 |
+
"""
|
138 |
+
self.crash_report_fname = "Crash_report_%s.txt" % app.name
|
139 |
+
self.app = app
|
140 |
+
self.call_pdb = call_pdb
|
141 |
+
#self.call_pdb = True # dbg
|
142 |
+
self.show_crash_traceback = show_crash_traceback
|
143 |
+
self.info = dict(app_name = app.name,
|
144 |
+
contact_name = contact_name,
|
145 |
+
contact_email = contact_email,
|
146 |
+
bug_tracker = bug_tracker,
|
147 |
+
crash_report_fname = self.crash_report_fname)
|
148 |
+
|
149 |
+
def __call__(
|
150 |
+
self,
|
151 |
+
etype: type[BaseException],
|
152 |
+
evalue: BaseException,
|
153 |
+
etb: types.TracebackType,
|
154 |
+
) -> None:
|
155 |
+
"""Handle an exception, call for compatible with sys.excepthook"""
|
156 |
+
|
157 |
+
# do not allow the crash handler to be called twice without reinstalling it
|
158 |
+
# this prevents unlikely errors in the crash handling from entering an
|
159 |
+
# infinite loop.
|
160 |
+
sys.excepthook = sys.__excepthook__
|
161 |
+
|
162 |
+
# Report tracebacks shouldn't use color in general (safer for users)
|
163 |
+
color_scheme = 'NoColor'
|
164 |
+
|
165 |
+
# Use this ONLY for developer debugging (keep commented out for release)
|
166 |
+
# color_scheme = 'Linux' # dbg
|
167 |
+
ipython_dir = getattr(self.app, "ipython_dir", None)
|
168 |
+
if ipython_dir is not None:
|
169 |
+
assert isinstance(ipython_dir, str)
|
170 |
+
rptdir = Path(ipython_dir)
|
171 |
+
else:
|
172 |
+
rptdir = Path.cwd()
|
173 |
+
if not rptdir.is_dir():
|
174 |
+
rptdir = Path.cwd()
|
175 |
+
report_name = rptdir / self.crash_report_fname
|
176 |
+
# write the report filename into the instance dict so it can get
|
177 |
+
# properly expanded out in the user message template
|
178 |
+
self.crash_report_fname = str(report_name)
|
179 |
+
self.info["crash_report_fname"] = str(report_name)
|
180 |
+
TBhandler = ultratb.VerboseTB(
|
181 |
+
color_scheme=color_scheme,
|
182 |
+
long_header=True,
|
183 |
+
call_pdb=self.call_pdb,
|
184 |
+
)
|
185 |
+
if self.call_pdb:
|
186 |
+
TBhandler(etype,evalue,etb)
|
187 |
+
return
|
188 |
+
else:
|
189 |
+
traceback = TBhandler.text(etype,evalue,etb,context=31)
|
190 |
+
|
191 |
+
# print traceback to screen
|
192 |
+
if self.show_crash_traceback:
|
193 |
+
print(traceback, file=sys.stderr)
|
194 |
+
|
195 |
+
# and generate a complete report on disk
|
196 |
+
try:
|
197 |
+
report = open(report_name, "w", encoding="utf-8")
|
198 |
+
except:
|
199 |
+
print('Could not create crash report on disk.', file=sys.stderr)
|
200 |
+
return
|
201 |
+
|
202 |
+
with report:
|
203 |
+
# Inform user on stderr of what happened
|
204 |
+
print('\n'+'*'*70+'\n', file=sys.stderr)
|
205 |
+
print(self.message_template.format(**self.info), file=sys.stderr)
|
206 |
+
|
207 |
+
# Construct report on disk
|
208 |
+
report.write(self.make_report(str(traceback)))
|
209 |
+
|
210 |
+
builtin_mod.input("Hit <Enter> to quit (your terminal may close):")
|
211 |
+
|
212 |
+
def make_report(self, traceback: str) -> str:
|
213 |
+
"""Return a string containing a crash report."""
|
214 |
+
|
215 |
+
sec_sep = self.section_sep
|
216 |
+
|
217 |
+
report = ['*'*75+'\n\n'+'IPython post-mortem report\n\n']
|
218 |
+
rpt_add = report.append
|
219 |
+
rpt_add(sys_info())
|
220 |
+
|
221 |
+
try:
|
222 |
+
config = pformat(self.app.config)
|
223 |
+
rpt_add(sec_sep)
|
224 |
+
rpt_add("Application name: %s\n\n" % self.app.name)
|
225 |
+
rpt_add("Current user configuration structure:\n\n")
|
226 |
+
rpt_add(config)
|
227 |
+
except:
|
228 |
+
pass
|
229 |
+
rpt_add(sec_sep+'Crash traceback:\n\n' + traceback)
|
230 |
+
|
231 |
+
return ''.join(report)
|
232 |
+
|
233 |
+
|
234 |
+
def crash_handler_lite(
|
235 |
+
etype: type[BaseException], evalue: BaseException, tb: types.TracebackType
|
236 |
+
) -> None:
|
237 |
+
"""a light excepthook, adding a small message to the usual traceback"""
|
238 |
+
traceback.print_exception(etype, evalue, tb)
|
239 |
+
|
240 |
+
from IPython.core.interactiveshell import InteractiveShell
|
241 |
+
if InteractiveShell.initialized():
|
242 |
+
# we are in a Shell environment, give %magic example
|
243 |
+
config = "%config "
|
244 |
+
else:
|
245 |
+
# we are not in a shell, show generic config
|
246 |
+
config = "c."
|
247 |
+
print(_lite_message_template.format(email=author_email, config=config, version=version), file=sys.stderr)
|
248 |
+
|
openalex_env_map/lib/python3.10/site-packages/IPython/core/debugger.py
ADDED
@@ -0,0 +1,1136 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"""
|
2 |
+
Pdb debugger class.
|
3 |
+
|
4 |
+
|
5 |
+
This is an extension to PDB which adds a number of new features.
|
6 |
+
Note that there is also the `IPython.terminal.debugger` class which provides UI
|
7 |
+
improvements.
|
8 |
+
|
9 |
+
We also strongly recommend to use this via the `ipdb` package, which provides
|
10 |
+
extra configuration options.
|
11 |
+
|
12 |
+
Among other things, this subclass of PDB:
|
13 |
+
- supports many IPython magics like pdef/psource
|
14 |
+
- hide frames in tracebacks based on `__tracebackhide__`
|
15 |
+
- allows to skip frames based on `__debuggerskip__`
|
16 |
+
|
17 |
+
|
18 |
+
Global Configuration
|
19 |
+
--------------------
|
20 |
+
|
21 |
+
The IPython debugger will by read the global ``~/.pdbrc`` file.
|
22 |
+
That is to say you can list all commands supported by ipdb in your `~/.pdbrc`
|
23 |
+
configuration file, to globally configure pdb.
|
24 |
+
|
25 |
+
Example::
|
26 |
+
|
27 |
+
# ~/.pdbrc
|
28 |
+
skip_predicates debuggerskip false
|
29 |
+
skip_hidden false
|
30 |
+
context 25
|
31 |
+
|
32 |
+
Features
|
33 |
+
--------
|
34 |
+
|
35 |
+
The IPython debugger can hide and skip frames when printing or moving through
|
36 |
+
the stack. This can have a performance impact, so can be configures.
|
37 |
+
|
38 |
+
The skipping and hiding frames are configurable via the `skip_predicates`
|
39 |
+
command.
|
40 |
+
|
41 |
+
By default, frames from readonly files will be hidden, frames containing
|
42 |
+
``__tracebackhide__ = True`` will be hidden.
|
43 |
+
|
44 |
+
Frames containing ``__debuggerskip__`` will be stepped over, frames whose parent
|
45 |
+
frames value of ``__debuggerskip__`` is ``True`` will also be skipped.
|
46 |
+
|
47 |
+
>>> def helpers_helper():
|
48 |
+
... pass
|
49 |
+
...
|
50 |
+
... def helper_1():
|
51 |
+
... print("don't step in me")
|
52 |
+
... helpers_helpers() # will be stepped over unless breakpoint set.
|
53 |
+
...
|
54 |
+
...
|
55 |
+
... def helper_2():
|
56 |
+
... print("in me neither")
|
57 |
+
...
|
58 |
+
|
59 |
+
One can define a decorator that wraps a function between the two helpers:
|
60 |
+
|
61 |
+
>>> def pdb_skipped_decorator(function):
|
62 |
+
...
|
63 |
+
...
|
64 |
+
... def wrapped_fn(*args, **kwargs):
|
65 |
+
... __debuggerskip__ = True
|
66 |
+
... helper_1()
|
67 |
+
... __debuggerskip__ = False
|
68 |
+
... result = function(*args, **kwargs)
|
69 |
+
... __debuggerskip__ = True
|
70 |
+
... helper_2()
|
71 |
+
... # setting __debuggerskip__ to False again is not necessary
|
72 |
+
... return result
|
73 |
+
...
|
74 |
+
... return wrapped_fn
|
75 |
+
|
76 |
+
When decorating a function, ipdb will directly step into ``bar()`` by
|
77 |
+
default:
|
78 |
+
|
79 |
+
>>> @foo_decorator
|
80 |
+
... def bar(x, y):
|
81 |
+
... return x * y
|
82 |
+
|
83 |
+
|
84 |
+
You can toggle the behavior with
|
85 |
+
|
86 |
+
ipdb> skip_predicates debuggerskip false
|
87 |
+
|
88 |
+
or configure it in your ``.pdbrc``
|
89 |
+
|
90 |
+
|
91 |
+
|
92 |
+
License
|
93 |
+
-------
|
94 |
+
|
95 |
+
Modified from the standard pdb.Pdb class to avoid including readline, so that
|
96 |
+
the command line completion of other programs which include this isn't
|
97 |
+
damaged.
|
98 |
+
|
99 |
+
In the future, this class will be expanded with improvements over the standard
|
100 |
+
pdb.
|
101 |
+
|
102 |
+
The original code in this file is mainly lifted out of cmd.py in Python 2.2,
|
103 |
+
with minor changes. Licensing should therefore be under the standard Python
|
104 |
+
terms. For details on the PSF (Python Software Foundation) standard license,
|
105 |
+
see:
|
106 |
+
|
107 |
+
https://docs.python.org/2/license.html
|
108 |
+
|
109 |
+
|
110 |
+
All the changes since then are under the same license as IPython.
|
111 |
+
|
112 |
+
"""
|
113 |
+
|
114 |
+
#*****************************************************************************
|
115 |
+
#
|
116 |
+
# This file is licensed under the PSF license.
|
117 |
+
#
|
118 |
+
# Copyright (C) 2001 Python Software Foundation, www.python.org
|
119 |
+
# Copyright (C) 2005-2006 Fernando Perez. <[email protected]>
|
120 |
+
#
|
121 |
+
#
|
122 |
+
#*****************************************************************************
|
123 |
+
|
124 |
+
from __future__ import annotations
|
125 |
+
|
126 |
+
import inspect
|
127 |
+
import linecache
|
128 |
+
import os
|
129 |
+
import re
|
130 |
+
import sys
|
131 |
+
from contextlib import contextmanager
|
132 |
+
from functools import lru_cache
|
133 |
+
|
134 |
+
from IPython import get_ipython
|
135 |
+
from IPython.core.excolors import exception_colors
|
136 |
+
from IPython.utils import PyColorize, coloransi, py3compat
|
137 |
+
|
138 |
+
from typing import TYPE_CHECKING
|
139 |
+
|
140 |
+
if TYPE_CHECKING:
|
141 |
+
# otherwise circular import
|
142 |
+
from IPython.core.interactiveshell import InteractiveShell
|
143 |
+
|
144 |
+
# skip module docstests
|
145 |
+
__skip_doctest__ = True
|
146 |
+
|
147 |
+
prompt = 'ipdb> '
|
148 |
+
|
149 |
+
# We have to check this directly from sys.argv, config struct not yet available
|
150 |
+
from pdb import Pdb as OldPdb
|
151 |
+
|
152 |
+
# Allow the set_trace code to operate outside of an ipython instance, even if
|
153 |
+
# it does so with some limitations. The rest of this support is implemented in
|
154 |
+
# the Tracer constructor.
|
155 |
+
|
156 |
+
DEBUGGERSKIP = "__debuggerskip__"
|
157 |
+
|
158 |
+
|
159 |
+
# this has been implemented in Pdb in Python 3.13 (https://github.com/python/cpython/pull/106676
|
160 |
+
# on lower python versions, we backported the feature.
|
161 |
+
CHAIN_EXCEPTIONS = sys.version_info < (3, 13)
|
162 |
+
|
163 |
+
|
164 |
+
def make_arrow(pad):
|
165 |
+
"""generate the leading arrow in front of traceback or debugger"""
|
166 |
+
if pad >= 2:
|
167 |
+
return '-'*(pad-2) + '> '
|
168 |
+
elif pad == 1:
|
169 |
+
return '>'
|
170 |
+
return ''
|
171 |
+
|
172 |
+
|
173 |
+
def BdbQuit_excepthook(et, ev, tb, excepthook=None):
|
174 |
+
"""Exception hook which handles `BdbQuit` exceptions.
|
175 |
+
|
176 |
+
All other exceptions are processed using the `excepthook`
|
177 |
+
parameter.
|
178 |
+
"""
|
179 |
+
raise ValueError(
|
180 |
+
"`BdbQuit_excepthook` is deprecated since version 5.1. It is still around only because it is still imported by ipdb.",
|
181 |
+
)
|
182 |
+
|
183 |
+
|
184 |
+
RGX_EXTRA_INDENT = re.compile(r'(?<=\n)\s+')
|
185 |
+
|
186 |
+
|
187 |
+
def strip_indentation(multiline_string):
|
188 |
+
return RGX_EXTRA_INDENT.sub('', multiline_string)
|
189 |
+
|
190 |
+
|
191 |
+
def decorate_fn_with_doc(new_fn, old_fn, additional_text=""):
|
192 |
+
"""Make new_fn have old_fn's doc string. This is particularly useful
|
193 |
+
for the ``do_...`` commands that hook into the help system.
|
194 |
+
Adapted from from a comp.lang.python posting
|
195 |
+
by Duncan Booth."""
|
196 |
+
def wrapper(*args, **kw):
|
197 |
+
return new_fn(*args, **kw)
|
198 |
+
if old_fn.__doc__:
|
199 |
+
wrapper.__doc__ = strip_indentation(old_fn.__doc__) + additional_text
|
200 |
+
return wrapper
|
201 |
+
|
202 |
+
|
203 |
+
class Pdb(OldPdb):
|
204 |
+
"""Modified Pdb class, does not load readline.
|
205 |
+
|
206 |
+
for a standalone version that uses prompt_toolkit, see
|
207 |
+
`IPython.terminal.debugger.TerminalPdb` and
|
208 |
+
`IPython.terminal.debugger.set_trace()`
|
209 |
+
|
210 |
+
|
211 |
+
This debugger can hide and skip frames that are tagged according to some predicates.
|
212 |
+
See the `skip_predicates` commands.
|
213 |
+
|
214 |
+
"""
|
215 |
+
|
216 |
+
shell: InteractiveShell
|
217 |
+
|
218 |
+
if CHAIN_EXCEPTIONS:
|
219 |
+
MAX_CHAINED_EXCEPTION_DEPTH = 999
|
220 |
+
|
221 |
+
default_predicates = {
|
222 |
+
"tbhide": True,
|
223 |
+
"readonly": False,
|
224 |
+
"ipython_internal": True,
|
225 |
+
"debuggerskip": True,
|
226 |
+
}
|
227 |
+
|
228 |
+
def __init__(self, completekey=None, stdin=None, stdout=None, context=5, **kwargs):
|
229 |
+
"""Create a new IPython debugger.
|
230 |
+
|
231 |
+
Parameters
|
232 |
+
----------
|
233 |
+
completekey : default None
|
234 |
+
Passed to pdb.Pdb.
|
235 |
+
stdin : default None
|
236 |
+
Passed to pdb.Pdb.
|
237 |
+
stdout : default None
|
238 |
+
Passed to pdb.Pdb.
|
239 |
+
context : int
|
240 |
+
Number of lines of source code context to show when
|
241 |
+
displaying stacktrace information.
|
242 |
+
**kwargs
|
243 |
+
Passed to pdb.Pdb.
|
244 |
+
|
245 |
+
Notes
|
246 |
+
-----
|
247 |
+
The possibilities are python version dependent, see the python
|
248 |
+
docs for more info.
|
249 |
+
"""
|
250 |
+
|
251 |
+
# Parent constructor:
|
252 |
+
try:
|
253 |
+
self.context = int(context)
|
254 |
+
if self.context <= 0:
|
255 |
+
raise ValueError("Context must be a positive integer")
|
256 |
+
except (TypeError, ValueError) as e:
|
257 |
+
raise ValueError("Context must be a positive integer") from e
|
258 |
+
|
259 |
+
# `kwargs` ensures full compatibility with stdlib's `pdb.Pdb`.
|
260 |
+
OldPdb.__init__(self, completekey, stdin, stdout, **kwargs)
|
261 |
+
|
262 |
+
# IPython changes...
|
263 |
+
self.shell = get_ipython()
|
264 |
+
|
265 |
+
if self.shell is None:
|
266 |
+
save_main = sys.modules['__main__']
|
267 |
+
# No IPython instance running, we must create one
|
268 |
+
from IPython.terminal.interactiveshell import \
|
269 |
+
TerminalInteractiveShell
|
270 |
+
self.shell = TerminalInteractiveShell.instance()
|
271 |
+
# needed by any code which calls __import__("__main__") after
|
272 |
+
# the debugger was entered. See also #9941.
|
273 |
+
sys.modules["__main__"] = save_main
|
274 |
+
|
275 |
+
|
276 |
+
color_scheme = self.shell.colors
|
277 |
+
|
278 |
+
self.aliases = {}
|
279 |
+
|
280 |
+
# Create color table: we copy the default one from the traceback
|
281 |
+
# module and add a few attributes needed for debugging
|
282 |
+
self.color_scheme_table = exception_colors()
|
283 |
+
|
284 |
+
# shorthands
|
285 |
+
C = coloransi.TermColors
|
286 |
+
cst = self.color_scheme_table
|
287 |
+
|
288 |
+
|
289 |
+
# Add a python parser so we can syntax highlight source while
|
290 |
+
# debugging.
|
291 |
+
self.parser = PyColorize.Parser(style=color_scheme)
|
292 |
+
self.set_colors(color_scheme)
|
293 |
+
|
294 |
+
# Set the prompt - the default prompt is '(Pdb)'
|
295 |
+
self.prompt = prompt
|
296 |
+
self.skip_hidden = True
|
297 |
+
self.report_skipped = True
|
298 |
+
|
299 |
+
# list of predicates we use to skip frames
|
300 |
+
self._predicates = self.default_predicates
|
301 |
+
|
302 |
+
if CHAIN_EXCEPTIONS:
|
303 |
+
self._chained_exceptions = tuple()
|
304 |
+
self._chained_exception_index = 0
|
305 |
+
|
306 |
+
#
|
307 |
+
def set_colors(self, scheme):
|
308 |
+
"""Shorthand access to the color table scheme selector method."""
|
309 |
+
self.color_scheme_table.set_active_scheme(scheme)
|
310 |
+
self.parser.style = scheme
|
311 |
+
|
312 |
+
def set_trace(self, frame=None):
|
313 |
+
if frame is None:
|
314 |
+
frame = sys._getframe().f_back
|
315 |
+
self.initial_frame = frame
|
316 |
+
return super().set_trace(frame)
|
317 |
+
|
318 |
+
def _hidden_predicate(self, frame):
|
319 |
+
"""
|
320 |
+
Given a frame return whether it it should be hidden or not by IPython.
|
321 |
+
"""
|
322 |
+
|
323 |
+
if self._predicates["readonly"]:
|
324 |
+
fname = frame.f_code.co_filename
|
325 |
+
# we need to check for file existence and interactively define
|
326 |
+
# function would otherwise appear as RO.
|
327 |
+
if os.path.isfile(fname) and not os.access(fname, os.W_OK):
|
328 |
+
return True
|
329 |
+
|
330 |
+
if self._predicates["tbhide"]:
|
331 |
+
if frame in (self.curframe, getattr(self, "initial_frame", None)):
|
332 |
+
return False
|
333 |
+
frame_locals = self._get_frame_locals(frame)
|
334 |
+
if "__tracebackhide__" not in frame_locals:
|
335 |
+
return False
|
336 |
+
return frame_locals["__tracebackhide__"]
|
337 |
+
return False
|
338 |
+
|
339 |
+
def hidden_frames(self, stack):
|
340 |
+
"""
|
341 |
+
Given an index in the stack return whether it should be skipped.
|
342 |
+
|
343 |
+
This is used in up/down and where to skip frames.
|
344 |
+
"""
|
345 |
+
# The f_locals dictionary is updated from the actual frame
|
346 |
+
# locals whenever the .f_locals accessor is called, so we
|
347 |
+
# avoid calling it here to preserve self.curframe_locals.
|
348 |
+
# Furthermore, there is no good reason to hide the current frame.
|
349 |
+
ip_hide = [self._hidden_predicate(s[0]) for s in stack]
|
350 |
+
ip_start = [i for i, s in enumerate(ip_hide) if s == "__ipython_bottom__"]
|
351 |
+
if ip_start and self._predicates["ipython_internal"]:
|
352 |
+
ip_hide = [h if i > ip_start[0] else True for (i, h) in enumerate(ip_hide)]
|
353 |
+
return ip_hide
|
354 |
+
|
355 |
+
if CHAIN_EXCEPTIONS:
|
356 |
+
|
357 |
+
def _get_tb_and_exceptions(self, tb_or_exc):
|
358 |
+
"""
|
359 |
+
Given a tracecack or an exception, return a tuple of chained exceptions
|
360 |
+
and current traceback to inspect.
|
361 |
+
This will deal with selecting the right ``__cause__`` or ``__context__``
|
362 |
+
as well as handling cycles, and return a flattened list of exceptions we
|
363 |
+
can jump to with do_exceptions.
|
364 |
+
"""
|
365 |
+
_exceptions = []
|
366 |
+
if isinstance(tb_or_exc, BaseException):
|
367 |
+
traceback, current = tb_or_exc.__traceback__, tb_or_exc
|
368 |
+
|
369 |
+
while current is not None:
|
370 |
+
if current in _exceptions:
|
371 |
+
break
|
372 |
+
_exceptions.append(current)
|
373 |
+
if current.__cause__ is not None:
|
374 |
+
current = current.__cause__
|
375 |
+
elif (
|
376 |
+
current.__context__ is not None
|
377 |
+
and not current.__suppress_context__
|
378 |
+
):
|
379 |
+
current = current.__context__
|
380 |
+
|
381 |
+
if len(_exceptions) >= self.MAX_CHAINED_EXCEPTION_DEPTH:
|
382 |
+
self.message(
|
383 |
+
f"More than {self.MAX_CHAINED_EXCEPTION_DEPTH}"
|
384 |
+
" chained exceptions found, not all exceptions"
|
385 |
+
"will be browsable with `exceptions`."
|
386 |
+
)
|
387 |
+
break
|
388 |
+
else:
|
389 |
+
traceback = tb_or_exc
|
390 |
+
return tuple(reversed(_exceptions)), traceback
|
391 |
+
|
392 |
+
@contextmanager
|
393 |
+
def _hold_exceptions(self, exceptions):
|
394 |
+
"""
|
395 |
+
Context manager to ensure proper cleaning of exceptions references
|
396 |
+
When given a chained exception instead of a traceback,
|
397 |
+
pdb may hold references to many objects which may leak memory.
|
398 |
+
We use this context manager to make sure everything is properly cleaned
|
399 |
+
"""
|
400 |
+
try:
|
401 |
+
self._chained_exceptions = exceptions
|
402 |
+
self._chained_exception_index = len(exceptions) - 1
|
403 |
+
yield
|
404 |
+
finally:
|
405 |
+
# we can't put those in forget as otherwise they would
|
406 |
+
# be cleared on exception change
|
407 |
+
self._chained_exceptions = tuple()
|
408 |
+
self._chained_exception_index = 0
|
409 |
+
|
410 |
+
def do_exceptions(self, arg):
|
411 |
+
"""exceptions [number]
|
412 |
+
List or change current exception in an exception chain.
|
413 |
+
Without arguments, list all the current exception in the exception
|
414 |
+
chain. Exceptions will be numbered, with the current exception indicated
|
415 |
+
with an arrow.
|
416 |
+
If given an integer as argument, switch to the exception at that index.
|
417 |
+
"""
|
418 |
+
if not self._chained_exceptions:
|
419 |
+
self.message(
|
420 |
+
"Did not find chained exceptions. To move between"
|
421 |
+
" exceptions, pdb/post_mortem must be given an exception"
|
422 |
+
" object rather than a traceback."
|
423 |
+
)
|
424 |
+
return
|
425 |
+
if not arg:
|
426 |
+
for ix, exc in enumerate(self._chained_exceptions):
|
427 |
+
prompt = ">" if ix == self._chained_exception_index else " "
|
428 |
+
rep = repr(exc)
|
429 |
+
if len(rep) > 80:
|
430 |
+
rep = rep[:77] + "..."
|
431 |
+
indicator = (
|
432 |
+
" -"
|
433 |
+
if self._chained_exceptions[ix].__traceback__ is None
|
434 |
+
else f"{ix:>3}"
|
435 |
+
)
|
436 |
+
self.message(f"{prompt} {indicator} {rep}")
|
437 |
+
else:
|
438 |
+
try:
|
439 |
+
number = int(arg)
|
440 |
+
except ValueError:
|
441 |
+
self.error("Argument must be an integer")
|
442 |
+
return
|
443 |
+
if 0 <= number < len(self._chained_exceptions):
|
444 |
+
if self._chained_exceptions[number].__traceback__ is None:
|
445 |
+
self.error(
|
446 |
+
"This exception does not have a traceback, cannot jump to it"
|
447 |
+
)
|
448 |
+
return
|
449 |
+
|
450 |
+
self._chained_exception_index = number
|
451 |
+
self.setup(None, self._chained_exceptions[number].__traceback__)
|
452 |
+
self.print_stack_entry(self.stack[self.curindex])
|
453 |
+
else:
|
454 |
+
self.error("No exception with that number")
|
455 |
+
|
456 |
+
def interaction(self, frame, tb_or_exc):
|
457 |
+
try:
|
458 |
+
if CHAIN_EXCEPTIONS:
|
459 |
+
# this context manager is part of interaction in 3.13
|
460 |
+
_chained_exceptions, tb = self._get_tb_and_exceptions(tb_or_exc)
|
461 |
+
if isinstance(tb_or_exc, BaseException):
|
462 |
+
assert tb is not None, "main exception must have a traceback"
|
463 |
+
with self._hold_exceptions(_chained_exceptions):
|
464 |
+
OldPdb.interaction(self, frame, tb)
|
465 |
+
else:
|
466 |
+
OldPdb.interaction(self, frame, tb_or_exc)
|
467 |
+
|
468 |
+
except KeyboardInterrupt:
|
469 |
+
self.stdout.write("\n" + self.shell.get_exception_only())
|
470 |
+
|
471 |
+
def precmd(self, line):
|
472 |
+
"""Perform useful escapes on the command before it is executed."""
|
473 |
+
|
474 |
+
if line.endswith("??"):
|
475 |
+
line = "pinfo2 " + line[:-2]
|
476 |
+
elif line.endswith("?"):
|
477 |
+
line = "pinfo " + line[:-1]
|
478 |
+
|
479 |
+
line = super().precmd(line)
|
480 |
+
|
481 |
+
return line
|
482 |
+
|
483 |
+
def new_do_quit(self, arg):
|
484 |
+
return OldPdb.do_quit(self, arg)
|
485 |
+
|
486 |
+
do_q = do_quit = decorate_fn_with_doc(new_do_quit, OldPdb.do_quit)
|
487 |
+
|
488 |
+
def print_stack_trace(self, context=None):
|
489 |
+
Colors = self.color_scheme_table.active_colors
|
490 |
+
ColorsNormal = Colors.Normal
|
491 |
+
if context is None:
|
492 |
+
context = self.context
|
493 |
+
try:
|
494 |
+
context = int(context)
|
495 |
+
if context <= 0:
|
496 |
+
raise ValueError("Context must be a positive integer")
|
497 |
+
except (TypeError, ValueError) as e:
|
498 |
+
raise ValueError("Context must be a positive integer") from e
|
499 |
+
try:
|
500 |
+
skipped = 0
|
501 |
+
for hidden, frame_lineno in zip(self.hidden_frames(self.stack), self.stack):
|
502 |
+
if hidden and self.skip_hidden:
|
503 |
+
skipped += 1
|
504 |
+
continue
|
505 |
+
if skipped:
|
506 |
+
print(
|
507 |
+
f"{Colors.excName} [... skipping {skipped} hidden frame(s)]{ColorsNormal}\n"
|
508 |
+
)
|
509 |
+
skipped = 0
|
510 |
+
self.print_stack_entry(frame_lineno, context=context)
|
511 |
+
if skipped:
|
512 |
+
print(
|
513 |
+
f"{Colors.excName} [... skipping {skipped} hidden frame(s)]{ColorsNormal}\n"
|
514 |
+
)
|
515 |
+
except KeyboardInterrupt:
|
516 |
+
pass
|
517 |
+
|
518 |
+
def print_stack_entry(self, frame_lineno, prompt_prefix='\n-> ',
|
519 |
+
context=None):
|
520 |
+
if context is None:
|
521 |
+
context = self.context
|
522 |
+
try:
|
523 |
+
context = int(context)
|
524 |
+
if context <= 0:
|
525 |
+
raise ValueError("Context must be a positive integer")
|
526 |
+
except (TypeError, ValueError) as e:
|
527 |
+
raise ValueError("Context must be a positive integer") from e
|
528 |
+
print(self.format_stack_entry(frame_lineno, '', context), file=self.stdout)
|
529 |
+
|
530 |
+
# vds: >>
|
531 |
+
frame, lineno = frame_lineno
|
532 |
+
filename = frame.f_code.co_filename
|
533 |
+
self.shell.hooks.synchronize_with_editor(filename, lineno, 0)
|
534 |
+
# vds: <<
|
535 |
+
|
536 |
+
def _get_frame_locals(self, frame):
|
537 |
+
""" "
|
538 |
+
Accessing f_local of current frame reset the namespace, so we want to avoid
|
539 |
+
that or the following can happen
|
540 |
+
|
541 |
+
ipdb> foo
|
542 |
+
"old"
|
543 |
+
ipdb> foo = "new"
|
544 |
+
ipdb> foo
|
545 |
+
"new"
|
546 |
+
ipdb> where
|
547 |
+
ipdb> foo
|
548 |
+
"old"
|
549 |
+
|
550 |
+
So if frame is self.current_frame we instead return self.curframe_locals
|
551 |
+
|
552 |
+
"""
|
553 |
+
if frame is getattr(self, "curframe", None):
|
554 |
+
return self.curframe_locals
|
555 |
+
else:
|
556 |
+
return frame.f_locals
|
557 |
+
|
558 |
+
def format_stack_entry(self, frame_lineno, lprefix=': ', context=None):
|
559 |
+
if context is None:
|
560 |
+
context = self.context
|
561 |
+
try:
|
562 |
+
context = int(context)
|
563 |
+
if context <= 0:
|
564 |
+
print("Context must be a positive integer", file=self.stdout)
|
565 |
+
except (TypeError, ValueError):
|
566 |
+
print("Context must be a positive integer", file=self.stdout)
|
567 |
+
|
568 |
+
import reprlib
|
569 |
+
|
570 |
+
ret = []
|
571 |
+
|
572 |
+
Colors = self.color_scheme_table.active_colors
|
573 |
+
ColorsNormal = Colors.Normal
|
574 |
+
tpl_link = "%s%%s%s" % (Colors.filenameEm, ColorsNormal)
|
575 |
+
tpl_call = "%s%%s%s%%s%s" % (Colors.vName, Colors.valEm, ColorsNormal)
|
576 |
+
tpl_line = "%%s%s%%s %s%%s" % (Colors.lineno, ColorsNormal)
|
577 |
+
tpl_line_em = "%%s%s%%s %s%%s%s" % (Colors.linenoEm, Colors.line, ColorsNormal)
|
578 |
+
|
579 |
+
frame, lineno = frame_lineno
|
580 |
+
|
581 |
+
return_value = ''
|
582 |
+
loc_frame = self._get_frame_locals(frame)
|
583 |
+
if "__return__" in loc_frame:
|
584 |
+
rv = loc_frame["__return__"]
|
585 |
+
# return_value += '->'
|
586 |
+
return_value += reprlib.repr(rv) + "\n"
|
587 |
+
ret.append(return_value)
|
588 |
+
|
589 |
+
#s = filename + '(' + `lineno` + ')'
|
590 |
+
filename = self.canonic(frame.f_code.co_filename)
|
591 |
+
link = tpl_link % py3compat.cast_unicode(filename)
|
592 |
+
|
593 |
+
if frame.f_code.co_name:
|
594 |
+
func = frame.f_code.co_name
|
595 |
+
else:
|
596 |
+
func = "<lambda>"
|
597 |
+
|
598 |
+
call = ""
|
599 |
+
if func != "?":
|
600 |
+
if "__args__" in loc_frame:
|
601 |
+
args = reprlib.repr(loc_frame["__args__"])
|
602 |
+
else:
|
603 |
+
args = '()'
|
604 |
+
call = tpl_call % (func, args)
|
605 |
+
|
606 |
+
# The level info should be generated in the same format pdb uses, to
|
607 |
+
# avoid breaking the pdbtrack functionality of python-mode in *emacs.
|
608 |
+
if frame is self.curframe:
|
609 |
+
ret.append('> ')
|
610 |
+
else:
|
611 |
+
ret.append(" ")
|
612 |
+
ret.append("%s(%s)%s\n" % (link, lineno, call))
|
613 |
+
|
614 |
+
start = lineno - 1 - context//2
|
615 |
+
lines = linecache.getlines(filename)
|
616 |
+
start = min(start, len(lines) - context)
|
617 |
+
start = max(start, 0)
|
618 |
+
lines = lines[start : start + context]
|
619 |
+
|
620 |
+
for i, line in enumerate(lines):
|
621 |
+
show_arrow = start + 1 + i == lineno
|
622 |
+
linetpl = (frame is self.curframe or show_arrow) and tpl_line_em or tpl_line
|
623 |
+
ret.append(
|
624 |
+
self.__format_line(
|
625 |
+
linetpl, filename, start + 1 + i, line, arrow=show_arrow
|
626 |
+
)
|
627 |
+
)
|
628 |
+
return "".join(ret)
|
629 |
+
|
630 |
+
def __format_line(self, tpl_line, filename, lineno, line, arrow=False):
|
631 |
+
bp_mark = ""
|
632 |
+
bp_mark_color = ""
|
633 |
+
|
634 |
+
new_line, err = self.parser.format2(line, 'str')
|
635 |
+
if not err:
|
636 |
+
line = new_line
|
637 |
+
|
638 |
+
bp = None
|
639 |
+
if lineno in self.get_file_breaks(filename):
|
640 |
+
bps = self.get_breaks(filename, lineno)
|
641 |
+
bp = bps[-1]
|
642 |
+
|
643 |
+
if bp:
|
644 |
+
Colors = self.color_scheme_table.active_colors
|
645 |
+
bp_mark = str(bp.number)
|
646 |
+
bp_mark_color = Colors.breakpoint_enabled
|
647 |
+
if not bp.enabled:
|
648 |
+
bp_mark_color = Colors.breakpoint_disabled
|
649 |
+
|
650 |
+
numbers_width = 7
|
651 |
+
if arrow:
|
652 |
+
# This is the line with the error
|
653 |
+
pad = numbers_width - len(str(lineno)) - len(bp_mark)
|
654 |
+
num = '%s%s' % (make_arrow(pad), str(lineno))
|
655 |
+
else:
|
656 |
+
num = '%*s' % (numbers_width - len(bp_mark), str(lineno))
|
657 |
+
|
658 |
+
return tpl_line % (bp_mark_color + bp_mark, num, line)
|
659 |
+
|
660 |
+
def print_list_lines(self, filename, first, last):
|
661 |
+
"""The printing (as opposed to the parsing part of a 'list'
|
662 |
+
command."""
|
663 |
+
try:
|
664 |
+
Colors = self.color_scheme_table.active_colors
|
665 |
+
ColorsNormal = Colors.Normal
|
666 |
+
tpl_line = '%%s%s%%s %s%%s' % (Colors.lineno, ColorsNormal)
|
667 |
+
tpl_line_em = '%%s%s%%s %s%%s%s' % (Colors.linenoEm, Colors.line, ColorsNormal)
|
668 |
+
src = []
|
669 |
+
if filename == "<string>" and hasattr(self, "_exec_filename"):
|
670 |
+
filename = self._exec_filename
|
671 |
+
|
672 |
+
for lineno in range(first, last+1):
|
673 |
+
line = linecache.getline(filename, lineno)
|
674 |
+
if not line:
|
675 |
+
break
|
676 |
+
|
677 |
+
if lineno == self.curframe.f_lineno:
|
678 |
+
line = self.__format_line(
|
679 |
+
tpl_line_em, filename, lineno, line, arrow=True
|
680 |
+
)
|
681 |
+
else:
|
682 |
+
line = self.__format_line(
|
683 |
+
tpl_line, filename, lineno, line, arrow=False
|
684 |
+
)
|
685 |
+
|
686 |
+
src.append(line)
|
687 |
+
self.lineno = lineno
|
688 |
+
|
689 |
+
print(''.join(src), file=self.stdout)
|
690 |
+
|
691 |
+
except KeyboardInterrupt:
|
692 |
+
pass
|
693 |
+
|
694 |
+
def do_skip_predicates(self, args):
|
695 |
+
"""
|
696 |
+
Turn on/off individual predicates as to whether a frame should be hidden/skip.
|
697 |
+
|
698 |
+
The global option to skip (or not) hidden frames is set with skip_hidden
|
699 |
+
|
700 |
+
To change the value of a predicate
|
701 |
+
|
702 |
+
skip_predicates key [true|false]
|
703 |
+
|
704 |
+
Call without arguments to see the current values.
|
705 |
+
|
706 |
+
To permanently change the value of an option add the corresponding
|
707 |
+
command to your ``~/.pdbrc`` file. If you are programmatically using the
|
708 |
+
Pdb instance you can also change the ``default_predicates`` class
|
709 |
+
attribute.
|
710 |
+
"""
|
711 |
+
if not args.strip():
|
712 |
+
print("current predicates:")
|
713 |
+
for p, v in self._predicates.items():
|
714 |
+
print(" ", p, ":", v)
|
715 |
+
return
|
716 |
+
type_value = args.strip().split(" ")
|
717 |
+
if len(type_value) != 2:
|
718 |
+
print(
|
719 |
+
f"Usage: skip_predicates <type> <value>, with <type> one of {set(self._predicates.keys())}"
|
720 |
+
)
|
721 |
+
return
|
722 |
+
|
723 |
+
type_, value = type_value
|
724 |
+
if type_ not in self._predicates:
|
725 |
+
print(f"{type_!r} not in {set(self._predicates.keys())}")
|
726 |
+
return
|
727 |
+
if value.lower() not in ("true", "yes", "1", "no", "false", "0"):
|
728 |
+
print(
|
729 |
+
f"{value!r} is invalid - use one of ('true', 'yes', '1', 'no', 'false', '0')"
|
730 |
+
)
|
731 |
+
return
|
732 |
+
|
733 |
+
self._predicates[type_] = value.lower() in ("true", "yes", "1")
|
734 |
+
if not any(self._predicates.values()):
|
735 |
+
print(
|
736 |
+
"Warning, all predicates set to False, skip_hidden may not have any effects."
|
737 |
+
)
|
738 |
+
|
739 |
+
def do_skip_hidden(self, arg):
|
740 |
+
"""
|
741 |
+
Change whether or not we should skip frames with the
|
742 |
+
__tracebackhide__ attribute.
|
743 |
+
"""
|
744 |
+
if not arg.strip():
|
745 |
+
print(
|
746 |
+
f"skip_hidden = {self.skip_hidden}, use 'yes','no', 'true', or 'false' to change."
|
747 |
+
)
|
748 |
+
elif arg.strip().lower() in ("true", "yes"):
|
749 |
+
self.skip_hidden = True
|
750 |
+
elif arg.strip().lower() in ("false", "no"):
|
751 |
+
self.skip_hidden = False
|
752 |
+
if not any(self._predicates.values()):
|
753 |
+
print(
|
754 |
+
"Warning, all predicates set to False, skip_hidden may not have any effects."
|
755 |
+
)
|
756 |
+
|
757 |
+
def do_list(self, arg):
|
758 |
+
"""Print lines of code from the current stack frame
|
759 |
+
"""
|
760 |
+
self.lastcmd = 'list'
|
761 |
+
last = None
|
762 |
+
if arg and arg != ".":
|
763 |
+
try:
|
764 |
+
x = eval(arg, {}, {})
|
765 |
+
if type(x) == type(()):
|
766 |
+
first, last = x
|
767 |
+
first = int(first)
|
768 |
+
last = int(last)
|
769 |
+
if last < first:
|
770 |
+
# Assume it's a count
|
771 |
+
last = first + last
|
772 |
+
else:
|
773 |
+
first = max(1, int(x) - 5)
|
774 |
+
except:
|
775 |
+
print('*** Error in argument:', repr(arg), file=self.stdout)
|
776 |
+
return
|
777 |
+
elif self.lineno is None or arg == ".":
|
778 |
+
first = max(1, self.curframe.f_lineno - 5)
|
779 |
+
else:
|
780 |
+
first = self.lineno + 1
|
781 |
+
if last is None:
|
782 |
+
last = first + 10
|
783 |
+
self.print_list_lines(self.curframe.f_code.co_filename, first, last)
|
784 |
+
|
785 |
+
# vds: >>
|
786 |
+
lineno = first
|
787 |
+
filename = self.curframe.f_code.co_filename
|
788 |
+
self.shell.hooks.synchronize_with_editor(filename, lineno, 0)
|
789 |
+
# vds: <<
|
790 |
+
|
791 |
+
do_l = do_list
|
792 |
+
|
793 |
+
def getsourcelines(self, obj):
|
794 |
+
lines, lineno = inspect.findsource(obj)
|
795 |
+
if inspect.isframe(obj) and obj.f_globals is self._get_frame_locals(obj):
|
796 |
+
# must be a module frame: do not try to cut a block out of it
|
797 |
+
return lines, 1
|
798 |
+
elif inspect.ismodule(obj):
|
799 |
+
return lines, 1
|
800 |
+
return inspect.getblock(lines[lineno:]), lineno+1
|
801 |
+
|
802 |
+
def do_longlist(self, arg):
|
803 |
+
"""Print lines of code from the current stack frame.
|
804 |
+
|
805 |
+
Shows more lines than 'list' does.
|
806 |
+
"""
|
807 |
+
self.lastcmd = 'longlist'
|
808 |
+
try:
|
809 |
+
lines, lineno = self.getsourcelines(self.curframe)
|
810 |
+
except OSError as err:
|
811 |
+
self.error(err)
|
812 |
+
return
|
813 |
+
last = lineno + len(lines)
|
814 |
+
self.print_list_lines(self.curframe.f_code.co_filename, lineno, last)
|
815 |
+
do_ll = do_longlist
|
816 |
+
|
817 |
+
def do_debug(self, arg):
|
818 |
+
"""debug code
|
819 |
+
Enter a recursive debugger that steps through the code
|
820 |
+
argument (which is an arbitrary expression or statement to be
|
821 |
+
executed in the current environment).
|
822 |
+
"""
|
823 |
+
trace_function = sys.gettrace()
|
824 |
+
sys.settrace(None)
|
825 |
+
globals = self.curframe.f_globals
|
826 |
+
locals = self.curframe_locals
|
827 |
+
p = self.__class__(completekey=self.completekey,
|
828 |
+
stdin=self.stdin, stdout=self.stdout)
|
829 |
+
p.use_rawinput = self.use_rawinput
|
830 |
+
p.prompt = "(%s) " % self.prompt.strip()
|
831 |
+
self.message("ENTERING RECURSIVE DEBUGGER")
|
832 |
+
sys.call_tracing(p.run, (arg, globals, locals))
|
833 |
+
self.message("LEAVING RECURSIVE DEBUGGER")
|
834 |
+
sys.settrace(trace_function)
|
835 |
+
self.lastcmd = p.lastcmd
|
836 |
+
|
837 |
+
def do_pdef(self, arg):
|
838 |
+
"""Print the call signature for any callable object.
|
839 |
+
|
840 |
+
The debugger interface to %pdef"""
|
841 |
+
namespaces = [
|
842 |
+
("Locals", self.curframe_locals),
|
843 |
+
("Globals", self.curframe.f_globals),
|
844 |
+
]
|
845 |
+
self.shell.find_line_magic("pdef")(arg, namespaces=namespaces)
|
846 |
+
|
847 |
+
def do_pdoc(self, arg):
|
848 |
+
"""Print the docstring for an object.
|
849 |
+
|
850 |
+
The debugger interface to %pdoc."""
|
851 |
+
namespaces = [
|
852 |
+
("Locals", self.curframe_locals),
|
853 |
+
("Globals", self.curframe.f_globals),
|
854 |
+
]
|
855 |
+
self.shell.find_line_magic("pdoc")(arg, namespaces=namespaces)
|
856 |
+
|
857 |
+
def do_pfile(self, arg):
|
858 |
+
"""Print (or run through pager) the file where an object is defined.
|
859 |
+
|
860 |
+
The debugger interface to %pfile.
|
861 |
+
"""
|
862 |
+
namespaces = [
|
863 |
+
("Locals", self.curframe_locals),
|
864 |
+
("Globals", self.curframe.f_globals),
|
865 |
+
]
|
866 |
+
self.shell.find_line_magic("pfile")(arg, namespaces=namespaces)
|
867 |
+
|
868 |
+
def do_pinfo(self, arg):
|
869 |
+
"""Provide detailed information about an object.
|
870 |
+
|
871 |
+
The debugger interface to %pinfo, i.e., obj?."""
|
872 |
+
namespaces = [
|
873 |
+
("Locals", self.curframe_locals),
|
874 |
+
("Globals", self.curframe.f_globals),
|
875 |
+
]
|
876 |
+
self.shell.find_line_magic("pinfo")(arg, namespaces=namespaces)
|
877 |
+
|
878 |
+
def do_pinfo2(self, arg):
|
879 |
+
"""Provide extra detailed information about an object.
|
880 |
+
|
881 |
+
The debugger interface to %pinfo2, i.e., obj??."""
|
882 |
+
namespaces = [
|
883 |
+
("Locals", self.curframe_locals),
|
884 |
+
("Globals", self.curframe.f_globals),
|
885 |
+
]
|
886 |
+
self.shell.find_line_magic("pinfo2")(arg, namespaces=namespaces)
|
887 |
+
|
888 |
+
def do_psource(self, arg):
|
889 |
+
"""Print (or run through pager) the source code for an object."""
|
890 |
+
namespaces = [
|
891 |
+
("Locals", self.curframe_locals),
|
892 |
+
("Globals", self.curframe.f_globals),
|
893 |
+
]
|
894 |
+
self.shell.find_line_magic("psource")(arg, namespaces=namespaces)
|
895 |
+
|
896 |
+
def do_where(self, arg):
|
897 |
+
"""w(here)
|
898 |
+
Print a stack trace, with the most recent frame at the bottom.
|
899 |
+
An arrow indicates the "current frame", which determines the
|
900 |
+
context of most commands. 'bt' is an alias for this command.
|
901 |
+
|
902 |
+
Take a number as argument as an (optional) number of context line to
|
903 |
+
print"""
|
904 |
+
if arg:
|
905 |
+
try:
|
906 |
+
context = int(arg)
|
907 |
+
except ValueError as err:
|
908 |
+
self.error(err)
|
909 |
+
return
|
910 |
+
self.print_stack_trace(context)
|
911 |
+
else:
|
912 |
+
self.print_stack_trace()
|
913 |
+
|
914 |
+
do_w = do_where
|
915 |
+
|
916 |
+
def break_anywhere(self, frame):
|
917 |
+
"""
|
918 |
+
_stop_in_decorator_internals is overly restrictive, as we may still want
|
919 |
+
to trace function calls, so we need to also update break_anywhere so
|
920 |
+
that is we don't `stop_here`, because of debugger skip, we may still
|
921 |
+
stop at any point inside the function
|
922 |
+
|
923 |
+
"""
|
924 |
+
|
925 |
+
sup = super().break_anywhere(frame)
|
926 |
+
if sup:
|
927 |
+
return sup
|
928 |
+
if self._predicates["debuggerskip"]:
|
929 |
+
if DEBUGGERSKIP in frame.f_code.co_varnames:
|
930 |
+
return True
|
931 |
+
if frame.f_back and self._get_frame_locals(frame.f_back).get(DEBUGGERSKIP):
|
932 |
+
return True
|
933 |
+
return False
|
934 |
+
|
935 |
+
def _is_in_decorator_internal_and_should_skip(self, frame):
|
936 |
+
"""
|
937 |
+
Utility to tell us whether we are in a decorator internal and should stop.
|
938 |
+
|
939 |
+
"""
|
940 |
+
# if we are disabled don't skip
|
941 |
+
if not self._predicates["debuggerskip"]:
|
942 |
+
return False
|
943 |
+
|
944 |
+
return self._cachable_skip(frame)
|
945 |
+
|
946 |
+
@lru_cache(1024)
|
947 |
+
def _cached_one_parent_frame_debuggerskip(self, frame):
|
948 |
+
"""
|
949 |
+
Cache looking up for DEBUGGERSKIP on parent frame.
|
950 |
+
|
951 |
+
This should speedup walking through deep frame when one of the highest
|
952 |
+
one does have a debugger skip.
|
953 |
+
|
954 |
+
This is likely to introduce fake positive though.
|
955 |
+
"""
|
956 |
+
while getattr(frame, "f_back", None):
|
957 |
+
frame = frame.f_back
|
958 |
+
if self._get_frame_locals(frame).get(DEBUGGERSKIP):
|
959 |
+
return True
|
960 |
+
return None
|
961 |
+
|
962 |
+
@lru_cache(1024)
|
963 |
+
def _cachable_skip(self, frame):
|
964 |
+
# if frame is tagged, skip by default.
|
965 |
+
if DEBUGGERSKIP in frame.f_code.co_varnames:
|
966 |
+
return True
|
967 |
+
|
968 |
+
# if one of the parent frame value set to True skip as well.
|
969 |
+
if self._cached_one_parent_frame_debuggerskip(frame):
|
970 |
+
return True
|
971 |
+
|
972 |
+
return False
|
973 |
+
|
974 |
+
def stop_here(self, frame):
|
975 |
+
if self._is_in_decorator_internal_and_should_skip(frame) is True:
|
976 |
+
return False
|
977 |
+
|
978 |
+
hidden = False
|
979 |
+
if self.skip_hidden:
|
980 |
+
hidden = self._hidden_predicate(frame)
|
981 |
+
if hidden:
|
982 |
+
if self.report_skipped:
|
983 |
+
Colors = self.color_scheme_table.active_colors
|
984 |
+
ColorsNormal = Colors.Normal
|
985 |
+
print(
|
986 |
+
f"{Colors.excName} [... skipped 1 hidden frame]{ColorsNormal}\n"
|
987 |
+
)
|
988 |
+
return super().stop_here(frame)
|
989 |
+
|
990 |
+
def do_up(self, arg):
|
991 |
+
"""u(p) [count]
|
992 |
+
Move the current frame count (default one) levels up in the
|
993 |
+
stack trace (to an older frame).
|
994 |
+
|
995 |
+
Will skip hidden frames.
|
996 |
+
"""
|
997 |
+
# modified version of upstream that skips
|
998 |
+
# frames with __tracebackhide__
|
999 |
+
if self.curindex == 0:
|
1000 |
+
self.error("Oldest frame")
|
1001 |
+
return
|
1002 |
+
try:
|
1003 |
+
count = int(arg or 1)
|
1004 |
+
except ValueError:
|
1005 |
+
self.error("Invalid frame count (%s)" % arg)
|
1006 |
+
return
|
1007 |
+
skipped = 0
|
1008 |
+
if count < 0:
|
1009 |
+
_newframe = 0
|
1010 |
+
else:
|
1011 |
+
counter = 0
|
1012 |
+
hidden_frames = self.hidden_frames(self.stack)
|
1013 |
+
for i in range(self.curindex - 1, -1, -1):
|
1014 |
+
if hidden_frames[i] and self.skip_hidden:
|
1015 |
+
skipped += 1
|
1016 |
+
continue
|
1017 |
+
counter += 1
|
1018 |
+
if counter >= count:
|
1019 |
+
break
|
1020 |
+
else:
|
1021 |
+
# if no break occurred.
|
1022 |
+
self.error(
|
1023 |
+
"all frames above hidden, use `skip_hidden False` to get get into those."
|
1024 |
+
)
|
1025 |
+
return
|
1026 |
+
|
1027 |
+
Colors = self.color_scheme_table.active_colors
|
1028 |
+
ColorsNormal = Colors.Normal
|
1029 |
+
_newframe = i
|
1030 |
+
self._select_frame(_newframe)
|
1031 |
+
if skipped:
|
1032 |
+
print(
|
1033 |
+
f"{Colors.excName} [... skipped {skipped} hidden frame(s)]{ColorsNormal}\n"
|
1034 |
+
)
|
1035 |
+
|
1036 |
+
def do_down(self, arg):
|
1037 |
+
"""d(own) [count]
|
1038 |
+
Move the current frame count (default one) levels down in the
|
1039 |
+
stack trace (to a newer frame).
|
1040 |
+
|
1041 |
+
Will skip hidden frames.
|
1042 |
+
"""
|
1043 |
+
if self.curindex + 1 == len(self.stack):
|
1044 |
+
self.error("Newest frame")
|
1045 |
+
return
|
1046 |
+
try:
|
1047 |
+
count = int(arg or 1)
|
1048 |
+
except ValueError:
|
1049 |
+
self.error("Invalid frame count (%s)" % arg)
|
1050 |
+
return
|
1051 |
+
if count < 0:
|
1052 |
+
_newframe = len(self.stack) - 1
|
1053 |
+
else:
|
1054 |
+
counter = 0
|
1055 |
+
skipped = 0
|
1056 |
+
hidden_frames = self.hidden_frames(self.stack)
|
1057 |
+
for i in range(self.curindex + 1, len(self.stack)):
|
1058 |
+
if hidden_frames[i] and self.skip_hidden:
|
1059 |
+
skipped += 1
|
1060 |
+
continue
|
1061 |
+
counter += 1
|
1062 |
+
if counter >= count:
|
1063 |
+
break
|
1064 |
+
else:
|
1065 |
+
self.error(
|
1066 |
+
"all frames below hidden, use `skip_hidden False` to get get into those."
|
1067 |
+
)
|
1068 |
+
return
|
1069 |
+
|
1070 |
+
Colors = self.color_scheme_table.active_colors
|
1071 |
+
ColorsNormal = Colors.Normal
|
1072 |
+
if skipped:
|
1073 |
+
print(
|
1074 |
+
f"{Colors.excName} [... skipped {skipped} hidden frame(s)]{ColorsNormal}\n"
|
1075 |
+
)
|
1076 |
+
_newframe = i
|
1077 |
+
|
1078 |
+
self._select_frame(_newframe)
|
1079 |
+
|
1080 |
+
do_d = do_down
|
1081 |
+
do_u = do_up
|
1082 |
+
|
1083 |
+
def do_context(self, context):
|
1084 |
+
"""context number_of_lines
|
1085 |
+
Set the number of lines of source code to show when displaying
|
1086 |
+
stacktrace information.
|
1087 |
+
"""
|
1088 |
+
try:
|
1089 |
+
new_context = int(context)
|
1090 |
+
if new_context <= 0:
|
1091 |
+
raise ValueError()
|
1092 |
+
self.context = new_context
|
1093 |
+
except ValueError:
|
1094 |
+
self.error(
|
1095 |
+
f"The 'context' command requires a positive integer argument (current value {self.context})."
|
1096 |
+
)
|
1097 |
+
|
1098 |
+
|
1099 |
+
class InterruptiblePdb(Pdb):
|
1100 |
+
"""Version of debugger where KeyboardInterrupt exits the debugger altogether."""
|
1101 |
+
|
1102 |
+
def cmdloop(self, intro=None):
|
1103 |
+
"""Wrap cmdloop() such that KeyboardInterrupt stops the debugger."""
|
1104 |
+
try:
|
1105 |
+
return OldPdb.cmdloop(self, intro=intro)
|
1106 |
+
except KeyboardInterrupt:
|
1107 |
+
self.stop_here = lambda frame: False
|
1108 |
+
self.do_quit("")
|
1109 |
+
sys.settrace(None)
|
1110 |
+
self.quitting = False
|
1111 |
+
raise
|
1112 |
+
|
1113 |
+
def _cmdloop(self):
|
1114 |
+
while True:
|
1115 |
+
try:
|
1116 |
+
# keyboard interrupts allow for an easy way to cancel
|
1117 |
+
# the current command, so allow them during interactive input
|
1118 |
+
self.allow_kbdint = True
|
1119 |
+
self.cmdloop()
|
1120 |
+
self.allow_kbdint = False
|
1121 |
+
break
|
1122 |
+
except KeyboardInterrupt:
|
1123 |
+
self.message('--KeyboardInterrupt--')
|
1124 |
+
raise
|
1125 |
+
|
1126 |
+
|
1127 |
+
def set_trace(frame=None, header=None):
|
1128 |
+
"""
|
1129 |
+
Start debugging from `frame`.
|
1130 |
+
|
1131 |
+
If frame is not specified, debugging starts from caller's frame.
|
1132 |
+
"""
|
1133 |
+
pdb = Pdb()
|
1134 |
+
if header is not None:
|
1135 |
+
pdb.message(header)
|
1136 |
+
pdb.set_trace(frame or sys._getframe().f_back)
|
openalex_env_map/lib/python3.10/site-packages/IPython/core/display.py
ADDED
@@ -0,0 +1,1373 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# -*- coding: utf-8 -*-
|
2 |
+
"""Top-level display functions for displaying object in different formats."""
|
3 |
+
|
4 |
+
# Copyright (c) IPython Development Team.
|
5 |
+
# Distributed under the terms of the Modified BSD License.
|
6 |
+
|
7 |
+
|
8 |
+
from binascii import b2a_base64, hexlify
|
9 |
+
import html
|
10 |
+
import json
|
11 |
+
import mimetypes
|
12 |
+
import os
|
13 |
+
import struct
|
14 |
+
import warnings
|
15 |
+
from copy import deepcopy
|
16 |
+
from os.path import splitext
|
17 |
+
from pathlib import Path, PurePath
|
18 |
+
|
19 |
+
from typing import Optional
|
20 |
+
|
21 |
+
from IPython.testing.skipdoctest import skip_doctest
|
22 |
+
from . import display_functions
|
23 |
+
|
24 |
+
|
25 |
+
__all__ = [
|
26 |
+
"display_pretty",
|
27 |
+
"display_html",
|
28 |
+
"display_markdown",
|
29 |
+
"display_svg",
|
30 |
+
"display_png",
|
31 |
+
"display_jpeg",
|
32 |
+
"display_webp",
|
33 |
+
"display_latex",
|
34 |
+
"display_json",
|
35 |
+
"display_javascript",
|
36 |
+
"display_pdf",
|
37 |
+
"DisplayObject",
|
38 |
+
"TextDisplayObject",
|
39 |
+
"Pretty",
|
40 |
+
"HTML",
|
41 |
+
"Markdown",
|
42 |
+
"Math",
|
43 |
+
"Latex",
|
44 |
+
"SVG",
|
45 |
+
"ProgressBar",
|
46 |
+
"JSON",
|
47 |
+
"GeoJSON",
|
48 |
+
"Javascript",
|
49 |
+
"Image",
|
50 |
+
"set_matplotlib_formats",
|
51 |
+
"set_matplotlib_close",
|
52 |
+
"Video",
|
53 |
+
]
|
54 |
+
|
55 |
+
_deprecated_names = ["display", "clear_output", "publish_display_data", "update_display", "DisplayHandle"]
|
56 |
+
|
57 |
+
__all__ = __all__ + _deprecated_names
|
58 |
+
|
59 |
+
|
60 |
+
# ----- warn to import from IPython.display -----
|
61 |
+
|
62 |
+
from warnings import warn
|
63 |
+
|
64 |
+
|
65 |
+
def __getattr__(name):
|
66 |
+
if name in _deprecated_names:
|
67 |
+
warn(
|
68 |
+
f"Importing {name} from IPython.core.display is deprecated since IPython 7.14, please import from IPython.display",
|
69 |
+
DeprecationWarning,
|
70 |
+
stacklevel=2,
|
71 |
+
)
|
72 |
+
return getattr(display_functions, name)
|
73 |
+
|
74 |
+
if name in globals().keys():
|
75 |
+
return globals()[name]
|
76 |
+
else:
|
77 |
+
raise AttributeError(f"module {__name__} has no attribute {name}")
|
78 |
+
|
79 |
+
|
80 |
+
#-----------------------------------------------------------------------------
|
81 |
+
# utility functions
|
82 |
+
#-----------------------------------------------------------------------------
|
83 |
+
|
84 |
+
def _safe_exists(path):
|
85 |
+
"""Check path, but don't let exceptions raise"""
|
86 |
+
try:
|
87 |
+
return os.path.exists(path)
|
88 |
+
except Exception:
|
89 |
+
return False
|
90 |
+
|
91 |
+
|
92 |
+
def _display_mimetype(mimetype, objs, raw=False, metadata=None):
|
93 |
+
"""internal implementation of all display_foo methods
|
94 |
+
|
95 |
+
Parameters
|
96 |
+
----------
|
97 |
+
mimetype : str
|
98 |
+
The mimetype to be published (e.g. 'image/png')
|
99 |
+
*objs : object
|
100 |
+
The Python objects to display, or if raw=True raw text data to
|
101 |
+
display.
|
102 |
+
raw : bool
|
103 |
+
Are the data objects raw data or Python objects that need to be
|
104 |
+
formatted before display? [default: False]
|
105 |
+
metadata : dict (optional)
|
106 |
+
Metadata to be associated with the specific mimetype output.
|
107 |
+
"""
|
108 |
+
if metadata:
|
109 |
+
metadata = {mimetype: metadata}
|
110 |
+
if raw:
|
111 |
+
# turn list of pngdata into list of { 'image/png': pngdata }
|
112 |
+
objs = [ {mimetype: obj} for obj in objs ]
|
113 |
+
display_functions.display(*objs, raw=raw, metadata=metadata, include=[mimetype])
|
114 |
+
|
115 |
+
#-----------------------------------------------------------------------------
|
116 |
+
# Main functions
|
117 |
+
#-----------------------------------------------------------------------------
|
118 |
+
|
119 |
+
|
120 |
+
def display_pretty(*objs, **kwargs):
|
121 |
+
"""Display the pretty (default) representation of an object.
|
122 |
+
|
123 |
+
Parameters
|
124 |
+
----------
|
125 |
+
*objs : object
|
126 |
+
The Python objects to display, or if raw=True raw text data to
|
127 |
+
display.
|
128 |
+
raw : bool
|
129 |
+
Are the data objects raw data or Python objects that need to be
|
130 |
+
formatted before display? [default: False]
|
131 |
+
metadata : dict (optional)
|
132 |
+
Metadata to be associated with the specific mimetype output.
|
133 |
+
"""
|
134 |
+
_display_mimetype('text/plain', objs, **kwargs)
|
135 |
+
|
136 |
+
|
137 |
+
def display_html(*objs, **kwargs):
|
138 |
+
"""Display the HTML representation of an object.
|
139 |
+
|
140 |
+
Note: If raw=False and the object does not have a HTML
|
141 |
+
representation, no HTML will be shown.
|
142 |
+
|
143 |
+
Parameters
|
144 |
+
----------
|
145 |
+
*objs : object
|
146 |
+
The Python objects to display, or if raw=True raw HTML data to
|
147 |
+
display.
|
148 |
+
raw : bool
|
149 |
+
Are the data objects raw data or Python objects that need to be
|
150 |
+
formatted before display? [default: False]
|
151 |
+
metadata : dict (optional)
|
152 |
+
Metadata to be associated with the specific mimetype output.
|
153 |
+
"""
|
154 |
+
_display_mimetype('text/html', objs, **kwargs)
|
155 |
+
|
156 |
+
|
157 |
+
def display_markdown(*objs, **kwargs):
|
158 |
+
"""Displays the Markdown representation of an object.
|
159 |
+
|
160 |
+
Parameters
|
161 |
+
----------
|
162 |
+
*objs : object
|
163 |
+
The Python objects to display, or if raw=True raw markdown data to
|
164 |
+
display.
|
165 |
+
raw : bool
|
166 |
+
Are the data objects raw data or Python objects that need to be
|
167 |
+
formatted before display? [default: False]
|
168 |
+
metadata : dict (optional)
|
169 |
+
Metadata to be associated with the specific mimetype output.
|
170 |
+
"""
|
171 |
+
|
172 |
+
_display_mimetype('text/markdown', objs, **kwargs)
|
173 |
+
|
174 |
+
|
175 |
+
def display_svg(*objs, **kwargs):
|
176 |
+
"""Display the SVG representation of an object.
|
177 |
+
|
178 |
+
Parameters
|
179 |
+
----------
|
180 |
+
*objs : object
|
181 |
+
The Python objects to display, or if raw=True raw svg data to
|
182 |
+
display.
|
183 |
+
raw : bool
|
184 |
+
Are the data objects raw data or Python objects that need to be
|
185 |
+
formatted before display? [default: False]
|
186 |
+
metadata : dict (optional)
|
187 |
+
Metadata to be associated with the specific mimetype output.
|
188 |
+
"""
|
189 |
+
_display_mimetype('image/svg+xml', objs, **kwargs)
|
190 |
+
|
191 |
+
|
192 |
+
def display_png(*objs, **kwargs):
|
193 |
+
"""Display the PNG representation of an object.
|
194 |
+
|
195 |
+
Parameters
|
196 |
+
----------
|
197 |
+
*objs : object
|
198 |
+
The Python objects to display, or if raw=True raw png data to
|
199 |
+
display.
|
200 |
+
raw : bool
|
201 |
+
Are the data objects raw data or Python objects that need to be
|
202 |
+
formatted before display? [default: False]
|
203 |
+
metadata : dict (optional)
|
204 |
+
Metadata to be associated with the specific mimetype output.
|
205 |
+
"""
|
206 |
+
_display_mimetype('image/png', objs, **kwargs)
|
207 |
+
|
208 |
+
|
209 |
+
def display_jpeg(*objs, **kwargs):
|
210 |
+
"""Display the JPEG representation of an object.
|
211 |
+
|
212 |
+
Parameters
|
213 |
+
----------
|
214 |
+
*objs : object
|
215 |
+
The Python objects to display, or if raw=True raw JPEG data to
|
216 |
+
display.
|
217 |
+
raw : bool
|
218 |
+
Are the data objects raw data or Python objects that need to be
|
219 |
+
formatted before display? [default: False]
|
220 |
+
metadata : dict (optional)
|
221 |
+
Metadata to be associated with the specific mimetype output.
|
222 |
+
"""
|
223 |
+
_display_mimetype('image/jpeg', objs, **kwargs)
|
224 |
+
|
225 |
+
|
226 |
+
def display_webp(*objs, **kwargs):
|
227 |
+
"""Display the WEBP representation of an object.
|
228 |
+
|
229 |
+
Parameters
|
230 |
+
----------
|
231 |
+
*objs : object
|
232 |
+
The Python objects to display, or if raw=True raw JPEG data to
|
233 |
+
display.
|
234 |
+
raw : bool
|
235 |
+
Are the data objects raw data or Python objects that need to be
|
236 |
+
formatted before display? [default: False]
|
237 |
+
metadata : dict (optional)
|
238 |
+
Metadata to be associated with the specific mimetype output.
|
239 |
+
"""
|
240 |
+
_display_mimetype("image/webp", objs, **kwargs)
|
241 |
+
|
242 |
+
|
243 |
+
def display_latex(*objs, **kwargs):
|
244 |
+
"""Display the LaTeX representation of an object.
|
245 |
+
|
246 |
+
Parameters
|
247 |
+
----------
|
248 |
+
*objs : object
|
249 |
+
The Python objects to display, or if raw=True raw latex data to
|
250 |
+
display.
|
251 |
+
raw : bool
|
252 |
+
Are the data objects raw data or Python objects that need to be
|
253 |
+
formatted before display? [default: False]
|
254 |
+
metadata : dict (optional)
|
255 |
+
Metadata to be associated with the specific mimetype output.
|
256 |
+
"""
|
257 |
+
_display_mimetype('text/latex', objs, **kwargs)
|
258 |
+
|
259 |
+
|
260 |
+
def display_json(*objs, **kwargs):
|
261 |
+
"""Display the JSON representation of an object.
|
262 |
+
|
263 |
+
Note that not many frontends support displaying JSON.
|
264 |
+
|
265 |
+
Parameters
|
266 |
+
----------
|
267 |
+
*objs : object
|
268 |
+
The Python objects to display, or if raw=True raw json data to
|
269 |
+
display.
|
270 |
+
raw : bool
|
271 |
+
Are the data objects raw data or Python objects that need to be
|
272 |
+
formatted before display? [default: False]
|
273 |
+
metadata : dict (optional)
|
274 |
+
Metadata to be associated with the specific mimetype output.
|
275 |
+
"""
|
276 |
+
_display_mimetype('application/json', objs, **kwargs)
|
277 |
+
|
278 |
+
|
279 |
+
def display_javascript(*objs, **kwargs):
|
280 |
+
"""Display the Javascript representation of an object.
|
281 |
+
|
282 |
+
Parameters
|
283 |
+
----------
|
284 |
+
*objs : object
|
285 |
+
The Python objects to display, or if raw=True raw javascript data to
|
286 |
+
display.
|
287 |
+
raw : bool
|
288 |
+
Are the data objects raw data or Python objects that need to be
|
289 |
+
formatted before display? [default: False]
|
290 |
+
metadata : dict (optional)
|
291 |
+
Metadata to be associated with the specific mimetype output.
|
292 |
+
"""
|
293 |
+
_display_mimetype('application/javascript', objs, **kwargs)
|
294 |
+
|
295 |
+
|
296 |
+
def display_pdf(*objs, **kwargs):
|
297 |
+
"""Display the PDF representation of an object.
|
298 |
+
|
299 |
+
Parameters
|
300 |
+
----------
|
301 |
+
*objs : object
|
302 |
+
The Python objects to display, or if raw=True raw javascript data to
|
303 |
+
display.
|
304 |
+
raw : bool
|
305 |
+
Are the data objects raw data or Python objects that need to be
|
306 |
+
formatted before display? [default: False]
|
307 |
+
metadata : dict (optional)
|
308 |
+
Metadata to be associated with the specific mimetype output.
|
309 |
+
"""
|
310 |
+
_display_mimetype('application/pdf', objs, **kwargs)
|
311 |
+
|
312 |
+
|
313 |
+
#-----------------------------------------------------------------------------
|
314 |
+
# Smart classes
|
315 |
+
#-----------------------------------------------------------------------------
|
316 |
+
|
317 |
+
|
318 |
+
class DisplayObject(object):
|
319 |
+
"""An object that wraps data to be displayed."""
|
320 |
+
|
321 |
+
_read_flags = 'r'
|
322 |
+
_show_mem_addr = False
|
323 |
+
metadata = None
|
324 |
+
|
325 |
+
def __init__(self, data=None, url=None, filename=None, metadata=None):
|
326 |
+
"""Create a display object given raw data.
|
327 |
+
|
328 |
+
When this object is returned by an expression or passed to the
|
329 |
+
display function, it will result in the data being displayed
|
330 |
+
in the frontend. The MIME type of the data should match the
|
331 |
+
subclasses used, so the Png subclass should be used for 'image/png'
|
332 |
+
data. If the data is a URL, the data will first be downloaded
|
333 |
+
and then displayed.
|
334 |
+
|
335 |
+
Parameters
|
336 |
+
----------
|
337 |
+
data : unicode, str or bytes
|
338 |
+
The raw data or a URL or file to load the data from
|
339 |
+
url : unicode
|
340 |
+
A URL to download the data from.
|
341 |
+
filename : unicode
|
342 |
+
Path to a local file to load the data from.
|
343 |
+
metadata : dict
|
344 |
+
Dict of metadata associated to be the object when displayed
|
345 |
+
"""
|
346 |
+
if isinstance(data, (Path, PurePath)):
|
347 |
+
data = str(data)
|
348 |
+
|
349 |
+
if data is not None and isinstance(data, str):
|
350 |
+
if data.startswith('http') and url is None:
|
351 |
+
url = data
|
352 |
+
filename = None
|
353 |
+
data = None
|
354 |
+
elif _safe_exists(data) and filename is None:
|
355 |
+
url = None
|
356 |
+
filename = data
|
357 |
+
data = None
|
358 |
+
|
359 |
+
self.url = url
|
360 |
+
self.filename = filename
|
361 |
+
# because of @data.setter methods in
|
362 |
+
# subclasses ensure url and filename are set
|
363 |
+
# before assigning to self.data
|
364 |
+
self.data = data
|
365 |
+
|
366 |
+
if metadata is not None:
|
367 |
+
self.metadata = metadata
|
368 |
+
elif self.metadata is None:
|
369 |
+
self.metadata = {}
|
370 |
+
|
371 |
+
self.reload()
|
372 |
+
self._check_data()
|
373 |
+
|
374 |
+
def __repr__(self):
|
375 |
+
if not self._show_mem_addr:
|
376 |
+
cls = self.__class__
|
377 |
+
r = "<%s.%s object>" % (cls.__module__, cls.__name__)
|
378 |
+
else:
|
379 |
+
r = super(DisplayObject, self).__repr__()
|
380 |
+
return r
|
381 |
+
|
382 |
+
def _check_data(self):
|
383 |
+
"""Override in subclasses if there's something to check."""
|
384 |
+
pass
|
385 |
+
|
386 |
+
def _data_and_metadata(self):
|
387 |
+
"""shortcut for returning metadata with shape information, if defined"""
|
388 |
+
if self.metadata:
|
389 |
+
return self.data, deepcopy(self.metadata)
|
390 |
+
else:
|
391 |
+
return self.data
|
392 |
+
|
393 |
+
def reload(self):
|
394 |
+
"""Reload the raw data from file or URL."""
|
395 |
+
if self.filename is not None:
|
396 |
+
encoding = None if "b" in self._read_flags else "utf-8"
|
397 |
+
with open(self.filename, self._read_flags, encoding=encoding) as f:
|
398 |
+
self.data = f.read()
|
399 |
+
elif self.url is not None:
|
400 |
+
# Deferred import
|
401 |
+
from urllib.request import urlopen
|
402 |
+
response = urlopen(self.url)
|
403 |
+
data = response.read()
|
404 |
+
# extract encoding from header, if there is one:
|
405 |
+
encoding = None
|
406 |
+
if 'content-type' in response.headers:
|
407 |
+
for sub in response.headers['content-type'].split(';'):
|
408 |
+
sub = sub.strip()
|
409 |
+
if sub.startswith('charset'):
|
410 |
+
encoding = sub.split('=')[-1].strip()
|
411 |
+
break
|
412 |
+
if 'content-encoding' in response.headers:
|
413 |
+
# TODO: do deflate?
|
414 |
+
if 'gzip' in response.headers['content-encoding']:
|
415 |
+
import gzip
|
416 |
+
from io import BytesIO
|
417 |
+
|
418 |
+
# assume utf-8 if encoding is not specified
|
419 |
+
with gzip.open(
|
420 |
+
BytesIO(data), "rt", encoding=encoding or "utf-8"
|
421 |
+
) as fp:
|
422 |
+
encoding = None
|
423 |
+
data = fp.read()
|
424 |
+
|
425 |
+
# decode data, if an encoding was specified
|
426 |
+
# We only touch self.data once since
|
427 |
+
# subclasses such as SVG have @data.setter methods
|
428 |
+
# that transform self.data into ... well svg.
|
429 |
+
if encoding:
|
430 |
+
self.data = data.decode(encoding, 'replace')
|
431 |
+
else:
|
432 |
+
self.data = data
|
433 |
+
|
434 |
+
|
435 |
+
class TextDisplayObject(DisplayObject):
|
436 |
+
"""Create a text display object given raw data.
|
437 |
+
|
438 |
+
Parameters
|
439 |
+
----------
|
440 |
+
data : str or unicode
|
441 |
+
The raw data or a URL or file to load the data from.
|
442 |
+
url : unicode
|
443 |
+
A URL to download the data from.
|
444 |
+
filename : unicode
|
445 |
+
Path to a local file to load the data from.
|
446 |
+
metadata : dict
|
447 |
+
Dict of metadata associated to be the object when displayed
|
448 |
+
"""
|
449 |
+
def _check_data(self):
|
450 |
+
if self.data is not None and not isinstance(self.data, str):
|
451 |
+
raise TypeError("%s expects text, not %r" % (self.__class__.__name__, self.data))
|
452 |
+
|
453 |
+
class Pretty(TextDisplayObject):
|
454 |
+
|
455 |
+
def _repr_pretty_(self, pp, cycle):
|
456 |
+
return pp.text(self.data)
|
457 |
+
|
458 |
+
|
459 |
+
class HTML(TextDisplayObject):
|
460 |
+
|
461 |
+
def __init__(self, data=None, url=None, filename=None, metadata=None):
|
462 |
+
def warn():
|
463 |
+
if not data:
|
464 |
+
return False
|
465 |
+
|
466 |
+
#
|
467 |
+
# Avoid calling lower() on the entire data, because it could be a
|
468 |
+
# long string and we're only interested in its beginning and end.
|
469 |
+
#
|
470 |
+
prefix = data[:10].lower()
|
471 |
+
suffix = data[-10:].lower()
|
472 |
+
return prefix.startswith("<iframe ") and suffix.endswith("</iframe>")
|
473 |
+
|
474 |
+
if warn():
|
475 |
+
warnings.warn("Consider using IPython.display.IFrame instead")
|
476 |
+
super(HTML, self).__init__(data=data, url=url, filename=filename, metadata=metadata)
|
477 |
+
|
478 |
+
def _repr_html_(self):
|
479 |
+
return self._data_and_metadata()
|
480 |
+
|
481 |
+
def __html__(self):
|
482 |
+
"""
|
483 |
+
This method exists to inform other HTML-using modules (e.g. Markupsafe,
|
484 |
+
htmltag, etc) that this object is HTML and does not need things like
|
485 |
+
special characters (<>&) escaped.
|
486 |
+
"""
|
487 |
+
return self._repr_html_()
|
488 |
+
|
489 |
+
|
490 |
+
class Markdown(TextDisplayObject):
|
491 |
+
|
492 |
+
def _repr_markdown_(self):
|
493 |
+
return self._data_and_metadata()
|
494 |
+
|
495 |
+
|
496 |
+
class Math(TextDisplayObject):
|
497 |
+
|
498 |
+
def _repr_latex_(self):
|
499 |
+
s = r"$\displaystyle %s$" % self.data.strip('$')
|
500 |
+
if self.metadata:
|
501 |
+
return s, deepcopy(self.metadata)
|
502 |
+
else:
|
503 |
+
return s
|
504 |
+
|
505 |
+
|
506 |
+
class Latex(TextDisplayObject):
|
507 |
+
|
508 |
+
def _repr_latex_(self):
|
509 |
+
return self._data_and_metadata()
|
510 |
+
|
511 |
+
|
512 |
+
class SVG(DisplayObject):
|
513 |
+
"""Embed an SVG into the display.
|
514 |
+
|
515 |
+
Note if you just want to view a svg image via a URL use `:class:Image` with
|
516 |
+
a url=URL keyword argument.
|
517 |
+
"""
|
518 |
+
|
519 |
+
_read_flags = 'rb'
|
520 |
+
# wrap data in a property, which extracts the <svg> tag, discarding
|
521 |
+
# document headers
|
522 |
+
_data: Optional[str] = None
|
523 |
+
|
524 |
+
@property
|
525 |
+
def data(self):
|
526 |
+
return self._data
|
527 |
+
|
528 |
+
@data.setter
|
529 |
+
def data(self, svg):
|
530 |
+
if svg is None:
|
531 |
+
self._data = None
|
532 |
+
return
|
533 |
+
# parse into dom object
|
534 |
+
from xml.dom import minidom
|
535 |
+
x = minidom.parseString(svg)
|
536 |
+
# get svg tag (should be 1)
|
537 |
+
found_svg = x.getElementsByTagName('svg')
|
538 |
+
if found_svg:
|
539 |
+
svg = found_svg[0].toxml()
|
540 |
+
else:
|
541 |
+
# fallback on the input, trust the user
|
542 |
+
# but this is probably an error.
|
543 |
+
pass
|
544 |
+
if isinstance(svg, bytes):
|
545 |
+
self._data = svg.decode(errors="replace")
|
546 |
+
else:
|
547 |
+
self._data = svg
|
548 |
+
|
549 |
+
def _repr_svg_(self):
|
550 |
+
return self._data_and_metadata()
|
551 |
+
|
552 |
+
class ProgressBar(DisplayObject):
|
553 |
+
"""Progressbar supports displaying a progressbar like element
|
554 |
+
"""
|
555 |
+
def __init__(self, total):
|
556 |
+
"""Creates a new progressbar
|
557 |
+
|
558 |
+
Parameters
|
559 |
+
----------
|
560 |
+
total : int
|
561 |
+
maximum size of the progressbar
|
562 |
+
"""
|
563 |
+
self.total = total
|
564 |
+
self._progress = 0
|
565 |
+
self.html_width = '60ex'
|
566 |
+
self.text_width = 60
|
567 |
+
self._display_id = hexlify(os.urandom(8)).decode('ascii')
|
568 |
+
|
569 |
+
def __repr__(self):
|
570 |
+
fraction = self.progress / self.total
|
571 |
+
filled = '=' * int(fraction * self.text_width)
|
572 |
+
rest = ' ' * (self.text_width - len(filled))
|
573 |
+
return '[{}{}] {}/{}'.format(
|
574 |
+
filled, rest,
|
575 |
+
self.progress, self.total,
|
576 |
+
)
|
577 |
+
|
578 |
+
def _repr_html_(self):
|
579 |
+
return "<progress style='width:{}' max='{}' value='{}'></progress>".format(
|
580 |
+
self.html_width, self.total, self.progress)
|
581 |
+
|
582 |
+
def display(self):
|
583 |
+
display_functions.display(self, display_id=self._display_id)
|
584 |
+
|
585 |
+
def update(self):
|
586 |
+
display_functions.display(self, display_id=self._display_id, update=True)
|
587 |
+
|
588 |
+
@property
|
589 |
+
def progress(self):
|
590 |
+
return self._progress
|
591 |
+
|
592 |
+
@progress.setter
|
593 |
+
def progress(self, value):
|
594 |
+
self._progress = value
|
595 |
+
self.update()
|
596 |
+
|
597 |
+
def __iter__(self):
|
598 |
+
self.display()
|
599 |
+
self._progress = -1 # First iteration is 0
|
600 |
+
return self
|
601 |
+
|
602 |
+
def __next__(self):
|
603 |
+
"""Returns current value and increments display by one."""
|
604 |
+
self.progress += 1
|
605 |
+
if self.progress < self.total:
|
606 |
+
return self.progress
|
607 |
+
else:
|
608 |
+
raise StopIteration()
|
609 |
+
|
610 |
+
class JSON(DisplayObject):
|
611 |
+
"""JSON expects a JSON-able dict or list
|
612 |
+
|
613 |
+
not an already-serialized JSON string.
|
614 |
+
|
615 |
+
Scalar types (None, number, string) are not allowed, only dict or list containers.
|
616 |
+
"""
|
617 |
+
# wrap data in a property, which warns about passing already-serialized JSON
|
618 |
+
_data = None
|
619 |
+
def __init__(self, data=None, url=None, filename=None, expanded=False, metadata=None, root='root', **kwargs):
|
620 |
+
"""Create a JSON display object given raw data.
|
621 |
+
|
622 |
+
Parameters
|
623 |
+
----------
|
624 |
+
data : dict or list
|
625 |
+
JSON data to display. Not an already-serialized JSON string.
|
626 |
+
Scalar types (None, number, string) are not allowed, only dict
|
627 |
+
or list containers.
|
628 |
+
url : unicode
|
629 |
+
A URL to download the data from.
|
630 |
+
filename : unicode
|
631 |
+
Path to a local file to load the data from.
|
632 |
+
expanded : boolean
|
633 |
+
Metadata to control whether a JSON display component is expanded.
|
634 |
+
metadata : dict
|
635 |
+
Specify extra metadata to attach to the json display object.
|
636 |
+
root : str
|
637 |
+
The name of the root element of the JSON tree
|
638 |
+
"""
|
639 |
+
self.metadata = {
|
640 |
+
'expanded': expanded,
|
641 |
+
'root': root,
|
642 |
+
}
|
643 |
+
if metadata:
|
644 |
+
self.metadata.update(metadata)
|
645 |
+
if kwargs:
|
646 |
+
self.metadata.update(kwargs)
|
647 |
+
super(JSON, self).__init__(data=data, url=url, filename=filename)
|
648 |
+
|
649 |
+
def _check_data(self):
|
650 |
+
if self.data is not None and not isinstance(self.data, (dict, list)):
|
651 |
+
raise TypeError("%s expects JSONable dict or list, not %r" % (self.__class__.__name__, self.data))
|
652 |
+
|
653 |
+
@property
|
654 |
+
def data(self):
|
655 |
+
return self._data
|
656 |
+
|
657 |
+
@data.setter
|
658 |
+
def data(self, data):
|
659 |
+
if isinstance(data, (Path, PurePath)):
|
660 |
+
data = str(data)
|
661 |
+
|
662 |
+
if isinstance(data, str):
|
663 |
+
if self.filename is None and self.url is None:
|
664 |
+
warnings.warn("JSON expects JSONable dict or list, not JSON strings")
|
665 |
+
data = json.loads(data)
|
666 |
+
self._data = data
|
667 |
+
|
668 |
+
def _data_and_metadata(self):
|
669 |
+
return self.data, self.metadata
|
670 |
+
|
671 |
+
def _repr_json_(self):
|
672 |
+
return self._data_and_metadata()
|
673 |
+
|
674 |
+
|
675 |
+
_css_t = """var link = document.createElement("link");
|
676 |
+
link.rel = "stylesheet";
|
677 |
+
link.type = "text/css";
|
678 |
+
link.href = "%s";
|
679 |
+
document.head.appendChild(link);
|
680 |
+
"""
|
681 |
+
|
682 |
+
_lib_t1 = """new Promise(function(resolve, reject) {
|
683 |
+
var script = document.createElement("script");
|
684 |
+
script.onload = resolve;
|
685 |
+
script.onerror = reject;
|
686 |
+
script.src = "%s";
|
687 |
+
document.head.appendChild(script);
|
688 |
+
}).then(() => {
|
689 |
+
"""
|
690 |
+
|
691 |
+
_lib_t2 = """
|
692 |
+
});"""
|
693 |
+
|
694 |
+
class GeoJSON(JSON):
|
695 |
+
"""GeoJSON expects JSON-able dict
|
696 |
+
|
697 |
+
not an already-serialized JSON string.
|
698 |
+
|
699 |
+
Scalar types (None, number, string) are not allowed, only dict containers.
|
700 |
+
"""
|
701 |
+
|
702 |
+
def __init__(self, *args, **kwargs):
|
703 |
+
"""Create a GeoJSON display object given raw data.
|
704 |
+
|
705 |
+
Parameters
|
706 |
+
----------
|
707 |
+
data : dict or list
|
708 |
+
VegaLite data. Not an already-serialized JSON string.
|
709 |
+
Scalar types (None, number, string) are not allowed, only dict
|
710 |
+
or list containers.
|
711 |
+
url_template : string
|
712 |
+
Leaflet TileLayer URL template: http://leafletjs.com/reference.html#url-template
|
713 |
+
layer_options : dict
|
714 |
+
Leaflet TileLayer options: http://leafletjs.com/reference.html#tilelayer-options
|
715 |
+
url : unicode
|
716 |
+
A URL to download the data from.
|
717 |
+
filename : unicode
|
718 |
+
Path to a local file to load the data from.
|
719 |
+
metadata : dict
|
720 |
+
Specify extra metadata to attach to the json display object.
|
721 |
+
|
722 |
+
Examples
|
723 |
+
--------
|
724 |
+
The following will display an interactive map of Mars with a point of
|
725 |
+
interest on frontend that do support GeoJSON display.
|
726 |
+
|
727 |
+
>>> from IPython.display import GeoJSON
|
728 |
+
|
729 |
+
>>> GeoJSON(data={
|
730 |
+
... "type": "Feature",
|
731 |
+
... "geometry": {
|
732 |
+
... "type": "Point",
|
733 |
+
... "coordinates": [-81.327, 296.038]
|
734 |
+
... }
|
735 |
+
... },
|
736 |
+
... url_template="http://s3-eu-west-1.amazonaws.com/whereonmars.cartodb.net/{basemap_id}/{z}/{x}/{y}.png",
|
737 |
+
... layer_options={
|
738 |
+
... "basemap_id": "celestia_mars-shaded-16k_global",
|
739 |
+
... "attribution" : "Celestia/praesepe",
|
740 |
+
... "minZoom" : 0,
|
741 |
+
... "maxZoom" : 18,
|
742 |
+
... })
|
743 |
+
<IPython.core.display.GeoJSON object>
|
744 |
+
|
745 |
+
In the terminal IPython, you will only see the text representation of
|
746 |
+
the GeoJSON object.
|
747 |
+
|
748 |
+
"""
|
749 |
+
|
750 |
+
super(GeoJSON, self).__init__(*args, **kwargs)
|
751 |
+
|
752 |
+
|
753 |
+
def _ipython_display_(self):
|
754 |
+
bundle = {
|
755 |
+
'application/geo+json': self.data,
|
756 |
+
'text/plain': '<IPython.display.GeoJSON object>'
|
757 |
+
}
|
758 |
+
metadata = {
|
759 |
+
'application/geo+json': self.metadata
|
760 |
+
}
|
761 |
+
display_functions.display(bundle, metadata=metadata, raw=True)
|
762 |
+
|
763 |
+
class Javascript(TextDisplayObject):
|
764 |
+
|
765 |
+
def __init__(self, data=None, url=None, filename=None, lib=None, css=None):
|
766 |
+
"""Create a Javascript display object given raw data.
|
767 |
+
|
768 |
+
When this object is returned by an expression or passed to the
|
769 |
+
display function, it will result in the data being displayed
|
770 |
+
in the frontend. If the data is a URL, the data will first be
|
771 |
+
downloaded and then displayed.
|
772 |
+
|
773 |
+
In the Notebook, the containing element will be available as `element`,
|
774 |
+
and jQuery will be available. Content appended to `element` will be
|
775 |
+
visible in the output area.
|
776 |
+
|
777 |
+
Parameters
|
778 |
+
----------
|
779 |
+
data : unicode, str or bytes
|
780 |
+
The Javascript source code or a URL to download it from.
|
781 |
+
url : unicode
|
782 |
+
A URL to download the data from.
|
783 |
+
filename : unicode
|
784 |
+
Path to a local file to load the data from.
|
785 |
+
lib : list or str
|
786 |
+
A sequence of Javascript library URLs to load asynchronously before
|
787 |
+
running the source code. The full URLs of the libraries should
|
788 |
+
be given. A single Javascript library URL can also be given as a
|
789 |
+
string.
|
790 |
+
css : list or str
|
791 |
+
A sequence of css files to load before running the source code.
|
792 |
+
The full URLs of the css files should be given. A single css URL
|
793 |
+
can also be given as a string.
|
794 |
+
"""
|
795 |
+
if isinstance(lib, str):
|
796 |
+
lib = [lib]
|
797 |
+
elif lib is None:
|
798 |
+
lib = []
|
799 |
+
if isinstance(css, str):
|
800 |
+
css = [css]
|
801 |
+
elif css is None:
|
802 |
+
css = []
|
803 |
+
if not isinstance(lib, (list,tuple)):
|
804 |
+
raise TypeError('expected sequence, got: %r' % lib)
|
805 |
+
if not isinstance(css, (list,tuple)):
|
806 |
+
raise TypeError('expected sequence, got: %r' % css)
|
807 |
+
self.lib = lib
|
808 |
+
self.css = css
|
809 |
+
super(Javascript, self).__init__(data=data, url=url, filename=filename)
|
810 |
+
|
811 |
+
def _repr_javascript_(self):
|
812 |
+
r = ''
|
813 |
+
for c in self.css:
|
814 |
+
r += _css_t % c
|
815 |
+
for l in self.lib:
|
816 |
+
r += _lib_t1 % l
|
817 |
+
r += self.data
|
818 |
+
r += _lib_t2*len(self.lib)
|
819 |
+
return r
|
820 |
+
|
821 |
+
|
822 |
+
# constants for identifying png/jpeg/gif/webp data
|
823 |
+
_PNG = b"\x89PNG\r\n\x1a\n"
|
824 |
+
_JPEG = b"\xff\xd8"
|
825 |
+
_GIF1 = b"GIF87a"
|
826 |
+
_GIF2 = b"GIF89a"
|
827 |
+
_WEBP = b"WEBP"
|
828 |
+
|
829 |
+
|
830 |
+
def _pngxy(data):
|
831 |
+
"""read the (width, height) from a PNG header"""
|
832 |
+
ihdr = data.index(b'IHDR')
|
833 |
+
# next 8 bytes are width/height
|
834 |
+
return struct.unpack('>ii', data[ihdr+4:ihdr+12])
|
835 |
+
|
836 |
+
|
837 |
+
def _jpegxy(data):
|
838 |
+
"""read the (width, height) from a JPEG header"""
|
839 |
+
# adapted from http://www.64lines.com/jpeg-width-height
|
840 |
+
|
841 |
+
idx = 4
|
842 |
+
while True:
|
843 |
+
block_size = struct.unpack('>H', data[idx:idx+2])[0]
|
844 |
+
idx = idx + block_size
|
845 |
+
if data[idx:idx+2] == b'\xFF\xC0':
|
846 |
+
# found Start of Frame
|
847 |
+
iSOF = idx
|
848 |
+
break
|
849 |
+
else:
|
850 |
+
# read another block
|
851 |
+
idx += 2
|
852 |
+
|
853 |
+
h, w = struct.unpack('>HH', data[iSOF+5:iSOF+9])
|
854 |
+
return w, h
|
855 |
+
|
856 |
+
|
857 |
+
def _gifxy(data):
|
858 |
+
"""read the (width, height) from a GIF header"""
|
859 |
+
return struct.unpack('<HH', data[6:10])
|
860 |
+
|
861 |
+
|
862 |
+
def _webpxy(data):
|
863 |
+
"""read the (width, height) from a WEBP header"""
|
864 |
+
if data[12:16] == b"VP8 ":
|
865 |
+
width, height = struct.unpack("<HH", data[24:30])
|
866 |
+
width = width & 0x3FFF
|
867 |
+
height = height & 0x3FFF
|
868 |
+
return (width, height)
|
869 |
+
elif data[12:16] == b"VP8L":
|
870 |
+
size_info = struct.unpack("<I", data[21:25])[0]
|
871 |
+
width = 1 + ((size_info & 0x3F) << 8) | (size_info >> 24)
|
872 |
+
height = 1 + (
|
873 |
+
(((size_info >> 8) & 0xF) << 10)
|
874 |
+
| (((size_info >> 14) & 0x3FC) << 2)
|
875 |
+
| ((size_info >> 22) & 0x3)
|
876 |
+
)
|
877 |
+
return (width, height)
|
878 |
+
else:
|
879 |
+
raise ValueError("Not a valid WEBP header")
|
880 |
+
|
881 |
+
|
882 |
+
class Image(DisplayObject):
|
883 |
+
|
884 |
+
_read_flags = "rb"
|
885 |
+
_FMT_JPEG = "jpeg"
|
886 |
+
_FMT_PNG = "png"
|
887 |
+
_FMT_GIF = "gif"
|
888 |
+
_FMT_WEBP = "webp"
|
889 |
+
_ACCEPTABLE_EMBEDDINGS = [_FMT_JPEG, _FMT_PNG, _FMT_GIF, _FMT_WEBP]
|
890 |
+
_MIMETYPES = {
|
891 |
+
_FMT_PNG: "image/png",
|
892 |
+
_FMT_JPEG: "image/jpeg",
|
893 |
+
_FMT_GIF: "image/gif",
|
894 |
+
_FMT_WEBP: "image/webp",
|
895 |
+
}
|
896 |
+
|
897 |
+
def __init__(
|
898 |
+
self,
|
899 |
+
data=None,
|
900 |
+
url=None,
|
901 |
+
filename=None,
|
902 |
+
format=None,
|
903 |
+
embed=None,
|
904 |
+
width=None,
|
905 |
+
height=None,
|
906 |
+
retina=False,
|
907 |
+
unconfined=False,
|
908 |
+
metadata=None,
|
909 |
+
alt=None,
|
910 |
+
):
|
911 |
+
"""Create a PNG/JPEG/GIF/WEBP image object given raw data.
|
912 |
+
|
913 |
+
When this object is returned by an input cell or passed to the
|
914 |
+
display function, it will result in the image being displayed
|
915 |
+
in the frontend.
|
916 |
+
|
917 |
+
Parameters
|
918 |
+
----------
|
919 |
+
data : unicode, str or bytes
|
920 |
+
The raw image data or a URL or filename to load the data from.
|
921 |
+
This always results in embedded image data.
|
922 |
+
|
923 |
+
url : unicode
|
924 |
+
A URL to download the data from. If you specify `url=`,
|
925 |
+
the image data will not be embedded unless you also specify `embed=True`.
|
926 |
+
|
927 |
+
filename : unicode
|
928 |
+
Path to a local file to load the data from.
|
929 |
+
Images from a file are always embedded.
|
930 |
+
|
931 |
+
format : unicode
|
932 |
+
The format of the image data (png/jpeg/jpg/gif/webp). If a filename or URL is given
|
933 |
+
for format will be inferred from the filename extension.
|
934 |
+
|
935 |
+
embed : bool
|
936 |
+
Should the image data be embedded using a data URI (True) or be
|
937 |
+
loaded using an <img> tag. Set this to True if you want the image
|
938 |
+
to be viewable later with no internet connection in the notebook.
|
939 |
+
|
940 |
+
Default is `True`, unless the keyword argument `url` is set, then
|
941 |
+
default value is `False`.
|
942 |
+
|
943 |
+
Note that QtConsole is not able to display images if `embed` is set to `False`
|
944 |
+
|
945 |
+
width : int
|
946 |
+
Width in pixels to which to constrain the image in html
|
947 |
+
|
948 |
+
height : int
|
949 |
+
Height in pixels to which to constrain the image in html
|
950 |
+
|
951 |
+
retina : bool
|
952 |
+
Automatically set the width and height to half of the measured
|
953 |
+
width and height.
|
954 |
+
This only works for embedded images because it reads the width/height
|
955 |
+
from image data.
|
956 |
+
For non-embedded images, you can just set the desired display width
|
957 |
+
and height directly.
|
958 |
+
|
959 |
+
unconfined : bool
|
960 |
+
Set unconfined=True to disable max-width confinement of the image.
|
961 |
+
|
962 |
+
metadata : dict
|
963 |
+
Specify extra metadata to attach to the image.
|
964 |
+
|
965 |
+
alt : unicode
|
966 |
+
Alternative text for the image, for use by screen readers.
|
967 |
+
|
968 |
+
Examples
|
969 |
+
--------
|
970 |
+
embedded image data, works in qtconsole and notebook
|
971 |
+
when passed positionally, the first arg can be any of raw image data,
|
972 |
+
a URL, or a filename from which to load image data.
|
973 |
+
The result is always embedding image data for inline images.
|
974 |
+
|
975 |
+
>>> Image('https://www.google.fr/images/srpr/logo3w.png') # doctest: +SKIP
|
976 |
+
<IPython.core.display.Image object>
|
977 |
+
|
978 |
+
>>> Image('/path/to/image.jpg')
|
979 |
+
<IPython.core.display.Image object>
|
980 |
+
|
981 |
+
>>> Image(b'RAW_PNG_DATA...')
|
982 |
+
<IPython.core.display.Image object>
|
983 |
+
|
984 |
+
Specifying Image(url=...) does not embed the image data,
|
985 |
+
it only generates ``<img>`` tag with a link to the source.
|
986 |
+
This will not work in the qtconsole or offline.
|
987 |
+
|
988 |
+
>>> Image(url='https://www.google.fr/images/srpr/logo3w.png')
|
989 |
+
<IPython.core.display.Image object>
|
990 |
+
|
991 |
+
"""
|
992 |
+
if isinstance(data, (Path, PurePath)):
|
993 |
+
data = str(data)
|
994 |
+
|
995 |
+
if filename is not None:
|
996 |
+
ext = self._find_ext(filename)
|
997 |
+
elif url is not None:
|
998 |
+
ext = self._find_ext(url)
|
999 |
+
elif data is None:
|
1000 |
+
raise ValueError("No image data found. Expecting filename, url, or data.")
|
1001 |
+
elif isinstance(data, str) and (
|
1002 |
+
data.startswith('http') or _safe_exists(data)
|
1003 |
+
):
|
1004 |
+
ext = self._find_ext(data)
|
1005 |
+
else:
|
1006 |
+
ext = None
|
1007 |
+
|
1008 |
+
if format is None:
|
1009 |
+
if ext is not None:
|
1010 |
+
if ext == u'jpg' or ext == u'jpeg':
|
1011 |
+
format = self._FMT_JPEG
|
1012 |
+
elif ext == u'png':
|
1013 |
+
format = self._FMT_PNG
|
1014 |
+
elif ext == u'gif':
|
1015 |
+
format = self._FMT_GIF
|
1016 |
+
elif ext == "webp":
|
1017 |
+
format = self._FMT_WEBP
|
1018 |
+
else:
|
1019 |
+
format = ext.lower()
|
1020 |
+
elif isinstance(data, bytes):
|
1021 |
+
# infer image type from image data header,
|
1022 |
+
# only if format has not been specified.
|
1023 |
+
if data[:2] == _JPEG:
|
1024 |
+
format = self._FMT_JPEG
|
1025 |
+
elif data[:8] == _PNG:
|
1026 |
+
format = self._FMT_PNG
|
1027 |
+
elif data[8:12] == _WEBP:
|
1028 |
+
format = self._FMT_WEBP
|
1029 |
+
elif data[:6] == _GIF1 or data[:6] == _GIF2:
|
1030 |
+
format = self._FMT_GIF
|
1031 |
+
|
1032 |
+
# failed to detect format, default png
|
1033 |
+
if format is None:
|
1034 |
+
format = self._FMT_PNG
|
1035 |
+
|
1036 |
+
if format.lower() == 'jpg':
|
1037 |
+
# jpg->jpeg
|
1038 |
+
format = self._FMT_JPEG
|
1039 |
+
|
1040 |
+
self.format = format.lower()
|
1041 |
+
self.embed = embed if embed is not None else (url is None)
|
1042 |
+
|
1043 |
+
if self.embed and self.format not in self._ACCEPTABLE_EMBEDDINGS:
|
1044 |
+
raise ValueError("Cannot embed the '%s' image format" % (self.format))
|
1045 |
+
if self.embed:
|
1046 |
+
self._mimetype = self._MIMETYPES.get(self.format)
|
1047 |
+
|
1048 |
+
self.width = width
|
1049 |
+
self.height = height
|
1050 |
+
self.retina = retina
|
1051 |
+
self.unconfined = unconfined
|
1052 |
+
self.alt = alt
|
1053 |
+
super(Image, self).__init__(data=data, url=url, filename=filename,
|
1054 |
+
metadata=metadata)
|
1055 |
+
|
1056 |
+
if self.width is None and self.metadata.get('width', {}):
|
1057 |
+
self.width = metadata['width']
|
1058 |
+
|
1059 |
+
if self.height is None and self.metadata.get('height', {}):
|
1060 |
+
self.height = metadata['height']
|
1061 |
+
|
1062 |
+
if self.alt is None and self.metadata.get("alt", {}):
|
1063 |
+
self.alt = metadata["alt"]
|
1064 |
+
|
1065 |
+
if retina:
|
1066 |
+
self._retina_shape()
|
1067 |
+
|
1068 |
+
|
1069 |
+
def _retina_shape(self):
|
1070 |
+
"""load pixel-doubled width and height from image data"""
|
1071 |
+
if not self.embed:
|
1072 |
+
return
|
1073 |
+
if self.format == self._FMT_PNG:
|
1074 |
+
w, h = _pngxy(self.data)
|
1075 |
+
elif self.format == self._FMT_JPEG:
|
1076 |
+
w, h = _jpegxy(self.data)
|
1077 |
+
elif self.format == self._FMT_GIF:
|
1078 |
+
w, h = _gifxy(self.data)
|
1079 |
+
else:
|
1080 |
+
# retina only supports png
|
1081 |
+
return
|
1082 |
+
self.width = w // 2
|
1083 |
+
self.height = h // 2
|
1084 |
+
|
1085 |
+
def reload(self):
|
1086 |
+
"""Reload the raw data from file or URL."""
|
1087 |
+
if self.embed:
|
1088 |
+
super(Image,self).reload()
|
1089 |
+
if self.retina:
|
1090 |
+
self._retina_shape()
|
1091 |
+
|
1092 |
+
def _repr_html_(self):
|
1093 |
+
if not self.embed:
|
1094 |
+
width = height = klass = alt = ""
|
1095 |
+
if self.width:
|
1096 |
+
width = ' width="%d"' % self.width
|
1097 |
+
if self.height:
|
1098 |
+
height = ' height="%d"' % self.height
|
1099 |
+
if self.unconfined:
|
1100 |
+
klass = ' class="unconfined"'
|
1101 |
+
if self.alt:
|
1102 |
+
alt = ' alt="%s"' % html.escape(self.alt)
|
1103 |
+
return '<img src="{url}"{width}{height}{klass}{alt}/>'.format(
|
1104 |
+
url=self.url,
|
1105 |
+
width=width,
|
1106 |
+
height=height,
|
1107 |
+
klass=klass,
|
1108 |
+
alt=alt,
|
1109 |
+
)
|
1110 |
+
|
1111 |
+
def _repr_mimebundle_(self, include=None, exclude=None):
|
1112 |
+
"""Return the image as a mimebundle
|
1113 |
+
|
1114 |
+
Any new mimetype support should be implemented here.
|
1115 |
+
"""
|
1116 |
+
if self.embed:
|
1117 |
+
mimetype = self._mimetype
|
1118 |
+
data, metadata = self._data_and_metadata(always_both=True)
|
1119 |
+
if metadata:
|
1120 |
+
metadata = {mimetype: metadata}
|
1121 |
+
return {mimetype: data}, metadata
|
1122 |
+
else:
|
1123 |
+
return {'text/html': self._repr_html_()}
|
1124 |
+
|
1125 |
+
def _data_and_metadata(self, always_both=False):
|
1126 |
+
"""shortcut for returning metadata with shape information, if defined"""
|
1127 |
+
try:
|
1128 |
+
b64_data = b2a_base64(self.data, newline=False).decode("ascii")
|
1129 |
+
except TypeError as e:
|
1130 |
+
raise FileNotFoundError(
|
1131 |
+
"No such file or directory: '%s'" % (self.data)) from e
|
1132 |
+
md = {}
|
1133 |
+
if self.metadata:
|
1134 |
+
md.update(self.metadata)
|
1135 |
+
if self.width:
|
1136 |
+
md['width'] = self.width
|
1137 |
+
if self.height:
|
1138 |
+
md['height'] = self.height
|
1139 |
+
if self.unconfined:
|
1140 |
+
md['unconfined'] = self.unconfined
|
1141 |
+
if self.alt:
|
1142 |
+
md["alt"] = self.alt
|
1143 |
+
if md or always_both:
|
1144 |
+
return b64_data, md
|
1145 |
+
else:
|
1146 |
+
return b64_data
|
1147 |
+
|
1148 |
+
def _repr_png_(self):
|
1149 |
+
if self.embed and self.format == self._FMT_PNG:
|
1150 |
+
return self._data_and_metadata()
|
1151 |
+
|
1152 |
+
def _repr_jpeg_(self):
|
1153 |
+
if self.embed and self.format == self._FMT_JPEG:
|
1154 |
+
return self._data_and_metadata()
|
1155 |
+
|
1156 |
+
def _find_ext(self, s):
|
1157 |
+
base, ext = splitext(s)
|
1158 |
+
|
1159 |
+
if not ext:
|
1160 |
+
return base
|
1161 |
+
|
1162 |
+
# `splitext` includes leading period, so we skip it
|
1163 |
+
return ext[1:].lower()
|
1164 |
+
|
1165 |
+
|
1166 |
+
class Video(DisplayObject):
|
1167 |
+
|
1168 |
+
def __init__(self, data=None, url=None, filename=None, embed=False,
|
1169 |
+
mimetype=None, width=None, height=None, html_attributes="controls"):
|
1170 |
+
"""Create a video object given raw data or an URL.
|
1171 |
+
|
1172 |
+
When this object is returned by an input cell or passed to the
|
1173 |
+
display function, it will result in the video being displayed
|
1174 |
+
in the frontend.
|
1175 |
+
|
1176 |
+
Parameters
|
1177 |
+
----------
|
1178 |
+
data : unicode, str or bytes
|
1179 |
+
The raw video data or a URL or filename to load the data from.
|
1180 |
+
Raw data will require passing ``embed=True``.
|
1181 |
+
|
1182 |
+
url : unicode
|
1183 |
+
A URL for the video. If you specify ``url=``,
|
1184 |
+
the image data will not be embedded.
|
1185 |
+
|
1186 |
+
filename : unicode
|
1187 |
+
Path to a local file containing the video.
|
1188 |
+
Will be interpreted as a local URL unless ``embed=True``.
|
1189 |
+
|
1190 |
+
embed : bool
|
1191 |
+
Should the video be embedded using a data URI (True) or be
|
1192 |
+
loaded using a <video> tag (False).
|
1193 |
+
|
1194 |
+
Since videos are large, embedding them should be avoided, if possible.
|
1195 |
+
You must confirm embedding as your intention by passing ``embed=True``.
|
1196 |
+
|
1197 |
+
Local files can be displayed with URLs without embedding the content, via::
|
1198 |
+
|
1199 |
+
Video('./video.mp4')
|
1200 |
+
|
1201 |
+
mimetype : unicode
|
1202 |
+
Specify the mimetype for embedded videos.
|
1203 |
+
Default will be guessed from file extension, if available.
|
1204 |
+
|
1205 |
+
width : int
|
1206 |
+
Width in pixels to which to constrain the video in HTML.
|
1207 |
+
If not supplied, defaults to the width of the video.
|
1208 |
+
|
1209 |
+
height : int
|
1210 |
+
Height in pixels to which to constrain the video in html.
|
1211 |
+
If not supplied, defaults to the height of the video.
|
1212 |
+
|
1213 |
+
html_attributes : str
|
1214 |
+
Attributes for the HTML ``<video>`` block.
|
1215 |
+
Default: ``"controls"`` to get video controls.
|
1216 |
+
Other examples: ``"controls muted"`` for muted video with controls,
|
1217 |
+
``"loop autoplay"`` for looping autoplaying video without controls.
|
1218 |
+
|
1219 |
+
Examples
|
1220 |
+
--------
|
1221 |
+
::
|
1222 |
+
|
1223 |
+
Video('https://archive.org/download/Sita_Sings_the_Blues/Sita_Sings_the_Blues_small.mp4')
|
1224 |
+
Video('path/to/video.mp4')
|
1225 |
+
Video('path/to/video.mp4', embed=True)
|
1226 |
+
Video('path/to/video.mp4', embed=True, html_attributes="controls muted autoplay")
|
1227 |
+
Video(b'raw-videodata', embed=True)
|
1228 |
+
"""
|
1229 |
+
if isinstance(data, (Path, PurePath)):
|
1230 |
+
data = str(data)
|
1231 |
+
|
1232 |
+
if url is None and isinstance(data, str) and data.startswith(('http:', 'https:')):
|
1233 |
+
url = data
|
1234 |
+
data = None
|
1235 |
+
elif data is not None and os.path.exists(data):
|
1236 |
+
filename = data
|
1237 |
+
data = None
|
1238 |
+
|
1239 |
+
if data and not embed:
|
1240 |
+
msg = ''.join([
|
1241 |
+
"To embed videos, you must pass embed=True ",
|
1242 |
+
"(this may make your notebook files huge)\n",
|
1243 |
+
"Consider passing Video(url='...')",
|
1244 |
+
])
|
1245 |
+
raise ValueError(msg)
|
1246 |
+
|
1247 |
+
self.mimetype = mimetype
|
1248 |
+
self.embed = embed
|
1249 |
+
self.width = width
|
1250 |
+
self.height = height
|
1251 |
+
self.html_attributes = html_attributes
|
1252 |
+
super(Video, self).__init__(data=data, url=url, filename=filename)
|
1253 |
+
|
1254 |
+
def _repr_html_(self):
|
1255 |
+
width = height = ''
|
1256 |
+
if self.width:
|
1257 |
+
width = ' width="%d"' % self.width
|
1258 |
+
if self.height:
|
1259 |
+
height = ' height="%d"' % self.height
|
1260 |
+
|
1261 |
+
# External URLs and potentially local files are not embedded into the
|
1262 |
+
# notebook output.
|
1263 |
+
if not self.embed:
|
1264 |
+
url = self.url if self.url is not None else self.filename
|
1265 |
+
output = """<video src="{0}" {1} {2} {3}>
|
1266 |
+
Your browser does not support the <code>video</code> element.
|
1267 |
+
</video>""".format(url, self.html_attributes, width, height)
|
1268 |
+
return output
|
1269 |
+
|
1270 |
+
# Embedded videos are base64-encoded.
|
1271 |
+
mimetype = self.mimetype
|
1272 |
+
if self.filename is not None:
|
1273 |
+
if not mimetype:
|
1274 |
+
mimetype, _ = mimetypes.guess_type(self.filename)
|
1275 |
+
|
1276 |
+
with open(self.filename, 'rb') as f:
|
1277 |
+
video = f.read()
|
1278 |
+
else:
|
1279 |
+
video = self.data
|
1280 |
+
if isinstance(video, str):
|
1281 |
+
# unicode input is already b64-encoded
|
1282 |
+
b64_video = video
|
1283 |
+
else:
|
1284 |
+
b64_video = b2a_base64(video, newline=False).decode("ascii").rstrip()
|
1285 |
+
|
1286 |
+
output = """<video {0} {1} {2}>
|
1287 |
+
<source src="data:{3};base64,{4}" type="{3}">
|
1288 |
+
Your browser does not support the video tag.
|
1289 |
+
</video>""".format(self.html_attributes, width, height, mimetype, b64_video)
|
1290 |
+
return output
|
1291 |
+
|
1292 |
+
def reload(self):
|
1293 |
+
# TODO
|
1294 |
+
pass
|
1295 |
+
|
1296 |
+
|
1297 |
+
@skip_doctest
|
1298 |
+
def set_matplotlib_formats(*formats, **kwargs):
|
1299 |
+
"""
|
1300 |
+
.. deprecated:: 7.23
|
1301 |
+
|
1302 |
+
use `matplotlib_inline.backend_inline.set_matplotlib_formats()`
|
1303 |
+
|
1304 |
+
Select figure formats for the inline backend. Optionally pass quality for JPEG.
|
1305 |
+
|
1306 |
+
For example, this enables PNG and JPEG output with a JPEG quality of 90%::
|
1307 |
+
|
1308 |
+
In [1]: set_matplotlib_formats('png', 'jpeg', quality=90)
|
1309 |
+
|
1310 |
+
To set this in your config files use the following::
|
1311 |
+
|
1312 |
+
c.InlineBackend.figure_formats = {'png', 'jpeg'}
|
1313 |
+
c.InlineBackend.print_figure_kwargs.update({'quality' : 90})
|
1314 |
+
|
1315 |
+
Parameters
|
1316 |
+
----------
|
1317 |
+
*formats : strs
|
1318 |
+
One or more figure formats to enable: 'png', 'retina', 'jpeg', 'svg', 'pdf'.
|
1319 |
+
**kwargs
|
1320 |
+
Keyword args will be relayed to ``figure.canvas.print_figure``.
|
1321 |
+
"""
|
1322 |
+
warnings.warn(
|
1323 |
+
"`set_matplotlib_formats` is deprecated since IPython 7.23, directly "
|
1324 |
+
"use `matplotlib_inline.backend_inline.set_matplotlib_formats()`",
|
1325 |
+
DeprecationWarning,
|
1326 |
+
stacklevel=2,
|
1327 |
+
)
|
1328 |
+
|
1329 |
+
from matplotlib_inline.backend_inline import (
|
1330 |
+
set_matplotlib_formats as set_matplotlib_formats_orig,
|
1331 |
+
)
|
1332 |
+
|
1333 |
+
set_matplotlib_formats_orig(*formats, **kwargs)
|
1334 |
+
|
1335 |
+
@skip_doctest
|
1336 |
+
def set_matplotlib_close(close=True):
|
1337 |
+
"""
|
1338 |
+
.. deprecated:: 7.23
|
1339 |
+
|
1340 |
+
use `matplotlib_inline.backend_inline.set_matplotlib_close()`
|
1341 |
+
|
1342 |
+
Set whether the inline backend closes all figures automatically or not.
|
1343 |
+
|
1344 |
+
By default, the inline backend used in the IPython Notebook will close all
|
1345 |
+
matplotlib figures automatically after each cell is run. This means that
|
1346 |
+
plots in different cells won't interfere. Sometimes, you may want to make
|
1347 |
+
a plot in one cell and then refine it in later cells. This can be accomplished
|
1348 |
+
by::
|
1349 |
+
|
1350 |
+
In [1]: set_matplotlib_close(False)
|
1351 |
+
|
1352 |
+
To set this in your config files use the following::
|
1353 |
+
|
1354 |
+
c.InlineBackend.close_figures = False
|
1355 |
+
|
1356 |
+
Parameters
|
1357 |
+
----------
|
1358 |
+
close : bool
|
1359 |
+
Should all matplotlib figures be automatically closed after each cell is
|
1360 |
+
run?
|
1361 |
+
"""
|
1362 |
+
warnings.warn(
|
1363 |
+
"`set_matplotlib_close` is deprecated since IPython 7.23, directly "
|
1364 |
+
"use `matplotlib_inline.backend_inline.set_matplotlib_close()`",
|
1365 |
+
DeprecationWarning,
|
1366 |
+
stacklevel=2,
|
1367 |
+
)
|
1368 |
+
|
1369 |
+
from matplotlib_inline.backend_inline import (
|
1370 |
+
set_matplotlib_close as set_matplotlib_close_orig,
|
1371 |
+
)
|
1372 |
+
|
1373 |
+
set_matplotlib_close_orig(close)
|
openalex_env_map/lib/python3.10/site-packages/IPython/core/display_functions.py
ADDED
@@ -0,0 +1,391 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# -*- coding: utf-8 -*-
|
2 |
+
"""Top-level display functions for displaying object in different formats."""
|
3 |
+
|
4 |
+
# Copyright (c) IPython Development Team.
|
5 |
+
# Distributed under the terms of the Modified BSD License.
|
6 |
+
|
7 |
+
|
8 |
+
from binascii import b2a_hex
|
9 |
+
import os
|
10 |
+
import sys
|
11 |
+
import warnings
|
12 |
+
|
13 |
+
__all__ = ['display', 'clear_output', 'publish_display_data', 'update_display', 'DisplayHandle']
|
14 |
+
|
15 |
+
#-----------------------------------------------------------------------------
|
16 |
+
# utility functions
|
17 |
+
#-----------------------------------------------------------------------------
|
18 |
+
|
19 |
+
|
20 |
+
def _merge(d1, d2):
|
21 |
+
"""Like update, but merges sub-dicts instead of clobbering at the top level.
|
22 |
+
|
23 |
+
Updates d1 in-place
|
24 |
+
"""
|
25 |
+
|
26 |
+
if not isinstance(d2, dict) or not isinstance(d1, dict):
|
27 |
+
return d2
|
28 |
+
for key, value in d2.items():
|
29 |
+
d1[key] = _merge(d1.get(key), value)
|
30 |
+
return d1
|
31 |
+
|
32 |
+
|
33 |
+
#-----------------------------------------------------------------------------
|
34 |
+
# Main functions
|
35 |
+
#-----------------------------------------------------------------------------
|
36 |
+
|
37 |
+
class _Sentinel:
|
38 |
+
def __repr__(self):
|
39 |
+
return "<deprecated>"
|
40 |
+
|
41 |
+
|
42 |
+
_sentinel = _Sentinel()
|
43 |
+
|
44 |
+
# use * to indicate transient is keyword-only
|
45 |
+
def publish_display_data(
|
46 |
+
data, metadata=None, source=_sentinel, *, transient=None, **kwargs
|
47 |
+
):
|
48 |
+
"""Publish data and metadata to all frontends.
|
49 |
+
|
50 |
+
See the ``display_data`` message in the messaging documentation for
|
51 |
+
more details about this message type.
|
52 |
+
|
53 |
+
Keys of data and metadata can be any mime-type.
|
54 |
+
|
55 |
+
Parameters
|
56 |
+
----------
|
57 |
+
data : dict
|
58 |
+
A dictionary having keys that are valid MIME types (like
|
59 |
+
'text/plain' or 'image/svg+xml') and values that are the data for
|
60 |
+
that MIME type. The data itself must be a JSON'able data
|
61 |
+
structure. Minimally all data should have the 'text/plain' data,
|
62 |
+
which can be displayed by all frontends. If more than the plain
|
63 |
+
text is given, it is up to the frontend to decide which
|
64 |
+
representation to use.
|
65 |
+
metadata : dict
|
66 |
+
A dictionary for metadata related to the data. This can contain
|
67 |
+
arbitrary key, value pairs that frontends can use to interpret
|
68 |
+
the data. mime-type keys matching those in data can be used
|
69 |
+
to specify metadata about particular representations.
|
70 |
+
source : str, deprecated
|
71 |
+
Unused.
|
72 |
+
transient : dict, keyword-only
|
73 |
+
A dictionary of transient data, such as display_id.
|
74 |
+
"""
|
75 |
+
from IPython.core.interactiveshell import InteractiveShell
|
76 |
+
|
77 |
+
if source is not _sentinel:
|
78 |
+
warnings.warn(
|
79 |
+
"The `source` parameter emit a deprecation warning since"
|
80 |
+
" IPython 8.0, it had no effects for a long time and will "
|
81 |
+
" be removed in future versions.",
|
82 |
+
DeprecationWarning,
|
83 |
+
stacklevel=2,
|
84 |
+
)
|
85 |
+
display_pub = InteractiveShell.instance().display_pub
|
86 |
+
|
87 |
+
# only pass transient if supplied,
|
88 |
+
# to avoid errors with older ipykernel.
|
89 |
+
# TODO: We could check for ipykernel version and provide a detailed upgrade message.
|
90 |
+
if transient:
|
91 |
+
kwargs['transient'] = transient
|
92 |
+
|
93 |
+
display_pub.publish(
|
94 |
+
data=data,
|
95 |
+
metadata=metadata,
|
96 |
+
**kwargs
|
97 |
+
)
|
98 |
+
|
99 |
+
|
100 |
+
def _new_id():
|
101 |
+
"""Generate a new random text id with urandom"""
|
102 |
+
return b2a_hex(os.urandom(16)).decode('ascii')
|
103 |
+
|
104 |
+
|
105 |
+
def display(
|
106 |
+
*objs,
|
107 |
+
include=None,
|
108 |
+
exclude=None,
|
109 |
+
metadata=None,
|
110 |
+
transient=None,
|
111 |
+
display_id=None,
|
112 |
+
raw=False,
|
113 |
+
clear=False,
|
114 |
+
**kwargs,
|
115 |
+
):
|
116 |
+
"""Display a Python object in all frontends.
|
117 |
+
|
118 |
+
By default all representations will be computed and sent to the frontends.
|
119 |
+
Frontends can decide which representation is used and how.
|
120 |
+
|
121 |
+
In terminal IPython this will be similar to using :func:`print`, for use in richer
|
122 |
+
frontends see Jupyter notebook examples with rich display logic.
|
123 |
+
|
124 |
+
Parameters
|
125 |
+
----------
|
126 |
+
*objs : object
|
127 |
+
The Python objects to display.
|
128 |
+
raw : bool, optional
|
129 |
+
Are the objects to be displayed already mimetype-keyed dicts of raw display data,
|
130 |
+
or Python objects that need to be formatted before display? [default: False]
|
131 |
+
include : list, tuple or set, optional
|
132 |
+
A list of format type strings (MIME types) to include in the
|
133 |
+
format data dict. If this is set *only* the format types included
|
134 |
+
in this list will be computed.
|
135 |
+
exclude : list, tuple or set, optional
|
136 |
+
A list of format type strings (MIME types) to exclude in the format
|
137 |
+
data dict. If this is set all format types will be computed,
|
138 |
+
except for those included in this argument.
|
139 |
+
metadata : dict, optional
|
140 |
+
A dictionary of metadata to associate with the output.
|
141 |
+
mime-type keys in this dictionary will be associated with the individual
|
142 |
+
representation formats, if they exist.
|
143 |
+
transient : dict, optional
|
144 |
+
A dictionary of transient data to associate with the output.
|
145 |
+
Data in this dict should not be persisted to files (e.g. notebooks).
|
146 |
+
display_id : str, bool optional
|
147 |
+
Set an id for the display.
|
148 |
+
This id can be used for updating this display area later via update_display.
|
149 |
+
If given as `True`, generate a new `display_id`
|
150 |
+
clear : bool, optional
|
151 |
+
Should the output area be cleared before displaying anything? If True,
|
152 |
+
this will wait for additional output before clearing. [default: False]
|
153 |
+
**kwargs : additional keyword-args, optional
|
154 |
+
Additional keyword-arguments are passed through to the display publisher.
|
155 |
+
|
156 |
+
Returns
|
157 |
+
-------
|
158 |
+
handle: DisplayHandle
|
159 |
+
Returns a handle on updatable displays for use with :func:`update_display`,
|
160 |
+
if `display_id` is given. Returns :any:`None` if no `display_id` is given
|
161 |
+
(default).
|
162 |
+
|
163 |
+
Examples
|
164 |
+
--------
|
165 |
+
>>> class Json(object):
|
166 |
+
... def __init__(self, json):
|
167 |
+
... self.json = json
|
168 |
+
... def _repr_pretty_(self, pp, cycle):
|
169 |
+
... import json
|
170 |
+
... pp.text(json.dumps(self.json, indent=2))
|
171 |
+
... def __repr__(self):
|
172 |
+
... return str(self.json)
|
173 |
+
...
|
174 |
+
|
175 |
+
>>> d = Json({1:2, 3: {4:5}})
|
176 |
+
|
177 |
+
>>> print(d)
|
178 |
+
{1: 2, 3: {4: 5}}
|
179 |
+
|
180 |
+
>>> display(d)
|
181 |
+
{
|
182 |
+
"1": 2,
|
183 |
+
"3": {
|
184 |
+
"4": 5
|
185 |
+
}
|
186 |
+
}
|
187 |
+
|
188 |
+
>>> def int_formatter(integer, pp, cycle):
|
189 |
+
... pp.text('I'*integer)
|
190 |
+
|
191 |
+
>>> plain = get_ipython().display_formatter.formatters['text/plain']
|
192 |
+
>>> plain.for_type(int, int_formatter)
|
193 |
+
<function _repr_pprint at 0x...>
|
194 |
+
>>> display(7-5)
|
195 |
+
II
|
196 |
+
|
197 |
+
>>> del plain.type_printers[int]
|
198 |
+
>>> display(7-5)
|
199 |
+
2
|
200 |
+
|
201 |
+
See Also
|
202 |
+
--------
|
203 |
+
:func:`update_display`
|
204 |
+
|
205 |
+
Notes
|
206 |
+
-----
|
207 |
+
In Python, objects can declare their textual representation using the
|
208 |
+
`__repr__` method. IPython expands on this idea and allows objects to declare
|
209 |
+
other, rich representations including:
|
210 |
+
|
211 |
+
- HTML
|
212 |
+
- JSON
|
213 |
+
- PNG
|
214 |
+
- JPEG
|
215 |
+
- SVG
|
216 |
+
- LaTeX
|
217 |
+
|
218 |
+
A single object can declare some or all of these representations; all are
|
219 |
+
handled by IPython's display system.
|
220 |
+
|
221 |
+
The main idea of the first approach is that you have to implement special
|
222 |
+
display methods when you define your class, one for each representation you
|
223 |
+
want to use. Here is a list of the names of the special methods and the
|
224 |
+
values they must return:
|
225 |
+
|
226 |
+
- `_repr_html_`: return raw HTML as a string, or a tuple (see below).
|
227 |
+
- `_repr_json_`: return a JSONable dict, or a tuple (see below).
|
228 |
+
- `_repr_jpeg_`: return raw JPEG data, or a tuple (see below).
|
229 |
+
- `_repr_png_`: return raw PNG data, or a tuple (see below).
|
230 |
+
- `_repr_svg_`: return raw SVG data as a string, or a tuple (see below).
|
231 |
+
- `_repr_latex_`: return LaTeX commands in a string surrounded by "$",
|
232 |
+
or a tuple (see below).
|
233 |
+
- `_repr_mimebundle_`: return a full mimebundle containing the mapping
|
234 |
+
from all mimetypes to data.
|
235 |
+
Use this for any mime-type not listed above.
|
236 |
+
|
237 |
+
The above functions may also return the object's metadata alonside the
|
238 |
+
data. If the metadata is available, the functions will return a tuple
|
239 |
+
containing the data and metadata, in that order. If there is no metadata
|
240 |
+
available, then the functions will return the data only.
|
241 |
+
|
242 |
+
When you are directly writing your own classes, you can adapt them for
|
243 |
+
display in IPython by following the above approach. But in practice, you
|
244 |
+
often need to work with existing classes that you can't easily modify.
|
245 |
+
|
246 |
+
You can refer to the documentation on integrating with the display system in
|
247 |
+
order to register custom formatters for already existing types
|
248 |
+
(:ref:`integrating_rich_display`).
|
249 |
+
|
250 |
+
.. versionadded:: 5.4 display available without import
|
251 |
+
.. versionadded:: 6.1 display available without import
|
252 |
+
|
253 |
+
Since IPython 5.4 and 6.1 :func:`display` is automatically made available to
|
254 |
+
the user without import. If you are using display in a document that might
|
255 |
+
be used in a pure python context or with older version of IPython, use the
|
256 |
+
following import at the top of your file::
|
257 |
+
|
258 |
+
from IPython.display import display
|
259 |
+
|
260 |
+
"""
|
261 |
+
from IPython.core.interactiveshell import InteractiveShell
|
262 |
+
|
263 |
+
if not InteractiveShell.initialized():
|
264 |
+
# Directly print objects.
|
265 |
+
print(*objs)
|
266 |
+
return
|
267 |
+
|
268 |
+
if transient is None:
|
269 |
+
transient = {}
|
270 |
+
if metadata is None:
|
271 |
+
metadata={}
|
272 |
+
if display_id:
|
273 |
+
if display_id is True:
|
274 |
+
display_id = _new_id()
|
275 |
+
transient['display_id'] = display_id
|
276 |
+
if kwargs.get('update') and 'display_id' not in transient:
|
277 |
+
raise TypeError('display_id required for update_display')
|
278 |
+
if transient:
|
279 |
+
kwargs['transient'] = transient
|
280 |
+
|
281 |
+
if not objs and display_id:
|
282 |
+
# if given no objects, but still a request for a display_id,
|
283 |
+
# we assume the user wants to insert an empty output that
|
284 |
+
# can be updated later
|
285 |
+
objs = [{}]
|
286 |
+
raw = True
|
287 |
+
|
288 |
+
if not raw:
|
289 |
+
format = InteractiveShell.instance().display_formatter.format
|
290 |
+
|
291 |
+
if clear:
|
292 |
+
clear_output(wait=True)
|
293 |
+
|
294 |
+
for obj in objs:
|
295 |
+
if raw:
|
296 |
+
publish_display_data(data=obj, metadata=metadata, **kwargs)
|
297 |
+
else:
|
298 |
+
format_dict, md_dict = format(obj, include=include, exclude=exclude)
|
299 |
+
if not format_dict:
|
300 |
+
# nothing to display (e.g. _ipython_display_ took over)
|
301 |
+
continue
|
302 |
+
if metadata:
|
303 |
+
# kwarg-specified metadata gets precedence
|
304 |
+
_merge(md_dict, metadata)
|
305 |
+
publish_display_data(data=format_dict, metadata=md_dict, **kwargs)
|
306 |
+
if display_id:
|
307 |
+
return DisplayHandle(display_id)
|
308 |
+
|
309 |
+
|
310 |
+
# use * for keyword-only display_id arg
|
311 |
+
def update_display(obj, *, display_id, **kwargs):
|
312 |
+
"""Update an existing display by id
|
313 |
+
|
314 |
+
Parameters
|
315 |
+
----------
|
316 |
+
obj
|
317 |
+
The object with which to update the display
|
318 |
+
display_id : keyword-only
|
319 |
+
The id of the display to update
|
320 |
+
|
321 |
+
See Also
|
322 |
+
--------
|
323 |
+
:func:`display`
|
324 |
+
"""
|
325 |
+
kwargs['update'] = True
|
326 |
+
display(obj, display_id=display_id, **kwargs)
|
327 |
+
|
328 |
+
|
329 |
+
class DisplayHandle(object):
|
330 |
+
"""A handle on an updatable display
|
331 |
+
|
332 |
+
Call `.update(obj)` to display a new object.
|
333 |
+
|
334 |
+
Call `.display(obj`) to add a new instance of this display,
|
335 |
+
and update existing instances.
|
336 |
+
|
337 |
+
See Also
|
338 |
+
--------
|
339 |
+
|
340 |
+
:func:`display`, :func:`update_display`
|
341 |
+
|
342 |
+
"""
|
343 |
+
|
344 |
+
def __init__(self, display_id=None):
|
345 |
+
if display_id is None:
|
346 |
+
display_id = _new_id()
|
347 |
+
self.display_id = display_id
|
348 |
+
|
349 |
+
def __repr__(self):
|
350 |
+
return "<%s display_id=%s>" % (self.__class__.__name__, self.display_id)
|
351 |
+
|
352 |
+
def display(self, obj, **kwargs):
|
353 |
+
"""Make a new display with my id, updating existing instances.
|
354 |
+
|
355 |
+
Parameters
|
356 |
+
----------
|
357 |
+
obj
|
358 |
+
object to display
|
359 |
+
**kwargs
|
360 |
+
additional keyword arguments passed to display
|
361 |
+
"""
|
362 |
+
display(obj, display_id=self.display_id, **kwargs)
|
363 |
+
|
364 |
+
def update(self, obj, **kwargs):
|
365 |
+
"""Update existing displays with my id
|
366 |
+
|
367 |
+
Parameters
|
368 |
+
----------
|
369 |
+
obj
|
370 |
+
object to display
|
371 |
+
**kwargs
|
372 |
+
additional keyword arguments passed to update_display
|
373 |
+
"""
|
374 |
+
update_display(obj, display_id=self.display_id, **kwargs)
|
375 |
+
|
376 |
+
|
377 |
+
def clear_output(wait=False):
|
378 |
+
"""Clear the output of the current cell receiving output.
|
379 |
+
|
380 |
+
Parameters
|
381 |
+
----------
|
382 |
+
wait : bool [default: false]
|
383 |
+
Wait to clear the output until new output is available to replace it."""
|
384 |
+
from IPython.core.interactiveshell import InteractiveShell
|
385 |
+
if InteractiveShell.initialized():
|
386 |
+
InteractiveShell.instance().display_pub.clear_output(wait)
|
387 |
+
else:
|
388 |
+
print('\033[2K\r', end='')
|
389 |
+
sys.stdout.flush()
|
390 |
+
print('\033[2K\r', end='')
|
391 |
+
sys.stderr.flush()
|
openalex_env_map/lib/python3.10/site-packages/IPython/core/display_trap.py
ADDED
@@ -0,0 +1,70 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# encoding: utf-8
|
2 |
+
"""
|
3 |
+
A context manager for handling sys.displayhook.
|
4 |
+
|
5 |
+
Authors:
|
6 |
+
|
7 |
+
* Robert Kern
|
8 |
+
* Brian Granger
|
9 |
+
"""
|
10 |
+
|
11 |
+
#-----------------------------------------------------------------------------
|
12 |
+
# Copyright (C) 2008-2011 The IPython Development Team
|
13 |
+
#
|
14 |
+
# Distributed under the terms of the BSD License. The full license is in
|
15 |
+
# the file COPYING, distributed as part of this software.
|
16 |
+
#-----------------------------------------------------------------------------
|
17 |
+
|
18 |
+
#-----------------------------------------------------------------------------
|
19 |
+
# Imports
|
20 |
+
#-----------------------------------------------------------------------------
|
21 |
+
|
22 |
+
import sys
|
23 |
+
|
24 |
+
from traitlets.config.configurable import Configurable
|
25 |
+
from traitlets import Any
|
26 |
+
|
27 |
+
#-----------------------------------------------------------------------------
|
28 |
+
# Classes and functions
|
29 |
+
#-----------------------------------------------------------------------------
|
30 |
+
|
31 |
+
|
32 |
+
class DisplayTrap(Configurable):
|
33 |
+
"""Object to manage sys.displayhook.
|
34 |
+
|
35 |
+
This came from IPython.core.kernel.display_hook, but is simplified
|
36 |
+
(no callbacks or formatters) until more of the core is refactored.
|
37 |
+
"""
|
38 |
+
|
39 |
+
hook = Any()
|
40 |
+
|
41 |
+
def __init__(self, hook=None):
|
42 |
+
super(DisplayTrap, self).__init__(hook=hook, config=None)
|
43 |
+
self.old_hook = None
|
44 |
+
# We define this to track if a single BuiltinTrap is nested.
|
45 |
+
# Only turn off the trap when the outermost call to __exit__ is made.
|
46 |
+
self._nested_level = 0
|
47 |
+
|
48 |
+
def __enter__(self):
|
49 |
+
if self._nested_level == 0:
|
50 |
+
self.set()
|
51 |
+
self._nested_level += 1
|
52 |
+
return self
|
53 |
+
|
54 |
+
def __exit__(self, type, value, traceback):
|
55 |
+
if self._nested_level == 1:
|
56 |
+
self.unset()
|
57 |
+
self._nested_level -= 1
|
58 |
+
# Returning False will cause exceptions to propagate
|
59 |
+
return False
|
60 |
+
|
61 |
+
def set(self):
|
62 |
+
"""Set the hook."""
|
63 |
+
if sys.displayhook is not self.hook:
|
64 |
+
self.old_hook = sys.displayhook
|
65 |
+
sys.displayhook = self.hook
|
66 |
+
|
67 |
+
def unset(self):
|
68 |
+
"""Unset the hook."""
|
69 |
+
sys.displayhook = self.old_hook
|
70 |
+
|
openalex_env_map/lib/python3.10/site-packages/IPython/core/displayhook.py
ADDED
@@ -0,0 +1,336 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# -*- coding: utf-8 -*-
|
2 |
+
"""Displayhook for IPython.
|
3 |
+
|
4 |
+
This defines a callable class that IPython uses for `sys.displayhook`.
|
5 |
+
"""
|
6 |
+
|
7 |
+
# Copyright (c) IPython Development Team.
|
8 |
+
# Distributed under the terms of the Modified BSD License.
|
9 |
+
|
10 |
+
import builtins as builtin_mod
|
11 |
+
import sys
|
12 |
+
import io as _io
|
13 |
+
import tokenize
|
14 |
+
|
15 |
+
from traitlets.config.configurable import Configurable
|
16 |
+
from traitlets import Instance, Float
|
17 |
+
from warnings import warn
|
18 |
+
|
19 |
+
# TODO: Move the various attributes (cache_size, [others now moved]). Some
|
20 |
+
# of these are also attributes of InteractiveShell. They should be on ONE object
|
21 |
+
# only and the other objects should ask that one object for their values.
|
22 |
+
|
23 |
+
class DisplayHook(Configurable):
|
24 |
+
"""The custom IPython displayhook to replace sys.displayhook.
|
25 |
+
|
26 |
+
This class does many things, but the basic idea is that it is a callable
|
27 |
+
that gets called anytime user code returns a value.
|
28 |
+
"""
|
29 |
+
|
30 |
+
shell = Instance('IPython.core.interactiveshell.InteractiveShellABC',
|
31 |
+
allow_none=True)
|
32 |
+
exec_result = Instance('IPython.core.interactiveshell.ExecutionResult',
|
33 |
+
allow_none=True)
|
34 |
+
cull_fraction = Float(0.2)
|
35 |
+
|
36 |
+
def __init__(self, shell=None, cache_size=1000, **kwargs):
|
37 |
+
super(DisplayHook, self).__init__(shell=shell, **kwargs)
|
38 |
+
cache_size_min = 3
|
39 |
+
if cache_size <= 0:
|
40 |
+
self.do_full_cache = 0
|
41 |
+
cache_size = 0
|
42 |
+
elif cache_size < cache_size_min:
|
43 |
+
self.do_full_cache = 0
|
44 |
+
cache_size = 0
|
45 |
+
warn('caching was disabled (min value for cache size is %s).' %
|
46 |
+
cache_size_min,stacklevel=3)
|
47 |
+
else:
|
48 |
+
self.do_full_cache = 1
|
49 |
+
|
50 |
+
self.cache_size = cache_size
|
51 |
+
|
52 |
+
# we need a reference to the user-level namespace
|
53 |
+
self.shell = shell
|
54 |
+
|
55 |
+
self._,self.__,self.___ = '','',''
|
56 |
+
|
57 |
+
# these are deliberately global:
|
58 |
+
to_user_ns = {'_':self._,'__':self.__,'___':self.___}
|
59 |
+
self.shell.user_ns.update(to_user_ns)
|
60 |
+
|
61 |
+
@property
|
62 |
+
def prompt_count(self):
|
63 |
+
return self.shell.execution_count
|
64 |
+
|
65 |
+
#-------------------------------------------------------------------------
|
66 |
+
# Methods used in __call__. Override these methods to modify the behavior
|
67 |
+
# of the displayhook.
|
68 |
+
#-------------------------------------------------------------------------
|
69 |
+
|
70 |
+
def check_for_underscore(self):
|
71 |
+
"""Check if the user has set the '_' variable by hand."""
|
72 |
+
# If something injected a '_' variable in __builtin__, delete
|
73 |
+
# ipython's automatic one so we don't clobber that. gettext() in
|
74 |
+
# particular uses _, so we need to stay away from it.
|
75 |
+
if '_' in builtin_mod.__dict__:
|
76 |
+
try:
|
77 |
+
user_value = self.shell.user_ns['_']
|
78 |
+
if user_value is not self._:
|
79 |
+
return
|
80 |
+
del self.shell.user_ns['_']
|
81 |
+
except KeyError:
|
82 |
+
pass
|
83 |
+
|
84 |
+
def quiet(self):
|
85 |
+
"""Should we silence the display hook because of ';'?"""
|
86 |
+
# do not print output if input ends in ';'
|
87 |
+
|
88 |
+
try:
|
89 |
+
cell = self.shell.history_manager.input_hist_parsed[-1]
|
90 |
+
except IndexError:
|
91 |
+
# some uses of ipshellembed may fail here
|
92 |
+
return False
|
93 |
+
|
94 |
+
return self.semicolon_at_end_of_expression(cell)
|
95 |
+
|
96 |
+
@staticmethod
|
97 |
+
def semicolon_at_end_of_expression(expression):
|
98 |
+
"""Parse Python expression and detects whether last token is ';'"""
|
99 |
+
|
100 |
+
sio = _io.StringIO(expression)
|
101 |
+
tokens = list(tokenize.generate_tokens(sio.readline))
|
102 |
+
|
103 |
+
for token in reversed(tokens):
|
104 |
+
if token[0] in (tokenize.ENDMARKER, tokenize.NL, tokenize.NEWLINE, tokenize.COMMENT):
|
105 |
+
continue
|
106 |
+
if (token[0] == tokenize.OP) and (token[1] == ';'):
|
107 |
+
return True
|
108 |
+
else:
|
109 |
+
return False
|
110 |
+
|
111 |
+
def start_displayhook(self):
|
112 |
+
"""Start the displayhook, initializing resources."""
|
113 |
+
pass
|
114 |
+
|
115 |
+
def write_output_prompt(self):
|
116 |
+
"""Write the output prompt.
|
117 |
+
|
118 |
+
The default implementation simply writes the prompt to
|
119 |
+
``sys.stdout``.
|
120 |
+
"""
|
121 |
+
# Use write, not print which adds an extra space.
|
122 |
+
sys.stdout.write(self.shell.separate_out)
|
123 |
+
outprompt = 'Out[{}]: '.format(self.shell.execution_count)
|
124 |
+
if self.do_full_cache:
|
125 |
+
sys.stdout.write(outprompt)
|
126 |
+
|
127 |
+
def compute_format_data(self, result):
|
128 |
+
"""Compute format data of the object to be displayed.
|
129 |
+
|
130 |
+
The format data is a generalization of the :func:`repr` of an object.
|
131 |
+
In the default implementation the format data is a :class:`dict` of
|
132 |
+
key value pair where the keys are valid MIME types and the values
|
133 |
+
are JSON'able data structure containing the raw data for that MIME
|
134 |
+
type. It is up to frontends to determine pick a MIME to to use and
|
135 |
+
display that data in an appropriate manner.
|
136 |
+
|
137 |
+
This method only computes the format data for the object and should
|
138 |
+
NOT actually print or write that to a stream.
|
139 |
+
|
140 |
+
Parameters
|
141 |
+
----------
|
142 |
+
result : object
|
143 |
+
The Python object passed to the display hook, whose format will be
|
144 |
+
computed.
|
145 |
+
|
146 |
+
Returns
|
147 |
+
-------
|
148 |
+
(format_dict, md_dict) : dict
|
149 |
+
format_dict is a :class:`dict` whose keys are valid MIME types and values are
|
150 |
+
JSON'able raw data for that MIME type. It is recommended that
|
151 |
+
all return values of this should always include the "text/plain"
|
152 |
+
MIME type representation of the object.
|
153 |
+
md_dict is a :class:`dict` with the same MIME type keys
|
154 |
+
of metadata associated with each output.
|
155 |
+
|
156 |
+
"""
|
157 |
+
return self.shell.display_formatter.format(result)
|
158 |
+
|
159 |
+
# This can be set to True by the write_output_prompt method in a subclass
|
160 |
+
prompt_end_newline = False
|
161 |
+
|
162 |
+
def write_format_data(self, format_dict, md_dict=None) -> None:
|
163 |
+
"""Write the format data dict to the frontend.
|
164 |
+
|
165 |
+
This default version of this method simply writes the plain text
|
166 |
+
representation of the object to ``sys.stdout``. Subclasses should
|
167 |
+
override this method to send the entire `format_dict` to the
|
168 |
+
frontends.
|
169 |
+
|
170 |
+
Parameters
|
171 |
+
----------
|
172 |
+
format_dict : dict
|
173 |
+
The format dict for the object passed to `sys.displayhook`.
|
174 |
+
md_dict : dict (optional)
|
175 |
+
The metadata dict to be associated with the display data.
|
176 |
+
"""
|
177 |
+
if 'text/plain' not in format_dict:
|
178 |
+
# nothing to do
|
179 |
+
return
|
180 |
+
# We want to print because we want to always make sure we have a
|
181 |
+
# newline, even if all the prompt separators are ''. This is the
|
182 |
+
# standard IPython behavior.
|
183 |
+
result_repr = format_dict['text/plain']
|
184 |
+
if '\n' in result_repr:
|
185 |
+
# So that multi-line strings line up with the left column of
|
186 |
+
# the screen, instead of having the output prompt mess up
|
187 |
+
# their first line.
|
188 |
+
# We use the prompt template instead of the expanded prompt
|
189 |
+
# because the expansion may add ANSI escapes that will interfere
|
190 |
+
# with our ability to determine whether or not we should add
|
191 |
+
# a newline.
|
192 |
+
if not self.prompt_end_newline:
|
193 |
+
# But avoid extraneous empty lines.
|
194 |
+
result_repr = '\n' + result_repr
|
195 |
+
|
196 |
+
try:
|
197 |
+
print(result_repr)
|
198 |
+
except UnicodeEncodeError:
|
199 |
+
# If a character is not supported by the terminal encoding replace
|
200 |
+
# it with its \u or \x representation
|
201 |
+
print(result_repr.encode(sys.stdout.encoding,'backslashreplace').decode(sys.stdout.encoding))
|
202 |
+
|
203 |
+
def update_user_ns(self, result):
|
204 |
+
"""Update user_ns with various things like _, __, _1, etc."""
|
205 |
+
|
206 |
+
# Avoid recursive reference when displaying _oh/Out
|
207 |
+
if self.cache_size and result is not self.shell.user_ns['_oh']:
|
208 |
+
if len(self.shell.user_ns['_oh']) >= self.cache_size and self.do_full_cache:
|
209 |
+
self.cull_cache()
|
210 |
+
|
211 |
+
# Don't overwrite '_' and friends if '_' is in __builtin__
|
212 |
+
# (otherwise we cause buggy behavior for things like gettext). and
|
213 |
+
# do not overwrite _, __ or ___ if one of these has been assigned
|
214 |
+
# by the user.
|
215 |
+
update_unders = True
|
216 |
+
for unders in ['_'*i for i in range(1,4)]:
|
217 |
+
if not unders in self.shell.user_ns:
|
218 |
+
continue
|
219 |
+
if getattr(self, unders) is not self.shell.user_ns.get(unders):
|
220 |
+
update_unders = False
|
221 |
+
|
222 |
+
self.___ = self.__
|
223 |
+
self.__ = self._
|
224 |
+
self._ = result
|
225 |
+
|
226 |
+
if ('_' not in builtin_mod.__dict__) and (update_unders):
|
227 |
+
self.shell.push({'_':self._,
|
228 |
+
'__':self.__,
|
229 |
+
'___':self.___}, interactive=False)
|
230 |
+
|
231 |
+
# hackish access to top-level namespace to create _1,_2... dynamically
|
232 |
+
to_main = {}
|
233 |
+
if self.do_full_cache:
|
234 |
+
new_result = '_%s' % self.prompt_count
|
235 |
+
to_main[new_result] = result
|
236 |
+
self.shell.push(to_main, interactive=False)
|
237 |
+
self.shell.user_ns['_oh'][self.prompt_count] = result
|
238 |
+
|
239 |
+
def fill_exec_result(self, result):
|
240 |
+
if self.exec_result is not None:
|
241 |
+
self.exec_result.result = result
|
242 |
+
|
243 |
+
def log_output(self, format_dict):
|
244 |
+
"""Log the output."""
|
245 |
+
if 'text/plain' not in format_dict:
|
246 |
+
# nothing to do
|
247 |
+
return
|
248 |
+
if self.shell.logger.log_output:
|
249 |
+
self.shell.logger.log_write(format_dict['text/plain'], 'output')
|
250 |
+
self.shell.history_manager.output_hist_reprs[self.prompt_count] = \
|
251 |
+
format_dict['text/plain']
|
252 |
+
|
253 |
+
def finish_displayhook(self):
|
254 |
+
"""Finish up all displayhook activities."""
|
255 |
+
sys.stdout.write(self.shell.separate_out2)
|
256 |
+
sys.stdout.flush()
|
257 |
+
|
258 |
+
def __call__(self, result=None):
|
259 |
+
"""Printing with history cache management.
|
260 |
+
|
261 |
+
This is invoked every time the interpreter needs to print, and is
|
262 |
+
activated by setting the variable sys.displayhook to it.
|
263 |
+
"""
|
264 |
+
self.check_for_underscore()
|
265 |
+
if result is not None and not self.quiet():
|
266 |
+
self.start_displayhook()
|
267 |
+
self.write_output_prompt()
|
268 |
+
format_dict, md_dict = self.compute_format_data(result)
|
269 |
+
self.update_user_ns(result)
|
270 |
+
self.fill_exec_result(result)
|
271 |
+
if format_dict:
|
272 |
+
self.write_format_data(format_dict, md_dict)
|
273 |
+
self.log_output(format_dict)
|
274 |
+
self.finish_displayhook()
|
275 |
+
|
276 |
+
def cull_cache(self):
|
277 |
+
"""Output cache is full, cull the oldest entries"""
|
278 |
+
oh = self.shell.user_ns.get('_oh', {})
|
279 |
+
sz = len(oh)
|
280 |
+
cull_count = max(int(sz * self.cull_fraction), 2)
|
281 |
+
warn('Output cache limit (currently {sz} entries) hit.\n'
|
282 |
+
'Flushing oldest {cull_count} entries.'.format(sz=sz, cull_count=cull_count))
|
283 |
+
|
284 |
+
for i, n in enumerate(sorted(oh)):
|
285 |
+
if i >= cull_count:
|
286 |
+
break
|
287 |
+
self.shell.user_ns.pop('_%i' % n, None)
|
288 |
+
oh.pop(n, None)
|
289 |
+
|
290 |
+
|
291 |
+
def flush(self):
|
292 |
+
if not self.do_full_cache:
|
293 |
+
raise ValueError("You shouldn't have reached the cache flush "
|
294 |
+
"if full caching is not enabled!")
|
295 |
+
# delete auto-generated vars from global namespace
|
296 |
+
|
297 |
+
for n in range(1,self.prompt_count + 1):
|
298 |
+
key = '_'+repr(n)
|
299 |
+
try:
|
300 |
+
del self.shell.user_ns_hidden[key]
|
301 |
+
except KeyError:
|
302 |
+
pass
|
303 |
+
try:
|
304 |
+
del self.shell.user_ns[key]
|
305 |
+
except KeyError:
|
306 |
+
pass
|
307 |
+
# In some embedded circumstances, the user_ns doesn't have the
|
308 |
+
# '_oh' key set up.
|
309 |
+
oh = self.shell.user_ns.get('_oh', None)
|
310 |
+
if oh is not None:
|
311 |
+
oh.clear()
|
312 |
+
|
313 |
+
# Release our own references to objects:
|
314 |
+
self._, self.__, self.___ = '', '', ''
|
315 |
+
|
316 |
+
if '_' not in builtin_mod.__dict__:
|
317 |
+
self.shell.user_ns.update({'_':self._,'__':self.__,'___':self.___})
|
318 |
+
import gc
|
319 |
+
# TODO: Is this really needed?
|
320 |
+
# IronPython blocks here forever
|
321 |
+
if sys.platform != "cli":
|
322 |
+
gc.collect()
|
323 |
+
|
324 |
+
|
325 |
+
class CapturingDisplayHook(object):
|
326 |
+
def __init__(self, shell, outputs=None):
|
327 |
+
self.shell = shell
|
328 |
+
if outputs is None:
|
329 |
+
outputs = []
|
330 |
+
self.outputs = outputs
|
331 |
+
|
332 |
+
def __call__(self, result=None):
|
333 |
+
if result is None:
|
334 |
+
return
|
335 |
+
format_dict, md_dict = self.shell.display_formatter.format(result)
|
336 |
+
self.outputs.append({ 'data': format_dict, 'metadata': md_dict })
|
openalex_env_map/lib/python3.10/site-packages/IPython/core/displaypub.py
ADDED
@@ -0,0 +1,149 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"""An interface for publishing rich data to frontends.
|
2 |
+
|
3 |
+
There are two components of the display system:
|
4 |
+
|
5 |
+
* Display formatters, which take a Python object and compute the
|
6 |
+
representation of the object in various formats (text, HTML, SVG, etc.).
|
7 |
+
* The display publisher that is used to send the representation data to the
|
8 |
+
various frontends.
|
9 |
+
|
10 |
+
This module defines the logic display publishing. The display publisher uses
|
11 |
+
the ``display_data`` message type that is defined in the IPython messaging
|
12 |
+
spec.
|
13 |
+
"""
|
14 |
+
|
15 |
+
# Copyright (c) IPython Development Team.
|
16 |
+
# Distributed under the terms of the Modified BSD License.
|
17 |
+
|
18 |
+
|
19 |
+
import sys
|
20 |
+
|
21 |
+
from traitlets.config.configurable import Configurable
|
22 |
+
from traitlets import List
|
23 |
+
|
24 |
+
# This used to be defined here - it is imported for backwards compatibility
|
25 |
+
from .display_functions import publish_display_data
|
26 |
+
|
27 |
+
import typing as t
|
28 |
+
|
29 |
+
# -----------------------------------------------------------------------------
|
30 |
+
# Main payload class
|
31 |
+
#-----------------------------------------------------------------------------
|
32 |
+
|
33 |
+
|
34 |
+
class DisplayPublisher(Configurable):
|
35 |
+
"""A traited class that publishes display data to frontends.
|
36 |
+
|
37 |
+
Instances of this class are created by the main IPython object and should
|
38 |
+
be accessed there.
|
39 |
+
"""
|
40 |
+
|
41 |
+
def __init__(self, shell=None, *args, **kwargs):
|
42 |
+
self.shell = shell
|
43 |
+
super().__init__(*args, **kwargs)
|
44 |
+
|
45 |
+
def _validate_data(self, data, metadata=None):
|
46 |
+
"""Validate the display data.
|
47 |
+
|
48 |
+
Parameters
|
49 |
+
----------
|
50 |
+
data : dict
|
51 |
+
The formata data dictionary.
|
52 |
+
metadata : dict
|
53 |
+
Any metadata for the data.
|
54 |
+
"""
|
55 |
+
|
56 |
+
if not isinstance(data, dict):
|
57 |
+
raise TypeError('data must be a dict, got: %r' % data)
|
58 |
+
if metadata is not None:
|
59 |
+
if not isinstance(metadata, dict):
|
60 |
+
raise TypeError('metadata must be a dict, got: %r' % data)
|
61 |
+
|
62 |
+
# use * to indicate transient, update are keyword-only
|
63 |
+
def publish(self, data, metadata=None, source=None, *, transient=None, update=False, **kwargs) -> None:
|
64 |
+
"""Publish data and metadata to all frontends.
|
65 |
+
|
66 |
+
See the ``display_data`` message in the messaging documentation for
|
67 |
+
more details about this message type.
|
68 |
+
|
69 |
+
The following MIME types are currently implemented:
|
70 |
+
|
71 |
+
* text/plain
|
72 |
+
* text/html
|
73 |
+
* text/markdown
|
74 |
+
* text/latex
|
75 |
+
* application/json
|
76 |
+
* application/javascript
|
77 |
+
* image/png
|
78 |
+
* image/jpeg
|
79 |
+
* image/svg+xml
|
80 |
+
|
81 |
+
Parameters
|
82 |
+
----------
|
83 |
+
data : dict
|
84 |
+
A dictionary having keys that are valid MIME types (like
|
85 |
+
'text/plain' or 'image/svg+xml') and values that are the data for
|
86 |
+
that MIME type. The data itself must be a JSON'able data
|
87 |
+
structure. Minimally all data should have the 'text/plain' data,
|
88 |
+
which can be displayed by all frontends. If more than the plain
|
89 |
+
text is given, it is up to the frontend to decide which
|
90 |
+
representation to use.
|
91 |
+
metadata : dict
|
92 |
+
A dictionary for metadata related to the data. This can contain
|
93 |
+
arbitrary key, value pairs that frontends can use to interpret
|
94 |
+
the data. Metadata specific to each mime-type can be specified
|
95 |
+
in the metadata dict with the same mime-type keys as
|
96 |
+
the data itself.
|
97 |
+
source : str, deprecated
|
98 |
+
Unused.
|
99 |
+
transient : dict, keyword-only
|
100 |
+
A dictionary for transient data.
|
101 |
+
Data in this dictionary should not be persisted as part of saving this output.
|
102 |
+
Examples include 'display_id'.
|
103 |
+
update : bool, keyword-only, default: False
|
104 |
+
If True, only update existing outputs with the same display_id,
|
105 |
+
rather than creating a new output.
|
106 |
+
"""
|
107 |
+
|
108 |
+
handlers: t.Dict = {}
|
109 |
+
if self.shell is not None:
|
110 |
+
handlers = getattr(self.shell, "mime_renderers", {})
|
111 |
+
|
112 |
+
for mime, handler in handlers.items():
|
113 |
+
if mime in data:
|
114 |
+
handler(data[mime], metadata.get(mime, None))
|
115 |
+
return
|
116 |
+
|
117 |
+
if 'text/plain' in data:
|
118 |
+
print(data['text/plain'])
|
119 |
+
|
120 |
+
def clear_output(self, wait=False):
|
121 |
+
"""Clear the output of the cell receiving output."""
|
122 |
+
print('\033[2K\r', end='')
|
123 |
+
sys.stdout.flush()
|
124 |
+
print('\033[2K\r', end='')
|
125 |
+
sys.stderr.flush()
|
126 |
+
|
127 |
+
|
128 |
+
class CapturingDisplayPublisher(DisplayPublisher):
|
129 |
+
"""A DisplayPublisher that stores"""
|
130 |
+
|
131 |
+
outputs: List = List()
|
132 |
+
|
133 |
+
def publish(
|
134 |
+
self, data, metadata=None, source=None, *, transient=None, update=False
|
135 |
+
):
|
136 |
+
self.outputs.append(
|
137 |
+
{
|
138 |
+
"data": data,
|
139 |
+
"metadata": metadata,
|
140 |
+
"transient": transient,
|
141 |
+
"update": update,
|
142 |
+
}
|
143 |
+
)
|
144 |
+
|
145 |
+
def clear_output(self, wait=False):
|
146 |
+
super(CapturingDisplayPublisher, self).clear_output(wait)
|
147 |
+
|
148 |
+
# empty the list, *do not* reassign a new list
|
149 |
+
self.outputs.clear()
|
openalex_env_map/lib/python3.10/site-packages/IPython/core/error.py
ADDED
@@ -0,0 +1,60 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# encoding: utf-8
|
2 |
+
"""
|
3 |
+
Global exception classes for IPython.core.
|
4 |
+
|
5 |
+
Authors:
|
6 |
+
|
7 |
+
* Brian Granger
|
8 |
+
* Fernando Perez
|
9 |
+
* Min Ragan-Kelley
|
10 |
+
|
11 |
+
Notes
|
12 |
+
-----
|
13 |
+
"""
|
14 |
+
|
15 |
+
#-----------------------------------------------------------------------------
|
16 |
+
# Copyright (C) 2008 The IPython Development Team
|
17 |
+
#
|
18 |
+
# Distributed under the terms of the BSD License. The full license is in
|
19 |
+
# the file COPYING, distributed as part of this software.
|
20 |
+
#-----------------------------------------------------------------------------
|
21 |
+
|
22 |
+
#-----------------------------------------------------------------------------
|
23 |
+
# Imports
|
24 |
+
#-----------------------------------------------------------------------------
|
25 |
+
|
26 |
+
#-----------------------------------------------------------------------------
|
27 |
+
# Exception classes
|
28 |
+
#-----------------------------------------------------------------------------
|
29 |
+
|
30 |
+
class IPythonCoreError(Exception):
|
31 |
+
pass
|
32 |
+
|
33 |
+
|
34 |
+
class TryNext(IPythonCoreError):
|
35 |
+
"""Try next hook exception.
|
36 |
+
|
37 |
+
Raise this in your hook function to indicate that the next hook handler
|
38 |
+
should be used to handle the operation.
|
39 |
+
"""
|
40 |
+
|
41 |
+
class UsageError(IPythonCoreError):
|
42 |
+
"""Error in magic function arguments, etc.
|
43 |
+
|
44 |
+
Something that probably won't warrant a full traceback, but should
|
45 |
+
nevertheless interrupt a macro / batch file.
|
46 |
+
"""
|
47 |
+
|
48 |
+
class StdinNotImplementedError(IPythonCoreError, NotImplementedError):
|
49 |
+
"""raw_input was requested in a context where it is not supported
|
50 |
+
|
51 |
+
For use in IPython kernels, where only some frontends may support
|
52 |
+
stdin requests.
|
53 |
+
"""
|
54 |
+
|
55 |
+
class InputRejected(Exception):
|
56 |
+
"""Input rejected by ast transformer.
|
57 |
+
|
58 |
+
Raise this in your NodeTransformer to indicate that InteractiveShell should
|
59 |
+
not execute the supplied input.
|
60 |
+
"""
|
openalex_env_map/lib/python3.10/site-packages/IPython/core/events.py
ADDED
@@ -0,0 +1,158 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"""Infrastructure for registering and firing callbacks on application events.
|
2 |
+
|
3 |
+
Unlike :mod:`IPython.core.hooks`, which lets end users set single functions to
|
4 |
+
be called at specific times, or a collection of alternative methods to try,
|
5 |
+
callbacks are designed to be used by extension authors. A number of callbacks
|
6 |
+
can be registered for the same event without needing to be aware of one another.
|
7 |
+
|
8 |
+
The functions defined in this module are no-ops indicating the names of available
|
9 |
+
events and the arguments which will be passed to them.
|
10 |
+
|
11 |
+
.. note::
|
12 |
+
|
13 |
+
This API is experimental in IPython 2.0, and may be revised in future versions.
|
14 |
+
"""
|
15 |
+
|
16 |
+
|
17 |
+
class EventManager(object):
|
18 |
+
"""Manage a collection of events and a sequence of callbacks for each.
|
19 |
+
|
20 |
+
This is attached to :class:`~IPython.core.interactiveshell.InteractiveShell`
|
21 |
+
instances as an ``events`` attribute.
|
22 |
+
|
23 |
+
.. note::
|
24 |
+
|
25 |
+
This API is experimental in IPython 2.0, and may be revised in future versions.
|
26 |
+
"""
|
27 |
+
|
28 |
+
def __init__(self, shell, available_events, print_on_error=True):
|
29 |
+
"""Initialise the :class:`CallbackManager`.
|
30 |
+
|
31 |
+
Parameters
|
32 |
+
----------
|
33 |
+
shell
|
34 |
+
The :class:`~IPython.core.interactiveshell.InteractiveShell` instance
|
35 |
+
available_events
|
36 |
+
An iterable of names for callback events.
|
37 |
+
print_on_error:
|
38 |
+
A boolean flag to set whether the EventManager will print a warning which a event errors.
|
39 |
+
"""
|
40 |
+
self.shell = shell
|
41 |
+
self.callbacks = {n:[] for n in available_events}
|
42 |
+
self.print_on_error = print_on_error
|
43 |
+
|
44 |
+
def register(self, event, function):
|
45 |
+
"""Register a new event callback.
|
46 |
+
|
47 |
+
Parameters
|
48 |
+
----------
|
49 |
+
event : str
|
50 |
+
The event for which to register this callback.
|
51 |
+
function : callable
|
52 |
+
A function to be called on the given event. It should take the same
|
53 |
+
parameters as the appropriate callback prototype.
|
54 |
+
|
55 |
+
Raises
|
56 |
+
------
|
57 |
+
TypeError
|
58 |
+
If ``function`` is not callable.
|
59 |
+
KeyError
|
60 |
+
If ``event`` is not one of the known events.
|
61 |
+
"""
|
62 |
+
if not callable(function):
|
63 |
+
raise TypeError('Need a callable, got %r' % function)
|
64 |
+
if function not in self.callbacks[event]:
|
65 |
+
self.callbacks[event].append(function)
|
66 |
+
|
67 |
+
def unregister(self, event, function):
|
68 |
+
"""Remove a callback from the given event."""
|
69 |
+
if function in self.callbacks[event]:
|
70 |
+
return self.callbacks[event].remove(function)
|
71 |
+
|
72 |
+
raise ValueError('Function {!r} is not registered as a {} callback'.format(function, event))
|
73 |
+
|
74 |
+
def trigger(self, event, *args, **kwargs):
|
75 |
+
"""Call callbacks for ``event``.
|
76 |
+
|
77 |
+
Any additional arguments are passed to all callbacks registered for this
|
78 |
+
event. Exceptions raised by callbacks are caught, and a message printed.
|
79 |
+
"""
|
80 |
+
for func in self.callbacks[event][:]:
|
81 |
+
try:
|
82 |
+
func(*args, **kwargs)
|
83 |
+
except (Exception, KeyboardInterrupt):
|
84 |
+
if self.print_on_error:
|
85 |
+
print(
|
86 |
+
"Error in callback {} (for {}), with arguments args {},kwargs {}:".format(
|
87 |
+
func, event, args, kwargs
|
88 |
+
)
|
89 |
+
)
|
90 |
+
self.shell.showtraceback()
|
91 |
+
|
92 |
+
# event_name -> prototype mapping
|
93 |
+
available_events = {}
|
94 |
+
|
95 |
+
def _define_event(callback_function):
|
96 |
+
available_events[callback_function.__name__] = callback_function
|
97 |
+
return callback_function
|
98 |
+
|
99 |
+
# ------------------------------------------------------------------------------
|
100 |
+
# Callback prototypes
|
101 |
+
#
|
102 |
+
# No-op functions which describe the names of available events and the
|
103 |
+
# signatures of callbacks for those events.
|
104 |
+
# ------------------------------------------------------------------------------
|
105 |
+
|
106 |
+
@_define_event
|
107 |
+
def pre_execute():
|
108 |
+
"""Fires before code is executed in response to user/frontend action.
|
109 |
+
|
110 |
+
This includes comm and widget messages and silent execution, as well as user
|
111 |
+
code cells.
|
112 |
+
"""
|
113 |
+
pass
|
114 |
+
|
115 |
+
@_define_event
|
116 |
+
def pre_run_cell(info):
|
117 |
+
"""Fires before user-entered code runs.
|
118 |
+
|
119 |
+
Parameters
|
120 |
+
----------
|
121 |
+
info : :class:`~IPython.core.interactiveshell.ExecutionInfo`
|
122 |
+
An object containing information used for the code execution.
|
123 |
+
"""
|
124 |
+
pass
|
125 |
+
|
126 |
+
@_define_event
|
127 |
+
def post_execute():
|
128 |
+
"""Fires after code is executed in response to user/frontend action.
|
129 |
+
|
130 |
+
This includes comm and widget messages and silent execution, as well as user
|
131 |
+
code cells.
|
132 |
+
"""
|
133 |
+
pass
|
134 |
+
|
135 |
+
@_define_event
|
136 |
+
def post_run_cell(result):
|
137 |
+
"""Fires after user-entered code runs.
|
138 |
+
|
139 |
+
Parameters
|
140 |
+
----------
|
141 |
+
result : :class:`~IPython.core.interactiveshell.ExecutionResult`
|
142 |
+
The object which will be returned as the execution result.
|
143 |
+
"""
|
144 |
+
pass
|
145 |
+
|
146 |
+
@_define_event
|
147 |
+
def shell_initialized(ip):
|
148 |
+
"""Fires after initialisation of :class:`~IPython.core.interactiveshell.InteractiveShell`.
|
149 |
+
|
150 |
+
This is before extensions and startup scripts are loaded, so it can only be
|
151 |
+
set by subclassing.
|
152 |
+
|
153 |
+
Parameters
|
154 |
+
----------
|
155 |
+
ip : :class:`~IPython.core.interactiveshell.InteractiveShell`
|
156 |
+
The newly initialised shell.
|
157 |
+
"""
|
158 |
+
pass
|
openalex_env_map/lib/python3.10/site-packages/IPython/core/excolors.py
ADDED
@@ -0,0 +1,192 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# -*- coding: utf-8 -*-
|
2 |
+
"""
|
3 |
+
Color schemes for exception handling code in IPython.
|
4 |
+
"""
|
5 |
+
|
6 |
+
import os
|
7 |
+
|
8 |
+
#*****************************************************************************
|
9 |
+
# Copyright (C) 2005-2006 Fernando Perez <[email protected]>
|
10 |
+
#
|
11 |
+
# Distributed under the terms of the BSD License. The full license is in
|
12 |
+
# the file COPYING, distributed as part of this software.
|
13 |
+
#*****************************************************************************
|
14 |
+
|
15 |
+
from IPython.utils.coloransi import ColorSchemeTable, TermColors, ColorScheme
|
16 |
+
|
17 |
+
def exception_colors():
|
18 |
+
"""Return a color table with fields for exception reporting.
|
19 |
+
|
20 |
+
The table is an instance of ColorSchemeTable with schemes added for
|
21 |
+
'Neutral', 'Linux', 'LightBG' and 'NoColor' and fields for exception handling filled
|
22 |
+
in.
|
23 |
+
|
24 |
+
Examples:
|
25 |
+
|
26 |
+
>>> ec = exception_colors()
|
27 |
+
>>> ec.active_scheme_name
|
28 |
+
''
|
29 |
+
>>> print(ec.active_colors)
|
30 |
+
None
|
31 |
+
|
32 |
+
Now we activate a color scheme:
|
33 |
+
>>> ec.set_active_scheme('NoColor')
|
34 |
+
>>> ec.active_scheme_name
|
35 |
+
'NoColor'
|
36 |
+
>>> sorted(ec.active_colors.keys())
|
37 |
+
['Normal', 'breakpoint_disabled', 'breakpoint_enabled', 'caret', 'em',
|
38 |
+
'excName', 'filename', 'filenameEm', 'line', 'lineno', 'linenoEm', 'name',
|
39 |
+
'nameEm', 'normalEm', 'prompt', 'topline', 'vName', 'val', 'valEm']
|
40 |
+
|
41 |
+
"""
|
42 |
+
|
43 |
+
ex_colors = ColorSchemeTable()
|
44 |
+
|
45 |
+
# Populate it with color schemes
|
46 |
+
C = TermColors # shorthand and local lookup
|
47 |
+
ex_colors.add_scheme(
|
48 |
+
ColorScheme(
|
49 |
+
"NoColor",
|
50 |
+
{
|
51 |
+
# The color to be used for the top line
|
52 |
+
"topline": C.NoColor,
|
53 |
+
|
54 |
+
# The colors to be used in the traceback
|
55 |
+
"filename": C.NoColor,
|
56 |
+
"lineno": C.NoColor,
|
57 |
+
"name": C.NoColor,
|
58 |
+
"vName": C.NoColor,
|
59 |
+
"val": C.NoColor,
|
60 |
+
"em": C.NoColor,
|
61 |
+
|
62 |
+
# Emphasized colors for the last frame of the traceback
|
63 |
+
"normalEm": C.NoColor,
|
64 |
+
"filenameEm": C.NoColor,
|
65 |
+
"linenoEm": C.NoColor,
|
66 |
+
"nameEm": C.NoColor,
|
67 |
+
"valEm": C.NoColor,
|
68 |
+
|
69 |
+
# Colors for printing the exception
|
70 |
+
"excName": C.NoColor,
|
71 |
+
"line": C.NoColor,
|
72 |
+
"caret": C.NoColor,
|
73 |
+
"Normal": C.NoColor,
|
74 |
+
# debugger
|
75 |
+
"prompt": C.NoColor,
|
76 |
+
"breakpoint_enabled": C.NoColor,
|
77 |
+
"breakpoint_disabled": C.NoColor,
|
78 |
+
},
|
79 |
+
)
|
80 |
+
)
|
81 |
+
|
82 |
+
# make some schemes as instances so we can copy them for modification easily
|
83 |
+
ex_colors.add_scheme(
|
84 |
+
ColorScheme(
|
85 |
+
"Linux",
|
86 |
+
{
|
87 |
+
# The color to be used for the top line
|
88 |
+
"topline": C.LightRed,
|
89 |
+
# The colors to be used in the traceback
|
90 |
+
"filename": C.Green,
|
91 |
+
"lineno": C.Green,
|
92 |
+
"name": C.Purple,
|
93 |
+
"vName": C.Cyan,
|
94 |
+
"val": C.Green,
|
95 |
+
"em": C.LightCyan,
|
96 |
+
# Emphasized colors for the last frame of the traceback
|
97 |
+
"normalEm": C.LightCyan,
|
98 |
+
"filenameEm": C.LightGreen,
|
99 |
+
"linenoEm": C.LightGreen,
|
100 |
+
"nameEm": C.LightPurple,
|
101 |
+
"valEm": C.LightBlue,
|
102 |
+
# Colors for printing the exception
|
103 |
+
"excName": C.LightRed,
|
104 |
+
"line": C.Yellow,
|
105 |
+
"caret": C.White,
|
106 |
+
"Normal": C.Normal,
|
107 |
+
# debugger
|
108 |
+
"prompt": C.Green,
|
109 |
+
"breakpoint_enabled": C.LightRed,
|
110 |
+
"breakpoint_disabled": C.Red,
|
111 |
+
},
|
112 |
+
)
|
113 |
+
)
|
114 |
+
|
115 |
+
# For light backgrounds, swap dark/light colors
|
116 |
+
ex_colors.add_scheme(
|
117 |
+
ColorScheme(
|
118 |
+
"LightBG",
|
119 |
+
{
|
120 |
+
# The color to be used for the top line
|
121 |
+
"topline": C.Red,
|
122 |
+
|
123 |
+
# The colors to be used in the traceback
|
124 |
+
"filename": C.LightGreen,
|
125 |
+
"lineno": C.LightGreen,
|
126 |
+
"name": C.LightPurple,
|
127 |
+
"vName": C.Cyan,
|
128 |
+
"val": C.LightGreen,
|
129 |
+
"em": C.Cyan,
|
130 |
+
|
131 |
+
# Emphasized colors for the last frame of the traceback
|
132 |
+
"normalEm": C.Cyan,
|
133 |
+
"filenameEm": C.Green,
|
134 |
+
"linenoEm": C.Green,
|
135 |
+
"nameEm": C.Purple,
|
136 |
+
"valEm": C.Blue,
|
137 |
+
|
138 |
+
# Colors for printing the exception
|
139 |
+
"excName": C.Red,
|
140 |
+
# "line": C.Brown, # brown often is displayed as yellow
|
141 |
+
"line": C.Red,
|
142 |
+
"caret": C.Normal,
|
143 |
+
"Normal": C.Normal,
|
144 |
+
# debugger
|
145 |
+
"prompt": C.Blue,
|
146 |
+
"breakpoint_enabled": C.LightRed,
|
147 |
+
"breakpoint_disabled": C.Red,
|
148 |
+
},
|
149 |
+
)
|
150 |
+
)
|
151 |
+
|
152 |
+
ex_colors.add_scheme(
|
153 |
+
ColorScheme(
|
154 |
+
"Neutral",
|
155 |
+
{
|
156 |
+
# The color to be used for the top line
|
157 |
+
"topline": C.Red,
|
158 |
+
# The colors to be used in the traceback
|
159 |
+
"filename": C.LightGreen,
|
160 |
+
"lineno": C.LightGreen,
|
161 |
+
"name": C.LightPurple,
|
162 |
+
"vName": C.Cyan,
|
163 |
+
"val": C.LightGreen,
|
164 |
+
"em": C.Cyan,
|
165 |
+
# Emphasized colors for the last frame of the traceback
|
166 |
+
"normalEm": C.Cyan,
|
167 |
+
"filenameEm": C.Green,
|
168 |
+
"linenoEm": C.Green,
|
169 |
+
"nameEm": C.Purple,
|
170 |
+
"valEm": C.Blue,
|
171 |
+
# Colors for printing the exception
|
172 |
+
"excName": C.Red,
|
173 |
+
# line = C.Brown, # brown often is displayed as yellow
|
174 |
+
"line": C.Red,
|
175 |
+
"caret": C.Normal,
|
176 |
+
"Normal": C.Normal,
|
177 |
+
# debugger
|
178 |
+
"prompt": C.Blue,
|
179 |
+
"breakpoint_enabled": C.LightRed,
|
180 |
+
"breakpoint_disabled": C.Red,
|
181 |
+
},
|
182 |
+
)
|
183 |
+
)
|
184 |
+
|
185 |
+
# Hack: the 'neutral' colours are not very visible on a dark background on
|
186 |
+
# Windows. Since Windows command prompts have a dark background by default, and
|
187 |
+
# relatively few users are likely to alter that, we will use the 'Linux' colours,
|
188 |
+
# designed for a dark background, as the default on Windows.
|
189 |
+
if os.name == "nt":
|
190 |
+
ex_colors.add_scheme(ex_colors['Linux'].copy('Neutral'))
|
191 |
+
|
192 |
+
return ex_colors
|
openalex_env_map/lib/python3.10/site-packages/IPython/core/extensions.py
ADDED
@@ -0,0 +1,135 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# encoding: utf-8
|
2 |
+
"""A class for managing IPython extensions."""
|
3 |
+
|
4 |
+
# Copyright (c) IPython Development Team.
|
5 |
+
# Distributed under the terms of the Modified BSD License.
|
6 |
+
|
7 |
+
import os
|
8 |
+
import os.path
|
9 |
+
import sys
|
10 |
+
from importlib import import_module, reload
|
11 |
+
|
12 |
+
from traitlets.config.configurable import Configurable
|
13 |
+
from IPython.utils.path import ensure_dir_exists
|
14 |
+
from traitlets import Instance
|
15 |
+
|
16 |
+
|
17 |
+
#-----------------------------------------------------------------------------
|
18 |
+
# Main class
|
19 |
+
#-----------------------------------------------------------------------------
|
20 |
+
|
21 |
+
BUILTINS_EXTS = {"storemagic": False, "autoreload": False}
|
22 |
+
|
23 |
+
|
24 |
+
class ExtensionManager(Configurable):
|
25 |
+
"""A class to manage IPython extensions.
|
26 |
+
|
27 |
+
An IPython extension is an importable Python module that has
|
28 |
+
a function with the signature::
|
29 |
+
|
30 |
+
def load_ipython_extension(ipython):
|
31 |
+
# Do things with ipython
|
32 |
+
|
33 |
+
This function is called after your extension is imported and the
|
34 |
+
currently active :class:`InteractiveShell` instance is passed as
|
35 |
+
the only argument. You can do anything you want with IPython at
|
36 |
+
that point, including defining new magic and aliases, adding new
|
37 |
+
components, etc.
|
38 |
+
|
39 |
+
You can also optionally define an :func:`unload_ipython_extension(ipython)`
|
40 |
+
function, which will be called if the user unloads or reloads the extension.
|
41 |
+
The extension manager will only call :func:`load_ipython_extension` again
|
42 |
+
if the extension is reloaded.
|
43 |
+
|
44 |
+
You can put your extension modules anywhere you want, as long as
|
45 |
+
they can be imported by Python's standard import mechanism.
|
46 |
+
"""
|
47 |
+
|
48 |
+
shell = Instance('IPython.core.interactiveshell.InteractiveShellABC', allow_none=True)
|
49 |
+
|
50 |
+
def __init__(self, shell=None, **kwargs):
|
51 |
+
super(ExtensionManager, self).__init__(shell=shell, **kwargs)
|
52 |
+
self.loaded = set()
|
53 |
+
|
54 |
+
def load_extension(self, module_str: str):
|
55 |
+
"""Load an IPython extension by its module name.
|
56 |
+
|
57 |
+
Returns the string "already loaded" if the extension is already loaded,
|
58 |
+
"no load function" if the module doesn't have a load_ipython_extension
|
59 |
+
function, or None if it succeeded.
|
60 |
+
"""
|
61 |
+
try:
|
62 |
+
return self._load_extension(module_str)
|
63 |
+
except ModuleNotFoundError:
|
64 |
+
if module_str in BUILTINS_EXTS:
|
65 |
+
BUILTINS_EXTS[module_str] = True
|
66 |
+
return self._load_extension("IPython.extensions." + module_str)
|
67 |
+
raise
|
68 |
+
|
69 |
+
def _load_extension(self, module_str: str):
|
70 |
+
if module_str in self.loaded:
|
71 |
+
return "already loaded"
|
72 |
+
|
73 |
+
assert self.shell is not None
|
74 |
+
|
75 |
+
with self.shell.builtin_trap:
|
76 |
+
if module_str not in sys.modules:
|
77 |
+
mod = import_module(module_str)
|
78 |
+
mod = sys.modules[module_str]
|
79 |
+
if self._call_load_ipython_extension(mod):
|
80 |
+
self.loaded.add(module_str)
|
81 |
+
else:
|
82 |
+
return "no load function"
|
83 |
+
|
84 |
+
def unload_extension(self, module_str: str):
|
85 |
+
"""Unload an IPython extension by its module name.
|
86 |
+
|
87 |
+
This function looks up the extension's name in ``sys.modules`` and
|
88 |
+
simply calls ``mod.unload_ipython_extension(self)``.
|
89 |
+
|
90 |
+
Returns the string "no unload function" if the extension doesn't define
|
91 |
+
a function to unload itself, "not loaded" if the extension isn't loaded,
|
92 |
+
otherwise None.
|
93 |
+
"""
|
94 |
+
if BUILTINS_EXTS.get(module_str, False) is True:
|
95 |
+
module_str = "IPython.extensions." + module_str
|
96 |
+
if module_str not in self.loaded:
|
97 |
+
return "not loaded"
|
98 |
+
|
99 |
+
if module_str in sys.modules:
|
100 |
+
mod = sys.modules[module_str]
|
101 |
+
if self._call_unload_ipython_extension(mod):
|
102 |
+
self.loaded.discard(module_str)
|
103 |
+
else:
|
104 |
+
return "no unload function"
|
105 |
+
|
106 |
+
def reload_extension(self, module_str: str):
|
107 |
+
"""Reload an IPython extension by calling reload.
|
108 |
+
|
109 |
+
If the module has not been loaded before,
|
110 |
+
:meth:`InteractiveShell.load_extension` is called. Otherwise
|
111 |
+
:func:`reload` is called and then the :func:`load_ipython_extension`
|
112 |
+
function of the module, if it exists is called.
|
113 |
+
"""
|
114 |
+
|
115 |
+
if BUILTINS_EXTS.get(module_str, False) is True:
|
116 |
+
module_str = "IPython.extensions." + module_str
|
117 |
+
|
118 |
+
if (module_str in self.loaded) and (module_str in sys.modules):
|
119 |
+
self.unload_extension(module_str)
|
120 |
+
mod = sys.modules[module_str]
|
121 |
+
reload(mod)
|
122 |
+
if self._call_load_ipython_extension(mod):
|
123 |
+
self.loaded.add(module_str)
|
124 |
+
else:
|
125 |
+
self.load_extension(module_str)
|
126 |
+
|
127 |
+
def _call_load_ipython_extension(self, mod):
|
128 |
+
if hasattr(mod, 'load_ipython_extension'):
|
129 |
+
mod.load_ipython_extension(self.shell)
|
130 |
+
return True
|
131 |
+
|
132 |
+
def _call_unload_ipython_extension(self, mod):
|
133 |
+
if hasattr(mod, 'unload_ipython_extension'):
|
134 |
+
mod.unload_ipython_extension(self.shell)
|
135 |
+
return True
|
openalex_env_map/lib/python3.10/site-packages/IPython/core/formatters.py
ADDED
@@ -0,0 +1,1090 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# -*- coding: utf-8 -*-
|
2 |
+
"""Display formatters.
|
3 |
+
|
4 |
+
This module defines the base instances in order to implement custom
|
5 |
+
formatters/mimetypes
|
6 |
+
got objects:
|
7 |
+
|
8 |
+
As we want to see internal IPython working we are going to use the following
|
9 |
+
function to diaply objects instead of the normal print or display method:
|
10 |
+
|
11 |
+
>>> ip = get_ipython()
|
12 |
+
>>> ip.display_formatter.format(...)
|
13 |
+
({'text/plain': 'Ellipsis'}, {})
|
14 |
+
|
15 |
+
This return a tuple with the mimebumdle for the current object, and the
|
16 |
+
associated metadata.
|
17 |
+
|
18 |
+
|
19 |
+
We can now define our own formatter and register it:
|
20 |
+
|
21 |
+
|
22 |
+
>>> from IPython.core.formatters import BaseFormatter, FormatterABC
|
23 |
+
|
24 |
+
|
25 |
+
>>> class LLMFormatter(BaseFormatter):
|
26 |
+
...
|
27 |
+
... format_type = 'x-vendor/llm'
|
28 |
+
... print_method = '_repr_llm_'
|
29 |
+
... _return_type = (dict, str)
|
30 |
+
|
31 |
+
>>> llm_formatter = LLMFormatter(parent=ip.display_formatter)
|
32 |
+
|
33 |
+
>>> ip.display_formatter.formatters[LLMFormatter.format_type] = llm_formatter
|
34 |
+
|
35 |
+
Now any class that define `_repr_llm_` will return a x-vendor/llm as part of
|
36 |
+
it's display data:
|
37 |
+
|
38 |
+
>>> class A:
|
39 |
+
...
|
40 |
+
... def _repr_llm_(self, *kwargs):
|
41 |
+
... return 'This a A'
|
42 |
+
...
|
43 |
+
|
44 |
+
>>> ip.display_formatter.format(A())
|
45 |
+
({'text/plain': '<IPython.core.formatters.A at ...>', 'x-vendor/llm': 'This a A'}, {})
|
46 |
+
|
47 |
+
As usual, you can register methods for third party types (see
|
48 |
+
:ref:`third_party_formatting`)
|
49 |
+
|
50 |
+
>>> def llm_int(obj):
|
51 |
+
... return 'This is the integer %s, in between %s and %s'%(obj, obj-1, obj+1)
|
52 |
+
|
53 |
+
>>> llm_formatter.for_type(int, llm_int)
|
54 |
+
|
55 |
+
>>> ip.display_formatter.format(42)
|
56 |
+
({'text/plain': '42', 'x-vendor/llm': 'This is the integer 42, in between 41 and 43'}, {})
|
57 |
+
|
58 |
+
|
59 |
+
Inheritance diagram:
|
60 |
+
|
61 |
+
.. inheritance-diagram:: IPython.core.formatters
|
62 |
+
:parts: 3
|
63 |
+
"""
|
64 |
+
|
65 |
+
# Copyright (c) IPython Development Team.
|
66 |
+
# Distributed under the terms of the Modified BSD License.
|
67 |
+
|
68 |
+
import abc
|
69 |
+
import sys
|
70 |
+
import traceback
|
71 |
+
import warnings
|
72 |
+
from io import StringIO
|
73 |
+
|
74 |
+
from decorator import decorator
|
75 |
+
|
76 |
+
from traitlets.config.configurable import Configurable
|
77 |
+
from .getipython import get_ipython
|
78 |
+
from ..utils.sentinel import Sentinel
|
79 |
+
from ..utils.dir2 import get_real_method
|
80 |
+
from ..lib import pretty
|
81 |
+
from traitlets import (
|
82 |
+
Bool, Dict, Integer, Unicode, CUnicode, ObjectName, List,
|
83 |
+
ForwardDeclaredInstance,
|
84 |
+
default, observe,
|
85 |
+
)
|
86 |
+
|
87 |
+
from typing import Any
|
88 |
+
|
89 |
+
|
90 |
+
class DisplayFormatter(Configurable):
|
91 |
+
|
92 |
+
active_types = List(Unicode(),
|
93 |
+
help="""List of currently active mime-types to display.
|
94 |
+
You can use this to set a white-list for formats to display.
|
95 |
+
|
96 |
+
Most users will not need to change this value.
|
97 |
+
""",
|
98 |
+
).tag(config=True)
|
99 |
+
|
100 |
+
@default('active_types')
|
101 |
+
def _active_types_default(self):
|
102 |
+
return self.format_types
|
103 |
+
|
104 |
+
@observe('active_types')
|
105 |
+
def _active_types_changed(self, change):
|
106 |
+
for key, formatter in self.formatters.items():
|
107 |
+
if key in change['new']:
|
108 |
+
formatter.enabled = True
|
109 |
+
else:
|
110 |
+
formatter.enabled = False
|
111 |
+
|
112 |
+
ipython_display_formatter = ForwardDeclaredInstance("FormatterABC") # type: ignore
|
113 |
+
|
114 |
+
@default("ipython_display_formatter")
|
115 |
+
def _default_formatter(self):
|
116 |
+
return IPythonDisplayFormatter(parent=self)
|
117 |
+
|
118 |
+
mimebundle_formatter = ForwardDeclaredInstance("FormatterABC") # type: ignore
|
119 |
+
|
120 |
+
@default("mimebundle_formatter")
|
121 |
+
def _default_mime_formatter(self):
|
122 |
+
return MimeBundleFormatter(parent=self)
|
123 |
+
|
124 |
+
# A dict of formatter whose keys are format types (MIME types) and whose
|
125 |
+
# values are subclasses of BaseFormatter.
|
126 |
+
formatters = Dict()
|
127 |
+
|
128 |
+
@default("formatters")
|
129 |
+
def _formatters_default(self):
|
130 |
+
"""Activate the default formatters."""
|
131 |
+
formatter_classes = [
|
132 |
+
PlainTextFormatter,
|
133 |
+
HTMLFormatter,
|
134 |
+
MarkdownFormatter,
|
135 |
+
SVGFormatter,
|
136 |
+
PNGFormatter,
|
137 |
+
PDFFormatter,
|
138 |
+
JPEGFormatter,
|
139 |
+
LatexFormatter,
|
140 |
+
JSONFormatter,
|
141 |
+
JavascriptFormatter
|
142 |
+
]
|
143 |
+
d = {}
|
144 |
+
for cls in formatter_classes:
|
145 |
+
f = cls(parent=self)
|
146 |
+
d[f.format_type] = f
|
147 |
+
return d
|
148 |
+
|
149 |
+
def format(self, obj, include=None, exclude=None):
|
150 |
+
"""Return a format data dict for an object.
|
151 |
+
|
152 |
+
By default all format types will be computed.
|
153 |
+
|
154 |
+
The following MIME types are usually implemented:
|
155 |
+
|
156 |
+
* text/plain
|
157 |
+
* text/html
|
158 |
+
* text/markdown
|
159 |
+
* text/latex
|
160 |
+
* application/json
|
161 |
+
* application/javascript
|
162 |
+
* application/pdf
|
163 |
+
* image/png
|
164 |
+
* image/jpeg
|
165 |
+
* image/svg+xml
|
166 |
+
|
167 |
+
Parameters
|
168 |
+
----------
|
169 |
+
obj : object
|
170 |
+
The Python object whose format data will be computed.
|
171 |
+
include : list, tuple or set; optional
|
172 |
+
A list of format type strings (MIME types) to include in the
|
173 |
+
format data dict. If this is set *only* the format types included
|
174 |
+
in this list will be computed.
|
175 |
+
exclude : list, tuple or set; optional
|
176 |
+
A list of format type string (MIME types) to exclude in the format
|
177 |
+
data dict. If this is set all format types will be computed,
|
178 |
+
except for those included in this argument.
|
179 |
+
Mimetypes present in exclude will take precedence over the ones in include
|
180 |
+
|
181 |
+
Returns
|
182 |
+
-------
|
183 |
+
(format_dict, metadata_dict) : tuple of two dicts
|
184 |
+
format_dict is a dictionary of key/value pairs, one of each format that was
|
185 |
+
generated for the object. The keys are the format types, which
|
186 |
+
will usually be MIME type strings and the values and JSON'able
|
187 |
+
data structure containing the raw data for the representation in
|
188 |
+
that format.
|
189 |
+
|
190 |
+
metadata_dict is a dictionary of metadata about each mime-type output.
|
191 |
+
Its keys will be a strict subset of the keys in format_dict.
|
192 |
+
|
193 |
+
Notes
|
194 |
+
-----
|
195 |
+
If an object implement `_repr_mimebundle_` as well as various
|
196 |
+
`_repr_*_`, the data returned by `_repr_mimebundle_` will take
|
197 |
+
precedence and the corresponding `_repr_*_` for this mimetype will
|
198 |
+
not be called.
|
199 |
+
|
200 |
+
"""
|
201 |
+
format_dict = {}
|
202 |
+
md_dict = {}
|
203 |
+
|
204 |
+
if self.ipython_display_formatter(obj):
|
205 |
+
# object handled itself, don't proceed
|
206 |
+
return {}, {}
|
207 |
+
|
208 |
+
format_dict, md_dict = self.mimebundle_formatter(obj, include=include, exclude=exclude)
|
209 |
+
|
210 |
+
if format_dict or md_dict:
|
211 |
+
if include:
|
212 |
+
format_dict = {k:v for k,v in format_dict.items() if k in include}
|
213 |
+
md_dict = {k:v for k,v in md_dict.items() if k in include}
|
214 |
+
if exclude:
|
215 |
+
format_dict = {k:v for k,v in format_dict.items() if k not in exclude}
|
216 |
+
md_dict = {k:v for k,v in md_dict.items() if k not in exclude}
|
217 |
+
|
218 |
+
for format_type, formatter in self.formatters.items():
|
219 |
+
if format_type in format_dict:
|
220 |
+
# already got it from mimebundle, maybe don't render again.
|
221 |
+
# exception: manually registered per-mime renderer
|
222 |
+
# check priority:
|
223 |
+
# 1. user-registered per-mime formatter
|
224 |
+
# 2. mime-bundle (user-registered or repr method)
|
225 |
+
# 3. default per-mime formatter (e.g. repr method)
|
226 |
+
try:
|
227 |
+
formatter.lookup(obj)
|
228 |
+
except KeyError:
|
229 |
+
# no special formatter, use mime-bundle-provided value
|
230 |
+
continue
|
231 |
+
if include and format_type not in include:
|
232 |
+
continue
|
233 |
+
if exclude and format_type in exclude:
|
234 |
+
continue
|
235 |
+
|
236 |
+
md = None
|
237 |
+
try:
|
238 |
+
data = formatter(obj)
|
239 |
+
except:
|
240 |
+
# FIXME: log the exception
|
241 |
+
raise
|
242 |
+
|
243 |
+
# formatters can return raw data or (data, metadata)
|
244 |
+
if isinstance(data, tuple) and len(data) == 2:
|
245 |
+
data, md = data
|
246 |
+
|
247 |
+
if data is not None:
|
248 |
+
format_dict[format_type] = data
|
249 |
+
if md is not None:
|
250 |
+
md_dict[format_type] = md
|
251 |
+
return format_dict, md_dict
|
252 |
+
|
253 |
+
@property
|
254 |
+
def format_types(self):
|
255 |
+
"""Return the format types (MIME types) of the active formatters."""
|
256 |
+
return list(self.formatters.keys())
|
257 |
+
|
258 |
+
|
259 |
+
#-----------------------------------------------------------------------------
|
260 |
+
# Formatters for specific format types (text, html, svg, etc.)
|
261 |
+
#-----------------------------------------------------------------------------
|
262 |
+
|
263 |
+
|
264 |
+
def _safe_repr(obj):
|
265 |
+
"""Try to return a repr of an object
|
266 |
+
|
267 |
+
always returns a string, at least.
|
268 |
+
"""
|
269 |
+
try:
|
270 |
+
return repr(obj)
|
271 |
+
except Exception as e:
|
272 |
+
return "un-repr-able object (%r)" % e
|
273 |
+
|
274 |
+
|
275 |
+
class FormatterWarning(UserWarning):
|
276 |
+
"""Warning class for errors in formatters"""
|
277 |
+
|
278 |
+
@decorator
|
279 |
+
def catch_format_error(method, self, *args, **kwargs):
|
280 |
+
"""show traceback on failed format call"""
|
281 |
+
try:
|
282 |
+
r = method(self, *args, **kwargs)
|
283 |
+
except NotImplementedError:
|
284 |
+
# don't warn on NotImplementedErrors
|
285 |
+
return self._check_return(None, args[0])
|
286 |
+
except Exception:
|
287 |
+
exc_info = sys.exc_info()
|
288 |
+
ip = get_ipython()
|
289 |
+
if ip is not None:
|
290 |
+
ip.showtraceback(exc_info)
|
291 |
+
else:
|
292 |
+
traceback.print_exception(*exc_info)
|
293 |
+
return self._check_return(None, args[0])
|
294 |
+
return self._check_return(r, args[0])
|
295 |
+
|
296 |
+
|
297 |
+
class FormatterABC(metaclass=abc.ABCMeta):
|
298 |
+
""" Abstract base class for Formatters.
|
299 |
+
|
300 |
+
A formatter is a callable class that is responsible for computing the
|
301 |
+
raw format data for a particular format type (MIME type). For example,
|
302 |
+
an HTML formatter would have a format type of `text/html` and would return
|
303 |
+
the HTML representation of the object when called.
|
304 |
+
"""
|
305 |
+
|
306 |
+
# The format type of the data returned, usually a MIME type.
|
307 |
+
format_type = 'text/plain'
|
308 |
+
|
309 |
+
# Is the formatter enabled...
|
310 |
+
enabled = True
|
311 |
+
|
312 |
+
@abc.abstractmethod
|
313 |
+
def __call__(self, obj):
|
314 |
+
"""Return a JSON'able representation of the object.
|
315 |
+
|
316 |
+
If the object cannot be formatted by this formatter,
|
317 |
+
warn and return None.
|
318 |
+
"""
|
319 |
+
return repr(obj)
|
320 |
+
|
321 |
+
|
322 |
+
def _mod_name_key(typ):
|
323 |
+
"""Return a (__module__, __name__) tuple for a type.
|
324 |
+
|
325 |
+
Used as key in Formatter.deferred_printers.
|
326 |
+
"""
|
327 |
+
module = getattr(typ, '__module__', None)
|
328 |
+
name = getattr(typ, '__name__', None)
|
329 |
+
return (module, name)
|
330 |
+
|
331 |
+
|
332 |
+
def _get_type(obj):
|
333 |
+
"""Return the type of an instance (old and new-style)"""
|
334 |
+
return getattr(obj, '__class__', None) or type(obj)
|
335 |
+
|
336 |
+
|
337 |
+
_raise_key_error = Sentinel(
|
338 |
+
"_raise_key_error",
|
339 |
+
__name__,
|
340 |
+
"""
|
341 |
+
Special value to raise a KeyError
|
342 |
+
|
343 |
+
Raise KeyError in `BaseFormatter.pop` if passed as the default value to `pop`
|
344 |
+
""",
|
345 |
+
)
|
346 |
+
|
347 |
+
|
348 |
+
class BaseFormatter(Configurable):
|
349 |
+
"""A base formatter class that is configurable.
|
350 |
+
|
351 |
+
This formatter should usually be used as the base class of all formatters.
|
352 |
+
It is a traited :class:`Configurable` class and includes an extensible
|
353 |
+
API for users to determine how their objects are formatted. The following
|
354 |
+
logic is used to find a function to format an given object.
|
355 |
+
|
356 |
+
1. The object is introspected to see if it has a method with the name
|
357 |
+
:attr:`print_method`. If is does, that object is passed to that method
|
358 |
+
for formatting.
|
359 |
+
2. If no print method is found, three internal dictionaries are consulted
|
360 |
+
to find print method: :attr:`singleton_printers`, :attr:`type_printers`
|
361 |
+
and :attr:`deferred_printers`.
|
362 |
+
|
363 |
+
Users should use these dictionaries to register functions that will be
|
364 |
+
used to compute the format data for their objects (if those objects don't
|
365 |
+
have the special print methods). The easiest way of using these
|
366 |
+
dictionaries is through the :meth:`for_type` and :meth:`for_type_by_name`
|
367 |
+
methods.
|
368 |
+
|
369 |
+
If no function/callable is found to compute the format data, ``None`` is
|
370 |
+
returned and this format type is not used.
|
371 |
+
"""
|
372 |
+
|
373 |
+
format_type = Unicode("text/plain")
|
374 |
+
_return_type: Any = str
|
375 |
+
|
376 |
+
enabled = Bool(True).tag(config=True)
|
377 |
+
|
378 |
+
print_method = ObjectName('__repr__')
|
379 |
+
|
380 |
+
# The singleton printers.
|
381 |
+
# Maps the IDs of the builtin singleton objects to the format functions.
|
382 |
+
singleton_printers = Dict().tag(config=True)
|
383 |
+
|
384 |
+
# The type-specific printers.
|
385 |
+
# Map type objects to the format functions.
|
386 |
+
type_printers = Dict().tag(config=True)
|
387 |
+
|
388 |
+
# The deferred-import type-specific printers.
|
389 |
+
# Map (modulename, classname) pairs to the format functions.
|
390 |
+
deferred_printers = Dict().tag(config=True)
|
391 |
+
|
392 |
+
@catch_format_error
|
393 |
+
def __call__(self, obj):
|
394 |
+
"""Compute the format for an object."""
|
395 |
+
if self.enabled:
|
396 |
+
# lookup registered printer
|
397 |
+
try:
|
398 |
+
printer = self.lookup(obj)
|
399 |
+
except KeyError:
|
400 |
+
pass
|
401 |
+
else:
|
402 |
+
return printer(obj)
|
403 |
+
# Finally look for special method names
|
404 |
+
method = get_real_method(obj, self.print_method)
|
405 |
+
if method is not None:
|
406 |
+
return method()
|
407 |
+
return None
|
408 |
+
else:
|
409 |
+
return None
|
410 |
+
|
411 |
+
def __contains__(self, typ):
|
412 |
+
"""map in to lookup_by_type"""
|
413 |
+
try:
|
414 |
+
self.lookup_by_type(typ)
|
415 |
+
except KeyError:
|
416 |
+
return False
|
417 |
+
else:
|
418 |
+
return True
|
419 |
+
|
420 |
+
def _check_return(self, r, obj):
|
421 |
+
"""Check that a return value is appropriate
|
422 |
+
|
423 |
+
Return the value if so, None otherwise, warning if invalid.
|
424 |
+
"""
|
425 |
+
if r is None or isinstance(r, self._return_type) or \
|
426 |
+
(isinstance(r, tuple) and r and isinstance(r[0], self._return_type)):
|
427 |
+
return r
|
428 |
+
else:
|
429 |
+
warnings.warn(
|
430 |
+
"%s formatter returned invalid type %s (expected %s) for object: %s" % \
|
431 |
+
(self.format_type, type(r), self._return_type, _safe_repr(obj)),
|
432 |
+
FormatterWarning
|
433 |
+
)
|
434 |
+
|
435 |
+
def lookup(self, obj):
|
436 |
+
"""Look up the formatter for a given instance.
|
437 |
+
|
438 |
+
Parameters
|
439 |
+
----------
|
440 |
+
obj : object instance
|
441 |
+
|
442 |
+
Returns
|
443 |
+
-------
|
444 |
+
f : callable
|
445 |
+
The registered formatting callable for the type.
|
446 |
+
|
447 |
+
Raises
|
448 |
+
------
|
449 |
+
KeyError if the type has not been registered.
|
450 |
+
"""
|
451 |
+
# look for singleton first
|
452 |
+
obj_id = id(obj)
|
453 |
+
if obj_id in self.singleton_printers:
|
454 |
+
return self.singleton_printers[obj_id]
|
455 |
+
# then lookup by type
|
456 |
+
return self.lookup_by_type(_get_type(obj))
|
457 |
+
|
458 |
+
def lookup_by_type(self, typ):
|
459 |
+
"""Look up the registered formatter for a type.
|
460 |
+
|
461 |
+
Parameters
|
462 |
+
----------
|
463 |
+
typ : type or '__module__.__name__' string for a type
|
464 |
+
|
465 |
+
Returns
|
466 |
+
-------
|
467 |
+
f : callable
|
468 |
+
The registered formatting callable for the type.
|
469 |
+
|
470 |
+
Raises
|
471 |
+
------
|
472 |
+
KeyError if the type has not been registered.
|
473 |
+
"""
|
474 |
+
if isinstance(typ, str):
|
475 |
+
typ_key = tuple(typ.rsplit('.',1))
|
476 |
+
if typ_key not in self.deferred_printers:
|
477 |
+
# We may have it cached in the type map. We will have to
|
478 |
+
# iterate over all of the types to check.
|
479 |
+
for cls in self.type_printers:
|
480 |
+
if _mod_name_key(cls) == typ_key:
|
481 |
+
return self.type_printers[cls]
|
482 |
+
else:
|
483 |
+
return self.deferred_printers[typ_key]
|
484 |
+
else:
|
485 |
+
for cls in pretty._get_mro(typ):
|
486 |
+
if cls in self.type_printers or self._in_deferred_types(cls):
|
487 |
+
return self.type_printers[cls]
|
488 |
+
|
489 |
+
# If we have reached here, the lookup failed.
|
490 |
+
raise KeyError("No registered printer for {0!r}".format(typ))
|
491 |
+
|
492 |
+
def for_type(self, typ, func=None):
|
493 |
+
"""Add a format function for a given type.
|
494 |
+
|
495 |
+
Parameters
|
496 |
+
----------
|
497 |
+
typ : type or '__module__.__name__' string for a type
|
498 |
+
The class of the object that will be formatted using `func`.
|
499 |
+
|
500 |
+
func : callable
|
501 |
+
A callable for computing the format data.
|
502 |
+
`func` will be called with the object to be formatted,
|
503 |
+
and will return the raw data in this formatter's format.
|
504 |
+
Subclasses may use a different call signature for the
|
505 |
+
`func` argument.
|
506 |
+
|
507 |
+
If `func` is None or not specified, there will be no change,
|
508 |
+
only returning the current value.
|
509 |
+
|
510 |
+
Returns
|
511 |
+
-------
|
512 |
+
oldfunc : callable
|
513 |
+
The currently registered callable.
|
514 |
+
If you are registering a new formatter,
|
515 |
+
this will be the previous value (to enable restoring later).
|
516 |
+
"""
|
517 |
+
# if string given, interpret as 'pkg.module.class_name'
|
518 |
+
if isinstance(typ, str):
|
519 |
+
type_module, type_name = typ.rsplit('.', 1)
|
520 |
+
return self.for_type_by_name(type_module, type_name, func)
|
521 |
+
|
522 |
+
try:
|
523 |
+
oldfunc = self.lookup_by_type(typ)
|
524 |
+
except KeyError:
|
525 |
+
oldfunc = None
|
526 |
+
|
527 |
+
if func is not None:
|
528 |
+
self.type_printers[typ] = func
|
529 |
+
|
530 |
+
return oldfunc
|
531 |
+
|
532 |
+
def for_type_by_name(self, type_module, type_name, func=None):
|
533 |
+
"""Add a format function for a type specified by the full dotted
|
534 |
+
module and name of the type, rather than the type of the object.
|
535 |
+
|
536 |
+
Parameters
|
537 |
+
----------
|
538 |
+
type_module : str
|
539 |
+
The full dotted name of the module the type is defined in, like
|
540 |
+
``numpy``.
|
541 |
+
|
542 |
+
type_name : str
|
543 |
+
The name of the type (the class name), like ``dtype``
|
544 |
+
|
545 |
+
func : callable
|
546 |
+
A callable for computing the format data.
|
547 |
+
`func` will be called with the object to be formatted,
|
548 |
+
and will return the raw data in this formatter's format.
|
549 |
+
Subclasses may use a different call signature for the
|
550 |
+
`func` argument.
|
551 |
+
|
552 |
+
If `func` is None or unspecified, there will be no change,
|
553 |
+
only returning the current value.
|
554 |
+
|
555 |
+
Returns
|
556 |
+
-------
|
557 |
+
oldfunc : callable
|
558 |
+
The currently registered callable.
|
559 |
+
If you are registering a new formatter,
|
560 |
+
this will be the previous value (to enable restoring later).
|
561 |
+
"""
|
562 |
+
key = (type_module, type_name)
|
563 |
+
|
564 |
+
try:
|
565 |
+
oldfunc = self.lookup_by_type("%s.%s" % key)
|
566 |
+
except KeyError:
|
567 |
+
oldfunc = None
|
568 |
+
|
569 |
+
if func is not None:
|
570 |
+
self.deferred_printers[key] = func
|
571 |
+
return oldfunc
|
572 |
+
|
573 |
+
def pop(self, typ, default=_raise_key_error):
|
574 |
+
"""Pop a formatter for the given type.
|
575 |
+
|
576 |
+
Parameters
|
577 |
+
----------
|
578 |
+
typ : type or '__module__.__name__' string for a type
|
579 |
+
default : object
|
580 |
+
value to be returned if no formatter is registered for typ.
|
581 |
+
|
582 |
+
Returns
|
583 |
+
-------
|
584 |
+
obj : object
|
585 |
+
The last registered object for the type.
|
586 |
+
|
587 |
+
Raises
|
588 |
+
------
|
589 |
+
KeyError if the type is not registered and default is not specified.
|
590 |
+
"""
|
591 |
+
|
592 |
+
if isinstance(typ, str):
|
593 |
+
typ_key = tuple(typ.rsplit('.',1))
|
594 |
+
if typ_key not in self.deferred_printers:
|
595 |
+
# We may have it cached in the type map. We will have to
|
596 |
+
# iterate over all of the types to check.
|
597 |
+
for cls in self.type_printers:
|
598 |
+
if _mod_name_key(cls) == typ_key:
|
599 |
+
old = self.type_printers.pop(cls)
|
600 |
+
break
|
601 |
+
else:
|
602 |
+
old = default
|
603 |
+
else:
|
604 |
+
old = self.deferred_printers.pop(typ_key)
|
605 |
+
else:
|
606 |
+
if typ in self.type_printers:
|
607 |
+
old = self.type_printers.pop(typ)
|
608 |
+
else:
|
609 |
+
old = self.deferred_printers.pop(_mod_name_key(typ), default)
|
610 |
+
if old is _raise_key_error:
|
611 |
+
raise KeyError("No registered value for {0!r}".format(typ))
|
612 |
+
return old
|
613 |
+
|
614 |
+
def _in_deferred_types(self, cls):
|
615 |
+
"""
|
616 |
+
Check if the given class is specified in the deferred type registry.
|
617 |
+
|
618 |
+
Successful matches will be moved to the regular type registry for future use.
|
619 |
+
"""
|
620 |
+
mod = getattr(cls, '__module__', None)
|
621 |
+
name = getattr(cls, '__name__', None)
|
622 |
+
key = (mod, name)
|
623 |
+
if key in self.deferred_printers:
|
624 |
+
# Move the printer over to the regular registry.
|
625 |
+
printer = self.deferred_printers.pop(key)
|
626 |
+
self.type_printers[cls] = printer
|
627 |
+
return True
|
628 |
+
return False
|
629 |
+
|
630 |
+
|
631 |
+
class PlainTextFormatter(BaseFormatter):
|
632 |
+
"""The default pretty-printer.
|
633 |
+
|
634 |
+
This uses :mod:`IPython.lib.pretty` to compute the format data of
|
635 |
+
the object. If the object cannot be pretty printed, :func:`repr` is used.
|
636 |
+
See the documentation of :mod:`IPython.lib.pretty` for details on
|
637 |
+
how to write pretty printers. Here is a simple example::
|
638 |
+
|
639 |
+
def dtype_pprinter(obj, p, cycle):
|
640 |
+
if cycle:
|
641 |
+
return p.text('dtype(...)')
|
642 |
+
if hasattr(obj, 'fields'):
|
643 |
+
if obj.fields is None:
|
644 |
+
p.text(repr(obj))
|
645 |
+
else:
|
646 |
+
p.begin_group(7, 'dtype([')
|
647 |
+
for i, field in enumerate(obj.descr):
|
648 |
+
if i > 0:
|
649 |
+
p.text(',')
|
650 |
+
p.breakable()
|
651 |
+
p.pretty(field)
|
652 |
+
p.end_group(7, '])')
|
653 |
+
"""
|
654 |
+
|
655 |
+
# The format type of data returned.
|
656 |
+
format_type = Unicode('text/plain')
|
657 |
+
|
658 |
+
# This subclass ignores this attribute as it always need to return
|
659 |
+
# something.
|
660 |
+
enabled = Bool(True).tag(config=False)
|
661 |
+
|
662 |
+
max_seq_length = Integer(pretty.MAX_SEQ_LENGTH,
|
663 |
+
help="""Truncate large collections (lists, dicts, tuples, sets) to this size.
|
664 |
+
|
665 |
+
Set to 0 to disable truncation.
|
666 |
+
""",
|
667 |
+
).tag(config=True)
|
668 |
+
|
669 |
+
# Look for a _repr_pretty_ methods to use for pretty printing.
|
670 |
+
print_method = ObjectName('_repr_pretty_')
|
671 |
+
|
672 |
+
# Whether to pretty-print or not.
|
673 |
+
pprint = Bool(True).tag(config=True)
|
674 |
+
|
675 |
+
# Whether to be verbose or not.
|
676 |
+
verbose = Bool(False).tag(config=True)
|
677 |
+
|
678 |
+
# The maximum width.
|
679 |
+
max_width = Integer(79).tag(config=True)
|
680 |
+
|
681 |
+
# The newline character.
|
682 |
+
newline = Unicode('\n').tag(config=True)
|
683 |
+
|
684 |
+
# format-string for pprinting floats
|
685 |
+
float_format = Unicode('%r')
|
686 |
+
# setter for float precision, either int or direct format-string
|
687 |
+
float_precision = CUnicode('').tag(config=True)
|
688 |
+
|
689 |
+
@observe('float_precision')
|
690 |
+
def _float_precision_changed(self, change):
|
691 |
+
"""float_precision changed, set float_format accordingly.
|
692 |
+
|
693 |
+
float_precision can be set by int or str.
|
694 |
+
This will set float_format, after interpreting input.
|
695 |
+
If numpy has been imported, numpy print precision will also be set.
|
696 |
+
|
697 |
+
integer `n` sets format to '%.nf', otherwise, format set directly.
|
698 |
+
|
699 |
+
An empty string returns to defaults (repr for float, 8 for numpy).
|
700 |
+
|
701 |
+
This parameter can be set via the '%precision' magic.
|
702 |
+
"""
|
703 |
+
new = change['new']
|
704 |
+
if '%' in new:
|
705 |
+
# got explicit format string
|
706 |
+
fmt = new
|
707 |
+
try:
|
708 |
+
fmt%3.14159
|
709 |
+
except Exception as e:
|
710 |
+
raise ValueError("Precision must be int or format string, not %r"%new) from e
|
711 |
+
elif new:
|
712 |
+
# otherwise, should be an int
|
713 |
+
try:
|
714 |
+
i = int(new)
|
715 |
+
assert i >= 0
|
716 |
+
except ValueError as e:
|
717 |
+
raise ValueError("Precision must be int or format string, not %r"%new) from e
|
718 |
+
except AssertionError as e:
|
719 |
+
raise ValueError("int precision must be non-negative, not %r"%i) from e
|
720 |
+
|
721 |
+
fmt = '%%.%if'%i
|
722 |
+
if 'numpy' in sys.modules:
|
723 |
+
# set numpy precision if it has been imported
|
724 |
+
import numpy
|
725 |
+
numpy.set_printoptions(precision=i)
|
726 |
+
else:
|
727 |
+
# default back to repr
|
728 |
+
fmt = '%r'
|
729 |
+
if 'numpy' in sys.modules:
|
730 |
+
import numpy
|
731 |
+
# numpy default is 8
|
732 |
+
numpy.set_printoptions(precision=8)
|
733 |
+
self.float_format = fmt
|
734 |
+
|
735 |
+
# Use the default pretty printers from IPython.lib.pretty.
|
736 |
+
@default('singleton_printers')
|
737 |
+
def _singleton_printers_default(self):
|
738 |
+
return pretty._singleton_pprinters.copy()
|
739 |
+
|
740 |
+
@default('type_printers')
|
741 |
+
def _type_printers_default(self):
|
742 |
+
d = pretty._type_pprinters.copy()
|
743 |
+
d[float] = lambda obj,p,cycle: p.text(self.float_format%obj)
|
744 |
+
# if NumPy is used, set precision for its float64 type
|
745 |
+
if "numpy" in sys.modules:
|
746 |
+
import numpy
|
747 |
+
|
748 |
+
d[numpy.float64] = lambda obj, p, cycle: p.text(self.float_format % obj)
|
749 |
+
return d
|
750 |
+
|
751 |
+
@default('deferred_printers')
|
752 |
+
def _deferred_printers_default(self):
|
753 |
+
return pretty._deferred_type_pprinters.copy()
|
754 |
+
|
755 |
+
#### FormatterABC interface ####
|
756 |
+
|
757 |
+
@catch_format_error
|
758 |
+
def __call__(self, obj):
|
759 |
+
"""Compute the pretty representation of the object."""
|
760 |
+
if not self.pprint:
|
761 |
+
return repr(obj)
|
762 |
+
else:
|
763 |
+
stream = StringIO()
|
764 |
+
printer = pretty.RepresentationPrinter(stream, self.verbose,
|
765 |
+
self.max_width, self.newline,
|
766 |
+
max_seq_length=self.max_seq_length,
|
767 |
+
singleton_pprinters=self.singleton_printers,
|
768 |
+
type_pprinters=self.type_printers,
|
769 |
+
deferred_pprinters=self.deferred_printers)
|
770 |
+
printer.pretty(obj)
|
771 |
+
printer.flush()
|
772 |
+
return stream.getvalue()
|
773 |
+
|
774 |
+
|
775 |
+
class HTMLFormatter(BaseFormatter):
|
776 |
+
"""An HTML formatter.
|
777 |
+
|
778 |
+
To define the callables that compute the HTML representation of your
|
779 |
+
objects, define a :meth:`_repr_html_` method or use the :meth:`for_type`
|
780 |
+
or :meth:`for_type_by_name` methods to register functions that handle
|
781 |
+
this.
|
782 |
+
|
783 |
+
The return value of this formatter should be a valid HTML snippet that
|
784 |
+
could be injected into an existing DOM. It should *not* include the
|
785 |
+
```<html>`` or ```<body>`` tags.
|
786 |
+
"""
|
787 |
+
format_type = Unicode('text/html')
|
788 |
+
|
789 |
+
print_method = ObjectName('_repr_html_')
|
790 |
+
|
791 |
+
|
792 |
+
class MarkdownFormatter(BaseFormatter):
|
793 |
+
"""A Markdown formatter.
|
794 |
+
|
795 |
+
To define the callables that compute the Markdown representation of your
|
796 |
+
objects, define a :meth:`_repr_markdown_` method or use the :meth:`for_type`
|
797 |
+
or :meth:`for_type_by_name` methods to register functions that handle
|
798 |
+
this.
|
799 |
+
|
800 |
+
The return value of this formatter should be a valid Markdown.
|
801 |
+
"""
|
802 |
+
format_type = Unicode('text/markdown')
|
803 |
+
|
804 |
+
print_method = ObjectName('_repr_markdown_')
|
805 |
+
|
806 |
+
class SVGFormatter(BaseFormatter):
|
807 |
+
"""An SVG formatter.
|
808 |
+
|
809 |
+
To define the callables that compute the SVG representation of your
|
810 |
+
objects, define a :meth:`_repr_svg_` method or use the :meth:`for_type`
|
811 |
+
or :meth:`for_type_by_name` methods to register functions that handle
|
812 |
+
this.
|
813 |
+
|
814 |
+
The return value of this formatter should be valid SVG enclosed in
|
815 |
+
```<svg>``` tags, that could be injected into an existing DOM. It should
|
816 |
+
*not* include the ```<html>`` or ```<body>`` tags.
|
817 |
+
"""
|
818 |
+
format_type = Unicode('image/svg+xml')
|
819 |
+
|
820 |
+
print_method = ObjectName('_repr_svg_')
|
821 |
+
|
822 |
+
|
823 |
+
class PNGFormatter(BaseFormatter):
|
824 |
+
"""A PNG formatter.
|
825 |
+
|
826 |
+
To define the callables that compute the PNG representation of your
|
827 |
+
objects, define a :meth:`_repr_png_` method or use the :meth:`for_type`
|
828 |
+
or :meth:`for_type_by_name` methods to register functions that handle
|
829 |
+
this.
|
830 |
+
|
831 |
+
The return value of this formatter should be raw PNG data, *not*
|
832 |
+
base64 encoded.
|
833 |
+
"""
|
834 |
+
format_type = Unicode('image/png')
|
835 |
+
|
836 |
+
print_method = ObjectName('_repr_png_')
|
837 |
+
|
838 |
+
_return_type = (bytes, str)
|
839 |
+
|
840 |
+
|
841 |
+
class JPEGFormatter(BaseFormatter):
|
842 |
+
"""A JPEG formatter.
|
843 |
+
|
844 |
+
To define the callables that compute the JPEG representation of your
|
845 |
+
objects, define a :meth:`_repr_jpeg_` method or use the :meth:`for_type`
|
846 |
+
or :meth:`for_type_by_name` methods to register functions that handle
|
847 |
+
this.
|
848 |
+
|
849 |
+
The return value of this formatter should be raw JPEG data, *not*
|
850 |
+
base64 encoded.
|
851 |
+
"""
|
852 |
+
format_type = Unicode('image/jpeg')
|
853 |
+
|
854 |
+
print_method = ObjectName('_repr_jpeg_')
|
855 |
+
|
856 |
+
_return_type = (bytes, str)
|
857 |
+
|
858 |
+
|
859 |
+
class LatexFormatter(BaseFormatter):
|
860 |
+
"""A LaTeX formatter.
|
861 |
+
|
862 |
+
To define the callables that compute the LaTeX representation of your
|
863 |
+
objects, define a :meth:`_repr_latex_` method or use the :meth:`for_type`
|
864 |
+
or :meth:`for_type_by_name` methods to register functions that handle
|
865 |
+
this.
|
866 |
+
|
867 |
+
The return value of this formatter should be a valid LaTeX equation,
|
868 |
+
enclosed in either ```$```, ```$$``` or another LaTeX equation
|
869 |
+
environment.
|
870 |
+
"""
|
871 |
+
format_type = Unicode('text/latex')
|
872 |
+
|
873 |
+
print_method = ObjectName('_repr_latex_')
|
874 |
+
|
875 |
+
|
876 |
+
class JSONFormatter(BaseFormatter):
|
877 |
+
"""A JSON string formatter.
|
878 |
+
|
879 |
+
To define the callables that compute the JSONable representation of
|
880 |
+
your objects, define a :meth:`_repr_json_` method or use the :meth:`for_type`
|
881 |
+
or :meth:`for_type_by_name` methods to register functions that handle
|
882 |
+
this.
|
883 |
+
|
884 |
+
The return value of this formatter should be a JSONable list or dict.
|
885 |
+
JSON scalars (None, number, string) are not allowed, only dict or list containers.
|
886 |
+
"""
|
887 |
+
format_type = Unicode('application/json')
|
888 |
+
_return_type = (list, dict)
|
889 |
+
|
890 |
+
print_method = ObjectName('_repr_json_')
|
891 |
+
|
892 |
+
def _check_return(self, r, obj):
|
893 |
+
"""Check that a return value is appropriate
|
894 |
+
|
895 |
+
Return the value if so, None otherwise, warning if invalid.
|
896 |
+
"""
|
897 |
+
if r is None:
|
898 |
+
return
|
899 |
+
md = None
|
900 |
+
if isinstance(r, tuple):
|
901 |
+
# unpack data, metadata tuple for type checking on first element
|
902 |
+
r, md = r
|
903 |
+
|
904 |
+
assert not isinstance(
|
905 |
+
r, str
|
906 |
+
), "JSON-as-string has been deprecated since IPython < 3"
|
907 |
+
|
908 |
+
if md is not None:
|
909 |
+
# put the tuple back together
|
910 |
+
r = (r, md)
|
911 |
+
return super(JSONFormatter, self)._check_return(r, obj)
|
912 |
+
|
913 |
+
|
914 |
+
class JavascriptFormatter(BaseFormatter):
|
915 |
+
"""A Javascript formatter.
|
916 |
+
|
917 |
+
To define the callables that compute the Javascript representation of
|
918 |
+
your objects, define a :meth:`_repr_javascript_` method or use the
|
919 |
+
:meth:`for_type` or :meth:`for_type_by_name` methods to register functions
|
920 |
+
that handle this.
|
921 |
+
|
922 |
+
The return value of this formatter should be valid Javascript code and
|
923 |
+
should *not* be enclosed in ```<script>``` tags.
|
924 |
+
"""
|
925 |
+
format_type = Unicode('application/javascript')
|
926 |
+
|
927 |
+
print_method = ObjectName('_repr_javascript_')
|
928 |
+
|
929 |
+
|
930 |
+
class PDFFormatter(BaseFormatter):
|
931 |
+
"""A PDF formatter.
|
932 |
+
|
933 |
+
To define the callables that compute the PDF representation of your
|
934 |
+
objects, define a :meth:`_repr_pdf_` method or use the :meth:`for_type`
|
935 |
+
or :meth:`for_type_by_name` methods to register functions that handle
|
936 |
+
this.
|
937 |
+
|
938 |
+
The return value of this formatter should be raw PDF data, *not*
|
939 |
+
base64 encoded.
|
940 |
+
"""
|
941 |
+
format_type = Unicode('application/pdf')
|
942 |
+
|
943 |
+
print_method = ObjectName('_repr_pdf_')
|
944 |
+
|
945 |
+
_return_type = (bytes, str)
|
946 |
+
|
947 |
+
class IPythonDisplayFormatter(BaseFormatter):
|
948 |
+
"""An escape-hatch Formatter for objects that know how to display themselves.
|
949 |
+
|
950 |
+
To define the callables that compute the representation of your
|
951 |
+
objects, define a :meth:`_ipython_display_` method or use the :meth:`for_type`
|
952 |
+
or :meth:`for_type_by_name` methods to register functions that handle
|
953 |
+
this. Unlike mime-type displays, this method should not return anything,
|
954 |
+
instead calling any appropriate display methods itself.
|
955 |
+
|
956 |
+
This display formatter has highest priority.
|
957 |
+
If it fires, no other display formatter will be called.
|
958 |
+
|
959 |
+
Prior to IPython 6.1, `_ipython_display_` was the only way to display custom mime-types
|
960 |
+
without registering a new Formatter.
|
961 |
+
|
962 |
+
IPython 6.1 introduces `_repr_mimebundle_` for displaying custom mime-types,
|
963 |
+
so `_ipython_display_` should only be used for objects that require unusual
|
964 |
+
display patterns, such as multiple display calls.
|
965 |
+
"""
|
966 |
+
print_method = ObjectName('_ipython_display_')
|
967 |
+
_return_type = (type(None), bool)
|
968 |
+
|
969 |
+
@catch_format_error
|
970 |
+
def __call__(self, obj):
|
971 |
+
"""Compute the format for an object."""
|
972 |
+
if self.enabled:
|
973 |
+
# lookup registered printer
|
974 |
+
try:
|
975 |
+
printer = self.lookup(obj)
|
976 |
+
except KeyError:
|
977 |
+
pass
|
978 |
+
else:
|
979 |
+
printer(obj)
|
980 |
+
return True
|
981 |
+
# Finally look for special method names
|
982 |
+
method = get_real_method(obj, self.print_method)
|
983 |
+
if method is not None:
|
984 |
+
method()
|
985 |
+
return True
|
986 |
+
|
987 |
+
|
988 |
+
class MimeBundleFormatter(BaseFormatter):
|
989 |
+
"""A Formatter for arbitrary mime-types.
|
990 |
+
|
991 |
+
Unlike other `_repr_<mimetype>_` methods,
|
992 |
+
`_repr_mimebundle_` should return mime-bundle data,
|
993 |
+
either the mime-keyed `data` dictionary or the tuple `(data, metadata)`.
|
994 |
+
Any mime-type is valid.
|
995 |
+
|
996 |
+
To define the callables that compute the mime-bundle representation of your
|
997 |
+
objects, define a :meth:`_repr_mimebundle_` method or use the :meth:`for_type`
|
998 |
+
or :meth:`for_type_by_name` methods to register functions that handle
|
999 |
+
this.
|
1000 |
+
|
1001 |
+
.. versionadded:: 6.1
|
1002 |
+
"""
|
1003 |
+
print_method = ObjectName('_repr_mimebundle_')
|
1004 |
+
_return_type = dict
|
1005 |
+
|
1006 |
+
def _check_return(self, r, obj):
|
1007 |
+
r = super(MimeBundleFormatter, self)._check_return(r, obj)
|
1008 |
+
# always return (data, metadata):
|
1009 |
+
if r is None:
|
1010 |
+
return {}, {}
|
1011 |
+
if not isinstance(r, tuple):
|
1012 |
+
return r, {}
|
1013 |
+
return r
|
1014 |
+
|
1015 |
+
@catch_format_error
|
1016 |
+
def __call__(self, obj, include=None, exclude=None):
|
1017 |
+
"""Compute the format for an object.
|
1018 |
+
|
1019 |
+
Identical to parent's method but we pass extra parameters to the method.
|
1020 |
+
|
1021 |
+
Unlike other _repr_*_ `_repr_mimebundle_` should allow extra kwargs, in
|
1022 |
+
particular `include` and `exclude`.
|
1023 |
+
"""
|
1024 |
+
if self.enabled:
|
1025 |
+
# lookup registered printer
|
1026 |
+
try:
|
1027 |
+
printer = self.lookup(obj)
|
1028 |
+
except KeyError:
|
1029 |
+
pass
|
1030 |
+
else:
|
1031 |
+
return printer(obj)
|
1032 |
+
# Finally look for special method names
|
1033 |
+
method = get_real_method(obj, self.print_method)
|
1034 |
+
|
1035 |
+
if method is not None:
|
1036 |
+
return method(include=include, exclude=exclude)
|
1037 |
+
return None
|
1038 |
+
else:
|
1039 |
+
return None
|
1040 |
+
|
1041 |
+
|
1042 |
+
FormatterABC.register(BaseFormatter)
|
1043 |
+
FormatterABC.register(PlainTextFormatter)
|
1044 |
+
FormatterABC.register(HTMLFormatter)
|
1045 |
+
FormatterABC.register(MarkdownFormatter)
|
1046 |
+
FormatterABC.register(SVGFormatter)
|
1047 |
+
FormatterABC.register(PNGFormatter)
|
1048 |
+
FormatterABC.register(PDFFormatter)
|
1049 |
+
FormatterABC.register(JPEGFormatter)
|
1050 |
+
FormatterABC.register(LatexFormatter)
|
1051 |
+
FormatterABC.register(JSONFormatter)
|
1052 |
+
FormatterABC.register(JavascriptFormatter)
|
1053 |
+
FormatterABC.register(IPythonDisplayFormatter)
|
1054 |
+
FormatterABC.register(MimeBundleFormatter)
|
1055 |
+
|
1056 |
+
|
1057 |
+
def format_display_data(obj, include=None, exclude=None):
|
1058 |
+
"""Return a format data dict for an object.
|
1059 |
+
|
1060 |
+
By default all format types will be computed.
|
1061 |
+
|
1062 |
+
Parameters
|
1063 |
+
----------
|
1064 |
+
obj : object
|
1065 |
+
The Python object whose format data will be computed.
|
1066 |
+
|
1067 |
+
Returns
|
1068 |
+
-------
|
1069 |
+
format_dict : dict
|
1070 |
+
A dictionary of key/value pairs, one or each format that was
|
1071 |
+
generated for the object. The keys are the format types, which
|
1072 |
+
will usually be MIME type strings and the values and JSON'able
|
1073 |
+
data structure containing the raw data for the representation in
|
1074 |
+
that format.
|
1075 |
+
include : list or tuple, optional
|
1076 |
+
A list of format type strings (MIME types) to include in the
|
1077 |
+
format data dict. If this is set *only* the format types included
|
1078 |
+
in this list will be computed.
|
1079 |
+
exclude : list or tuple, optional
|
1080 |
+
A list of format type string (MIME types) to exclude in the format
|
1081 |
+
data dict. If this is set all format types will be computed,
|
1082 |
+
except for those included in this argument.
|
1083 |
+
"""
|
1084 |
+
from .interactiveshell import InteractiveShell
|
1085 |
+
|
1086 |
+
return InteractiveShell.instance().display_formatter.format(
|
1087 |
+
obj,
|
1088 |
+
include,
|
1089 |
+
exclude
|
1090 |
+
)
|
openalex_env_map/lib/python3.10/site-packages/IPython/core/getipython.py
ADDED
@@ -0,0 +1,24 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# encoding: utf-8
|
2 |
+
"""Simple function to call to get the current InteractiveShell instance
|
3 |
+
"""
|
4 |
+
|
5 |
+
#-----------------------------------------------------------------------------
|
6 |
+
# Copyright (C) 2013 The IPython Development Team
|
7 |
+
#
|
8 |
+
# Distributed under the terms of the BSD License. The full license is in
|
9 |
+
# the file COPYING, distributed as part of this software.
|
10 |
+
#-----------------------------------------------------------------------------
|
11 |
+
|
12 |
+
#-----------------------------------------------------------------------------
|
13 |
+
# Classes and functions
|
14 |
+
#-----------------------------------------------------------------------------
|
15 |
+
|
16 |
+
|
17 |
+
def get_ipython():
|
18 |
+
"""Get the global InteractiveShell instance.
|
19 |
+
|
20 |
+
Returns None if no InteractiveShell instance is registered.
|
21 |
+
"""
|
22 |
+
from IPython.core.interactiveshell import InteractiveShell
|
23 |
+
if InteractiveShell.initialized():
|
24 |
+
return InteractiveShell.instance()
|
openalex_env_map/lib/python3.10/site-packages/IPython/core/guarded_eval.py
ADDED
@@ -0,0 +1,895 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
from inspect import isclass, signature, Signature
|
2 |
+
from typing import (
|
3 |
+
Annotated,
|
4 |
+
AnyStr,
|
5 |
+
Callable,
|
6 |
+
Dict,
|
7 |
+
Literal,
|
8 |
+
NamedTuple,
|
9 |
+
NewType,
|
10 |
+
Optional,
|
11 |
+
Protocol,
|
12 |
+
Set,
|
13 |
+
Sequence,
|
14 |
+
Tuple,
|
15 |
+
Type,
|
16 |
+
TypeGuard,
|
17 |
+
Union,
|
18 |
+
get_args,
|
19 |
+
get_origin,
|
20 |
+
is_typeddict,
|
21 |
+
)
|
22 |
+
import ast
|
23 |
+
import builtins
|
24 |
+
import collections
|
25 |
+
import operator
|
26 |
+
import sys
|
27 |
+
from functools import cached_property
|
28 |
+
from dataclasses import dataclass, field
|
29 |
+
from types import MethodDescriptorType, ModuleType
|
30 |
+
|
31 |
+
from IPython.utils.decorators import undoc
|
32 |
+
|
33 |
+
|
34 |
+
if sys.version_info < (3, 11):
|
35 |
+
from typing_extensions import Self, LiteralString
|
36 |
+
else:
|
37 |
+
from typing import Self, LiteralString
|
38 |
+
|
39 |
+
if sys.version_info < (3, 12):
|
40 |
+
from typing_extensions import TypeAliasType
|
41 |
+
else:
|
42 |
+
from typing import TypeAliasType
|
43 |
+
|
44 |
+
|
45 |
+
@undoc
|
46 |
+
class HasGetItem(Protocol):
|
47 |
+
def __getitem__(self, key) -> None: ...
|
48 |
+
|
49 |
+
|
50 |
+
@undoc
|
51 |
+
class InstancesHaveGetItem(Protocol):
|
52 |
+
def __call__(self, *args, **kwargs) -> HasGetItem: ...
|
53 |
+
|
54 |
+
|
55 |
+
@undoc
|
56 |
+
class HasGetAttr(Protocol):
|
57 |
+
def __getattr__(self, key) -> None: ...
|
58 |
+
|
59 |
+
|
60 |
+
@undoc
|
61 |
+
class DoesNotHaveGetAttr(Protocol):
|
62 |
+
pass
|
63 |
+
|
64 |
+
|
65 |
+
# By default `__getattr__` is not explicitly implemented on most objects
|
66 |
+
MayHaveGetattr = Union[HasGetAttr, DoesNotHaveGetAttr]
|
67 |
+
|
68 |
+
|
69 |
+
def _unbind_method(func: Callable) -> Union[Callable, None]:
|
70 |
+
"""Get unbound method for given bound method.
|
71 |
+
|
72 |
+
Returns None if cannot get unbound method, or method is already unbound.
|
73 |
+
"""
|
74 |
+
owner = getattr(func, "__self__", None)
|
75 |
+
owner_class = type(owner)
|
76 |
+
name = getattr(func, "__name__", None)
|
77 |
+
instance_dict_overrides = getattr(owner, "__dict__", None)
|
78 |
+
if (
|
79 |
+
owner is not None
|
80 |
+
and name
|
81 |
+
and (
|
82 |
+
not instance_dict_overrides
|
83 |
+
or (instance_dict_overrides and name not in instance_dict_overrides)
|
84 |
+
)
|
85 |
+
):
|
86 |
+
return getattr(owner_class, name)
|
87 |
+
return None
|
88 |
+
|
89 |
+
|
90 |
+
@undoc
|
91 |
+
@dataclass
|
92 |
+
class EvaluationPolicy:
|
93 |
+
"""Definition of evaluation policy."""
|
94 |
+
|
95 |
+
allow_locals_access: bool = False
|
96 |
+
allow_globals_access: bool = False
|
97 |
+
allow_item_access: bool = False
|
98 |
+
allow_attr_access: bool = False
|
99 |
+
allow_builtins_access: bool = False
|
100 |
+
allow_all_operations: bool = False
|
101 |
+
allow_any_calls: bool = False
|
102 |
+
allowed_calls: Set[Callable] = field(default_factory=set)
|
103 |
+
|
104 |
+
def can_get_item(self, value, item):
|
105 |
+
return self.allow_item_access
|
106 |
+
|
107 |
+
def can_get_attr(self, value, attr):
|
108 |
+
return self.allow_attr_access
|
109 |
+
|
110 |
+
def can_operate(self, dunders: Tuple[str, ...], a, b=None):
|
111 |
+
if self.allow_all_operations:
|
112 |
+
return True
|
113 |
+
|
114 |
+
def can_call(self, func):
|
115 |
+
if self.allow_any_calls:
|
116 |
+
return True
|
117 |
+
|
118 |
+
if func in self.allowed_calls:
|
119 |
+
return True
|
120 |
+
|
121 |
+
owner_method = _unbind_method(func)
|
122 |
+
|
123 |
+
if owner_method and owner_method in self.allowed_calls:
|
124 |
+
return True
|
125 |
+
|
126 |
+
|
127 |
+
def _get_external(module_name: str, access_path: Sequence[str]):
|
128 |
+
"""Get value from external module given a dotted access path.
|
129 |
+
|
130 |
+
Raises:
|
131 |
+
* `KeyError` if module is removed not found, and
|
132 |
+
* `AttributeError` if access path does not match an exported object
|
133 |
+
"""
|
134 |
+
member_type = sys.modules[module_name]
|
135 |
+
for attr in access_path:
|
136 |
+
member_type = getattr(member_type, attr)
|
137 |
+
return member_type
|
138 |
+
|
139 |
+
|
140 |
+
def _has_original_dunder_external(
|
141 |
+
value,
|
142 |
+
module_name: str,
|
143 |
+
access_path: Sequence[str],
|
144 |
+
method_name: str,
|
145 |
+
):
|
146 |
+
if module_name not in sys.modules:
|
147 |
+
# LBYLB as it is faster
|
148 |
+
return False
|
149 |
+
try:
|
150 |
+
member_type = _get_external(module_name, access_path)
|
151 |
+
value_type = type(value)
|
152 |
+
if type(value) == member_type:
|
153 |
+
return True
|
154 |
+
if method_name == "__getattribute__":
|
155 |
+
# we have to short-circuit here due to an unresolved issue in
|
156 |
+
# `isinstance` implementation: https://bugs.python.org/issue32683
|
157 |
+
return False
|
158 |
+
if isinstance(value, member_type):
|
159 |
+
method = getattr(value_type, method_name, None)
|
160 |
+
member_method = getattr(member_type, method_name, None)
|
161 |
+
if member_method == method:
|
162 |
+
return True
|
163 |
+
except (AttributeError, KeyError):
|
164 |
+
return False
|
165 |
+
|
166 |
+
|
167 |
+
def _has_original_dunder(
|
168 |
+
value, allowed_types, allowed_methods, allowed_external, method_name
|
169 |
+
):
|
170 |
+
# note: Python ignores `__getattr__`/`__getitem__` on instances,
|
171 |
+
# we only need to check at class level
|
172 |
+
value_type = type(value)
|
173 |
+
|
174 |
+
# strict type check passes → no need to check method
|
175 |
+
if value_type in allowed_types:
|
176 |
+
return True
|
177 |
+
|
178 |
+
method = getattr(value_type, method_name, None)
|
179 |
+
|
180 |
+
if method is None:
|
181 |
+
return None
|
182 |
+
|
183 |
+
if method in allowed_methods:
|
184 |
+
return True
|
185 |
+
|
186 |
+
for module_name, *access_path in allowed_external:
|
187 |
+
if _has_original_dunder_external(value, module_name, access_path, method_name):
|
188 |
+
return True
|
189 |
+
|
190 |
+
return False
|
191 |
+
|
192 |
+
|
193 |
+
@undoc
|
194 |
+
@dataclass
|
195 |
+
class SelectivePolicy(EvaluationPolicy):
|
196 |
+
allowed_getitem: Set[InstancesHaveGetItem] = field(default_factory=set)
|
197 |
+
allowed_getitem_external: Set[Tuple[str, ...]] = field(default_factory=set)
|
198 |
+
|
199 |
+
allowed_getattr: Set[MayHaveGetattr] = field(default_factory=set)
|
200 |
+
allowed_getattr_external: Set[Tuple[str, ...]] = field(default_factory=set)
|
201 |
+
|
202 |
+
allowed_operations: Set = field(default_factory=set)
|
203 |
+
allowed_operations_external: Set[Tuple[str, ...]] = field(default_factory=set)
|
204 |
+
|
205 |
+
_operation_methods_cache: Dict[str, Set[Callable]] = field(
|
206 |
+
default_factory=dict, init=False
|
207 |
+
)
|
208 |
+
|
209 |
+
def can_get_attr(self, value, attr):
|
210 |
+
has_original_attribute = _has_original_dunder(
|
211 |
+
value,
|
212 |
+
allowed_types=self.allowed_getattr,
|
213 |
+
allowed_methods=self._getattribute_methods,
|
214 |
+
allowed_external=self.allowed_getattr_external,
|
215 |
+
method_name="__getattribute__",
|
216 |
+
)
|
217 |
+
has_original_attr = _has_original_dunder(
|
218 |
+
value,
|
219 |
+
allowed_types=self.allowed_getattr,
|
220 |
+
allowed_methods=self._getattr_methods,
|
221 |
+
allowed_external=self.allowed_getattr_external,
|
222 |
+
method_name="__getattr__",
|
223 |
+
)
|
224 |
+
|
225 |
+
accept = False
|
226 |
+
|
227 |
+
# Many objects do not have `__getattr__`, this is fine.
|
228 |
+
if has_original_attr is None and has_original_attribute:
|
229 |
+
accept = True
|
230 |
+
else:
|
231 |
+
# Accept objects without modifications to `__getattr__` and `__getattribute__`
|
232 |
+
accept = has_original_attr and has_original_attribute
|
233 |
+
|
234 |
+
if accept:
|
235 |
+
# We still need to check for overridden properties.
|
236 |
+
|
237 |
+
value_class = type(value)
|
238 |
+
if not hasattr(value_class, attr):
|
239 |
+
return True
|
240 |
+
|
241 |
+
class_attr_val = getattr(value_class, attr)
|
242 |
+
is_property = isinstance(class_attr_val, property)
|
243 |
+
|
244 |
+
if not is_property:
|
245 |
+
return True
|
246 |
+
|
247 |
+
# Properties in allowed types are ok (although we do not include any
|
248 |
+
# properties in our default allow list currently).
|
249 |
+
if type(value) in self.allowed_getattr:
|
250 |
+
return True # pragma: no cover
|
251 |
+
|
252 |
+
# Properties in subclasses of allowed types may be ok if not changed
|
253 |
+
for module_name, *access_path in self.allowed_getattr_external:
|
254 |
+
try:
|
255 |
+
external_class = _get_external(module_name, access_path)
|
256 |
+
external_class_attr_val = getattr(external_class, attr)
|
257 |
+
except (KeyError, AttributeError):
|
258 |
+
return False # pragma: no cover
|
259 |
+
return class_attr_val == external_class_attr_val
|
260 |
+
|
261 |
+
return False
|
262 |
+
|
263 |
+
def can_get_item(self, value, item):
|
264 |
+
"""Allow accessing `__getiitem__` of allow-listed instances unless it was not modified."""
|
265 |
+
return _has_original_dunder(
|
266 |
+
value,
|
267 |
+
allowed_types=self.allowed_getitem,
|
268 |
+
allowed_methods=self._getitem_methods,
|
269 |
+
allowed_external=self.allowed_getitem_external,
|
270 |
+
method_name="__getitem__",
|
271 |
+
)
|
272 |
+
|
273 |
+
def can_operate(self, dunders: Tuple[str, ...], a, b=None):
|
274 |
+
objects = [a]
|
275 |
+
if b is not None:
|
276 |
+
objects.append(b)
|
277 |
+
return all(
|
278 |
+
[
|
279 |
+
_has_original_dunder(
|
280 |
+
obj,
|
281 |
+
allowed_types=self.allowed_operations,
|
282 |
+
allowed_methods=self._operator_dunder_methods(dunder),
|
283 |
+
allowed_external=self.allowed_operations_external,
|
284 |
+
method_name=dunder,
|
285 |
+
)
|
286 |
+
for dunder in dunders
|
287 |
+
for obj in objects
|
288 |
+
]
|
289 |
+
)
|
290 |
+
|
291 |
+
def _operator_dunder_methods(self, dunder: str) -> Set[Callable]:
|
292 |
+
if dunder not in self._operation_methods_cache:
|
293 |
+
self._operation_methods_cache[dunder] = self._safe_get_methods(
|
294 |
+
self.allowed_operations, dunder
|
295 |
+
)
|
296 |
+
return self._operation_methods_cache[dunder]
|
297 |
+
|
298 |
+
@cached_property
|
299 |
+
def _getitem_methods(self) -> Set[Callable]:
|
300 |
+
return self._safe_get_methods(self.allowed_getitem, "__getitem__")
|
301 |
+
|
302 |
+
@cached_property
|
303 |
+
def _getattr_methods(self) -> Set[Callable]:
|
304 |
+
return self._safe_get_methods(self.allowed_getattr, "__getattr__")
|
305 |
+
|
306 |
+
@cached_property
|
307 |
+
def _getattribute_methods(self) -> Set[Callable]:
|
308 |
+
return self._safe_get_methods(self.allowed_getattr, "__getattribute__")
|
309 |
+
|
310 |
+
def _safe_get_methods(self, classes, name) -> Set[Callable]:
|
311 |
+
return {
|
312 |
+
method
|
313 |
+
for class_ in classes
|
314 |
+
for method in [getattr(class_, name, None)]
|
315 |
+
if method
|
316 |
+
}
|
317 |
+
|
318 |
+
|
319 |
+
class _DummyNamedTuple(NamedTuple):
|
320 |
+
"""Used internally to retrieve methods of named tuple instance."""
|
321 |
+
|
322 |
+
|
323 |
+
class EvaluationContext(NamedTuple):
|
324 |
+
#: Local namespace
|
325 |
+
locals: dict
|
326 |
+
#: Global namespace
|
327 |
+
globals: dict
|
328 |
+
#: Evaluation policy identifier
|
329 |
+
evaluation: Literal["forbidden", "minimal", "limited", "unsafe", "dangerous"] = (
|
330 |
+
"forbidden"
|
331 |
+
)
|
332 |
+
#: Whether the evaluation of code takes place inside of a subscript.
|
333 |
+
#: Useful for evaluating ``:-1, 'col'`` in ``df[:-1, 'col']``.
|
334 |
+
in_subscript: bool = False
|
335 |
+
|
336 |
+
|
337 |
+
class _IdentitySubscript:
|
338 |
+
"""Returns the key itself when item is requested via subscript."""
|
339 |
+
|
340 |
+
def __getitem__(self, key):
|
341 |
+
return key
|
342 |
+
|
343 |
+
|
344 |
+
IDENTITY_SUBSCRIPT = _IdentitySubscript()
|
345 |
+
SUBSCRIPT_MARKER = "__SUBSCRIPT_SENTINEL__"
|
346 |
+
UNKNOWN_SIGNATURE = Signature()
|
347 |
+
NOT_EVALUATED = object()
|
348 |
+
|
349 |
+
|
350 |
+
class GuardRejection(Exception):
|
351 |
+
"""Exception raised when guard rejects evaluation attempt."""
|
352 |
+
|
353 |
+
pass
|
354 |
+
|
355 |
+
|
356 |
+
def guarded_eval(code: str, context: EvaluationContext):
|
357 |
+
"""Evaluate provided code in the evaluation context.
|
358 |
+
|
359 |
+
If evaluation policy given by context is set to ``forbidden``
|
360 |
+
no evaluation will be performed; if it is set to ``dangerous``
|
361 |
+
standard :func:`eval` will be used; finally, for any other,
|
362 |
+
policy :func:`eval_node` will be called on parsed AST.
|
363 |
+
"""
|
364 |
+
locals_ = context.locals
|
365 |
+
|
366 |
+
if context.evaluation == "forbidden":
|
367 |
+
raise GuardRejection("Forbidden mode")
|
368 |
+
|
369 |
+
# note: not using `ast.literal_eval` as it does not implement
|
370 |
+
# getitem at all, for example it fails on simple `[0][1]`
|
371 |
+
|
372 |
+
if context.in_subscript:
|
373 |
+
# syntactic sugar for ellipsis (:) is only available in subscripts
|
374 |
+
# so we need to trick the ast parser into thinking that we have
|
375 |
+
# a subscript, but we need to be able to later recognise that we did
|
376 |
+
# it so we can ignore the actual __getitem__ operation
|
377 |
+
if not code:
|
378 |
+
return tuple()
|
379 |
+
locals_ = locals_.copy()
|
380 |
+
locals_[SUBSCRIPT_MARKER] = IDENTITY_SUBSCRIPT
|
381 |
+
code = SUBSCRIPT_MARKER + "[" + code + "]"
|
382 |
+
context = EvaluationContext(**{**context._asdict(), **{"locals": locals_}})
|
383 |
+
|
384 |
+
if context.evaluation == "dangerous":
|
385 |
+
return eval(code, context.globals, context.locals)
|
386 |
+
|
387 |
+
expression = ast.parse(code, mode="eval")
|
388 |
+
|
389 |
+
return eval_node(expression, context)
|
390 |
+
|
391 |
+
|
392 |
+
BINARY_OP_DUNDERS: Dict[Type[ast.operator], Tuple[str]] = {
|
393 |
+
ast.Add: ("__add__",),
|
394 |
+
ast.Sub: ("__sub__",),
|
395 |
+
ast.Mult: ("__mul__",),
|
396 |
+
ast.Div: ("__truediv__",),
|
397 |
+
ast.FloorDiv: ("__floordiv__",),
|
398 |
+
ast.Mod: ("__mod__",),
|
399 |
+
ast.Pow: ("__pow__",),
|
400 |
+
ast.LShift: ("__lshift__",),
|
401 |
+
ast.RShift: ("__rshift__",),
|
402 |
+
ast.BitOr: ("__or__",),
|
403 |
+
ast.BitXor: ("__xor__",),
|
404 |
+
ast.BitAnd: ("__and__",),
|
405 |
+
ast.MatMult: ("__matmul__",),
|
406 |
+
}
|
407 |
+
|
408 |
+
COMP_OP_DUNDERS: Dict[Type[ast.cmpop], Tuple[str, ...]] = {
|
409 |
+
ast.Eq: ("__eq__",),
|
410 |
+
ast.NotEq: ("__ne__", "__eq__"),
|
411 |
+
ast.Lt: ("__lt__", "__gt__"),
|
412 |
+
ast.LtE: ("__le__", "__ge__"),
|
413 |
+
ast.Gt: ("__gt__", "__lt__"),
|
414 |
+
ast.GtE: ("__ge__", "__le__"),
|
415 |
+
ast.In: ("__contains__",),
|
416 |
+
# Note: ast.Is, ast.IsNot, ast.NotIn are handled specially
|
417 |
+
}
|
418 |
+
|
419 |
+
UNARY_OP_DUNDERS: Dict[Type[ast.unaryop], Tuple[str, ...]] = {
|
420 |
+
ast.USub: ("__neg__",),
|
421 |
+
ast.UAdd: ("__pos__",),
|
422 |
+
# we have to check both __inv__ and __invert__!
|
423 |
+
ast.Invert: ("__invert__", "__inv__"),
|
424 |
+
ast.Not: ("__not__",),
|
425 |
+
}
|
426 |
+
|
427 |
+
|
428 |
+
class ImpersonatingDuck:
|
429 |
+
"""A dummy class used to create objects of other classes without calling their ``__init__``"""
|
430 |
+
|
431 |
+
# no-op: override __class__ to impersonate
|
432 |
+
|
433 |
+
|
434 |
+
class _Duck:
|
435 |
+
"""A dummy class used to create objects pretending to have given attributes"""
|
436 |
+
|
437 |
+
def __init__(self, attributes: Optional[dict] = None, items: Optional[dict] = None):
|
438 |
+
self.attributes = attributes or {}
|
439 |
+
self.items = items or {}
|
440 |
+
|
441 |
+
def __getattr__(self, attr: str):
|
442 |
+
return self.attributes[attr]
|
443 |
+
|
444 |
+
def __hasattr__(self, attr: str):
|
445 |
+
return attr in self.attributes
|
446 |
+
|
447 |
+
def __dir__(self):
|
448 |
+
return [*dir(super), *self.attributes]
|
449 |
+
|
450 |
+
def __getitem__(self, key: str):
|
451 |
+
return self.items[key]
|
452 |
+
|
453 |
+
def __hasitem__(self, key: str):
|
454 |
+
return self.items[key]
|
455 |
+
|
456 |
+
def _ipython_key_completions_(self):
|
457 |
+
return self.items.keys()
|
458 |
+
|
459 |
+
|
460 |
+
def _find_dunder(node_op, dunders) -> Union[Tuple[str, ...], None]:
|
461 |
+
dunder = None
|
462 |
+
for op, candidate_dunder in dunders.items():
|
463 |
+
if isinstance(node_op, op):
|
464 |
+
dunder = candidate_dunder
|
465 |
+
return dunder
|
466 |
+
|
467 |
+
|
468 |
+
def eval_node(node: Union[ast.AST, None], context: EvaluationContext):
|
469 |
+
"""Evaluate AST node in provided context.
|
470 |
+
|
471 |
+
Applies evaluation restrictions defined in the context. Currently does not support evaluation of functions with keyword arguments.
|
472 |
+
|
473 |
+
Does not evaluate actions that always have side effects:
|
474 |
+
|
475 |
+
- class definitions (``class sth: ...``)
|
476 |
+
- function definitions (``def sth: ...``)
|
477 |
+
- variable assignments (``x = 1``)
|
478 |
+
- augmented assignments (``x += 1``)
|
479 |
+
- deletions (``del x``)
|
480 |
+
|
481 |
+
Does not evaluate operations which do not return values:
|
482 |
+
|
483 |
+
- assertions (``assert x``)
|
484 |
+
- pass (``pass``)
|
485 |
+
- imports (``import x``)
|
486 |
+
- control flow:
|
487 |
+
|
488 |
+
- conditionals (``if x:``) except for ternary IfExp (``a if x else b``)
|
489 |
+
- loops (``for`` and ``while``)
|
490 |
+
- exception handling
|
491 |
+
|
492 |
+
The purpose of this function is to guard against unwanted side-effects;
|
493 |
+
it does not give guarantees on protection from malicious code execution.
|
494 |
+
"""
|
495 |
+
policy = EVALUATION_POLICIES[context.evaluation]
|
496 |
+
if node is None:
|
497 |
+
return None
|
498 |
+
if isinstance(node, ast.Expression):
|
499 |
+
return eval_node(node.body, context)
|
500 |
+
if isinstance(node, ast.BinOp):
|
501 |
+
left = eval_node(node.left, context)
|
502 |
+
right = eval_node(node.right, context)
|
503 |
+
dunders = _find_dunder(node.op, BINARY_OP_DUNDERS)
|
504 |
+
if dunders:
|
505 |
+
if policy.can_operate(dunders, left, right):
|
506 |
+
return getattr(left, dunders[0])(right)
|
507 |
+
else:
|
508 |
+
raise GuardRejection(
|
509 |
+
f"Operation (`{dunders}`) for",
|
510 |
+
type(left),
|
511 |
+
f"not allowed in {context.evaluation} mode",
|
512 |
+
)
|
513 |
+
if isinstance(node, ast.Compare):
|
514 |
+
left = eval_node(node.left, context)
|
515 |
+
all_true = True
|
516 |
+
negate = False
|
517 |
+
for op, right in zip(node.ops, node.comparators):
|
518 |
+
right = eval_node(right, context)
|
519 |
+
dunder = None
|
520 |
+
dunders = _find_dunder(op, COMP_OP_DUNDERS)
|
521 |
+
if not dunders:
|
522 |
+
if isinstance(op, ast.NotIn):
|
523 |
+
dunders = COMP_OP_DUNDERS[ast.In]
|
524 |
+
negate = True
|
525 |
+
if isinstance(op, ast.Is):
|
526 |
+
dunder = "is_"
|
527 |
+
if isinstance(op, ast.IsNot):
|
528 |
+
dunder = "is_"
|
529 |
+
negate = True
|
530 |
+
if not dunder and dunders:
|
531 |
+
dunder = dunders[0]
|
532 |
+
if dunder:
|
533 |
+
a, b = (right, left) if dunder == "__contains__" else (left, right)
|
534 |
+
if dunder == "is_" or dunders and policy.can_operate(dunders, a, b):
|
535 |
+
result = getattr(operator, dunder)(a, b)
|
536 |
+
if negate:
|
537 |
+
result = not result
|
538 |
+
if not result:
|
539 |
+
all_true = False
|
540 |
+
left = right
|
541 |
+
else:
|
542 |
+
raise GuardRejection(
|
543 |
+
f"Comparison (`{dunder}`) for",
|
544 |
+
type(left),
|
545 |
+
f"not allowed in {context.evaluation} mode",
|
546 |
+
)
|
547 |
+
else:
|
548 |
+
raise ValueError(
|
549 |
+
f"Comparison `{dunder}` not supported"
|
550 |
+
) # pragma: no cover
|
551 |
+
return all_true
|
552 |
+
if isinstance(node, ast.Constant):
|
553 |
+
return node.value
|
554 |
+
if isinstance(node, ast.Tuple):
|
555 |
+
return tuple(eval_node(e, context) for e in node.elts)
|
556 |
+
if isinstance(node, ast.List):
|
557 |
+
return [eval_node(e, context) for e in node.elts]
|
558 |
+
if isinstance(node, ast.Set):
|
559 |
+
return {eval_node(e, context) for e in node.elts}
|
560 |
+
if isinstance(node, ast.Dict):
|
561 |
+
return dict(
|
562 |
+
zip(
|
563 |
+
[eval_node(k, context) for k in node.keys],
|
564 |
+
[eval_node(v, context) for v in node.values],
|
565 |
+
)
|
566 |
+
)
|
567 |
+
if isinstance(node, ast.Slice):
|
568 |
+
return slice(
|
569 |
+
eval_node(node.lower, context),
|
570 |
+
eval_node(node.upper, context),
|
571 |
+
eval_node(node.step, context),
|
572 |
+
)
|
573 |
+
if isinstance(node, ast.UnaryOp):
|
574 |
+
value = eval_node(node.operand, context)
|
575 |
+
dunders = _find_dunder(node.op, UNARY_OP_DUNDERS)
|
576 |
+
if dunders:
|
577 |
+
if policy.can_operate(dunders, value):
|
578 |
+
return getattr(value, dunders[0])()
|
579 |
+
else:
|
580 |
+
raise GuardRejection(
|
581 |
+
f"Operation (`{dunders}`) for",
|
582 |
+
type(value),
|
583 |
+
f"not allowed in {context.evaluation} mode",
|
584 |
+
)
|
585 |
+
if isinstance(node, ast.Subscript):
|
586 |
+
value = eval_node(node.value, context)
|
587 |
+
slice_ = eval_node(node.slice, context)
|
588 |
+
if policy.can_get_item(value, slice_):
|
589 |
+
return value[slice_]
|
590 |
+
raise GuardRejection(
|
591 |
+
"Subscript access (`__getitem__`) for",
|
592 |
+
type(value), # not joined to avoid calling `repr`
|
593 |
+
f" not allowed in {context.evaluation} mode",
|
594 |
+
)
|
595 |
+
if isinstance(node, ast.Name):
|
596 |
+
return _eval_node_name(node.id, context)
|
597 |
+
if isinstance(node, ast.Attribute):
|
598 |
+
value = eval_node(node.value, context)
|
599 |
+
if policy.can_get_attr(value, node.attr):
|
600 |
+
return getattr(value, node.attr)
|
601 |
+
raise GuardRejection(
|
602 |
+
"Attribute access (`__getattr__`) for",
|
603 |
+
type(value), # not joined to avoid calling `repr`
|
604 |
+
f"not allowed in {context.evaluation} mode",
|
605 |
+
)
|
606 |
+
if isinstance(node, ast.IfExp):
|
607 |
+
test = eval_node(node.test, context)
|
608 |
+
if test:
|
609 |
+
return eval_node(node.body, context)
|
610 |
+
else:
|
611 |
+
return eval_node(node.orelse, context)
|
612 |
+
if isinstance(node, ast.Call):
|
613 |
+
func = eval_node(node.func, context)
|
614 |
+
if policy.can_call(func) and not node.keywords:
|
615 |
+
args = [eval_node(arg, context) for arg in node.args]
|
616 |
+
return func(*args)
|
617 |
+
if isclass(func):
|
618 |
+
# this code path gets entered when calling class e.g. `MyClass()`
|
619 |
+
# or `my_instance.__class__()` - in both cases `func` is `MyClass`.
|
620 |
+
# Should return `MyClass` if `__new__` is not overridden,
|
621 |
+
# otherwise whatever `__new__` return type is.
|
622 |
+
overridden_return_type = _eval_return_type(func.__new__, node, context)
|
623 |
+
if overridden_return_type is not NOT_EVALUATED:
|
624 |
+
return overridden_return_type
|
625 |
+
return _create_duck_for_heap_type(func)
|
626 |
+
else:
|
627 |
+
return_type = _eval_return_type(func, node, context)
|
628 |
+
if return_type is not NOT_EVALUATED:
|
629 |
+
return return_type
|
630 |
+
raise GuardRejection(
|
631 |
+
"Call for",
|
632 |
+
func, # not joined to avoid calling `repr`
|
633 |
+
f"not allowed in {context.evaluation} mode",
|
634 |
+
)
|
635 |
+
raise ValueError("Unhandled node", ast.dump(node))
|
636 |
+
|
637 |
+
|
638 |
+
def _eval_return_type(func: Callable, node: ast.Call, context: EvaluationContext):
|
639 |
+
"""Evaluate return type of a given callable function.
|
640 |
+
|
641 |
+
Returns the built-in type, a duck or NOT_EVALUATED sentinel.
|
642 |
+
"""
|
643 |
+
try:
|
644 |
+
sig = signature(func)
|
645 |
+
except ValueError:
|
646 |
+
sig = UNKNOWN_SIGNATURE
|
647 |
+
# if annotation was not stringized, or it was stringized
|
648 |
+
# but resolved by signature call we know the return type
|
649 |
+
not_empty = sig.return_annotation is not Signature.empty
|
650 |
+
if not_empty:
|
651 |
+
return _resolve_annotation(sig.return_annotation, sig, func, node, context)
|
652 |
+
return NOT_EVALUATED
|
653 |
+
|
654 |
+
|
655 |
+
def _resolve_annotation(
|
656 |
+
annotation,
|
657 |
+
sig: Signature,
|
658 |
+
func: Callable,
|
659 |
+
node: ast.Call,
|
660 |
+
context: EvaluationContext,
|
661 |
+
):
|
662 |
+
"""Resolve annotation created by user with `typing` module and custom objects."""
|
663 |
+
annotation = (
|
664 |
+
_eval_node_name(annotation, context)
|
665 |
+
if isinstance(annotation, str)
|
666 |
+
else annotation
|
667 |
+
)
|
668 |
+
origin = get_origin(annotation)
|
669 |
+
if annotation is Self and hasattr(func, "__self__"):
|
670 |
+
return func.__self__
|
671 |
+
elif origin is Literal:
|
672 |
+
type_args = get_args(annotation)
|
673 |
+
if len(type_args) == 1:
|
674 |
+
return type_args[0]
|
675 |
+
elif annotation is LiteralString:
|
676 |
+
return ""
|
677 |
+
elif annotation is AnyStr:
|
678 |
+
index = None
|
679 |
+
for i, (key, value) in enumerate(sig.parameters.items()):
|
680 |
+
if value.annotation is AnyStr:
|
681 |
+
index = i
|
682 |
+
break
|
683 |
+
if index is not None and index < len(node.args):
|
684 |
+
return eval_node(node.args[index], context)
|
685 |
+
elif origin is TypeGuard:
|
686 |
+
return bool()
|
687 |
+
elif origin is Union:
|
688 |
+
attributes = [
|
689 |
+
attr
|
690 |
+
for type_arg in get_args(annotation)
|
691 |
+
for attr in dir(_resolve_annotation(type_arg, sig, func, node, context))
|
692 |
+
]
|
693 |
+
return _Duck(attributes=dict.fromkeys(attributes))
|
694 |
+
elif is_typeddict(annotation):
|
695 |
+
return _Duck(
|
696 |
+
attributes=dict.fromkeys(dir(dict())),
|
697 |
+
items={
|
698 |
+
k: _resolve_annotation(v, sig, func, node, context)
|
699 |
+
for k, v in annotation.__annotations__.items()
|
700 |
+
},
|
701 |
+
)
|
702 |
+
elif hasattr(annotation, "_is_protocol"):
|
703 |
+
return _Duck(attributes=dict.fromkeys(dir(annotation)))
|
704 |
+
elif origin is Annotated:
|
705 |
+
type_arg = get_args(annotation)[0]
|
706 |
+
return _resolve_annotation(type_arg, sig, func, node, context)
|
707 |
+
elif isinstance(annotation, NewType):
|
708 |
+
return _eval_or_create_duck(annotation.__supertype__, node, context)
|
709 |
+
elif isinstance(annotation, TypeAliasType):
|
710 |
+
return _eval_or_create_duck(annotation.__value__, node, context)
|
711 |
+
else:
|
712 |
+
return _eval_or_create_duck(annotation, node, context)
|
713 |
+
|
714 |
+
|
715 |
+
def _eval_node_name(node_id: str, context: EvaluationContext):
|
716 |
+
policy = EVALUATION_POLICIES[context.evaluation]
|
717 |
+
if policy.allow_locals_access and node_id in context.locals:
|
718 |
+
return context.locals[node_id]
|
719 |
+
if policy.allow_globals_access and node_id in context.globals:
|
720 |
+
return context.globals[node_id]
|
721 |
+
if policy.allow_builtins_access and hasattr(builtins, node_id):
|
722 |
+
# note: do not use __builtins__, it is implementation detail of cPython
|
723 |
+
return getattr(builtins, node_id)
|
724 |
+
if not policy.allow_globals_access and not policy.allow_locals_access:
|
725 |
+
raise GuardRejection(
|
726 |
+
f"Namespace access not allowed in {context.evaluation} mode"
|
727 |
+
)
|
728 |
+
else:
|
729 |
+
raise NameError(f"{node_id} not found in locals, globals, nor builtins")
|
730 |
+
|
731 |
+
|
732 |
+
def _eval_or_create_duck(duck_type, node: ast.Call, context: EvaluationContext):
|
733 |
+
policy = EVALUATION_POLICIES[context.evaluation]
|
734 |
+
# if allow-listed builtin is on type annotation, instantiate it
|
735 |
+
if policy.can_call(duck_type) and not node.keywords:
|
736 |
+
args = [eval_node(arg, context) for arg in node.args]
|
737 |
+
return duck_type(*args)
|
738 |
+
# if custom class is in type annotation, mock it
|
739 |
+
return _create_duck_for_heap_type(duck_type)
|
740 |
+
|
741 |
+
|
742 |
+
def _create_duck_for_heap_type(duck_type):
|
743 |
+
"""Create an imitation of an object of a given type (a duck).
|
744 |
+
|
745 |
+
Returns the duck or NOT_EVALUATED sentinel if duck could not be created.
|
746 |
+
"""
|
747 |
+
duck = ImpersonatingDuck()
|
748 |
+
try:
|
749 |
+
# this only works for heap types, not builtins
|
750 |
+
duck.__class__ = duck_type
|
751 |
+
return duck
|
752 |
+
except TypeError:
|
753 |
+
pass
|
754 |
+
return NOT_EVALUATED
|
755 |
+
|
756 |
+
|
757 |
+
SUPPORTED_EXTERNAL_GETITEM = {
|
758 |
+
("pandas", "core", "indexing", "_iLocIndexer"),
|
759 |
+
("pandas", "core", "indexing", "_LocIndexer"),
|
760 |
+
("pandas", "DataFrame"),
|
761 |
+
("pandas", "Series"),
|
762 |
+
("numpy", "ndarray"),
|
763 |
+
("numpy", "void"),
|
764 |
+
}
|
765 |
+
|
766 |
+
|
767 |
+
BUILTIN_GETITEM: Set[InstancesHaveGetItem] = {
|
768 |
+
dict,
|
769 |
+
str, # type: ignore[arg-type]
|
770 |
+
bytes, # type: ignore[arg-type]
|
771 |
+
list,
|
772 |
+
tuple,
|
773 |
+
collections.defaultdict,
|
774 |
+
collections.deque,
|
775 |
+
collections.OrderedDict,
|
776 |
+
collections.ChainMap,
|
777 |
+
collections.UserDict,
|
778 |
+
collections.UserList,
|
779 |
+
collections.UserString, # type: ignore[arg-type]
|
780 |
+
_DummyNamedTuple,
|
781 |
+
_IdentitySubscript,
|
782 |
+
}
|
783 |
+
|
784 |
+
|
785 |
+
def _list_methods(cls, source=None):
|
786 |
+
"""For use on immutable objects or with methods returning a copy"""
|
787 |
+
return [getattr(cls, k) for k in (source if source else dir(cls))]
|
788 |
+
|
789 |
+
|
790 |
+
dict_non_mutating_methods = ("copy", "keys", "values", "items")
|
791 |
+
list_non_mutating_methods = ("copy", "index", "count")
|
792 |
+
set_non_mutating_methods = set(dir(set)) & set(dir(frozenset))
|
793 |
+
|
794 |
+
|
795 |
+
dict_keys: Type[collections.abc.KeysView] = type({}.keys())
|
796 |
+
|
797 |
+
NUMERICS = {int, float, complex}
|
798 |
+
|
799 |
+
ALLOWED_CALLS = {
|
800 |
+
bytes,
|
801 |
+
*_list_methods(bytes),
|
802 |
+
dict,
|
803 |
+
*_list_methods(dict, dict_non_mutating_methods),
|
804 |
+
dict_keys.isdisjoint,
|
805 |
+
list,
|
806 |
+
*_list_methods(list, list_non_mutating_methods),
|
807 |
+
set,
|
808 |
+
*_list_methods(set, set_non_mutating_methods),
|
809 |
+
frozenset,
|
810 |
+
*_list_methods(frozenset),
|
811 |
+
range,
|
812 |
+
str,
|
813 |
+
*_list_methods(str),
|
814 |
+
tuple,
|
815 |
+
*_list_methods(tuple),
|
816 |
+
*NUMERICS,
|
817 |
+
*[method for numeric_cls in NUMERICS for method in _list_methods(numeric_cls)],
|
818 |
+
collections.deque,
|
819 |
+
*_list_methods(collections.deque, list_non_mutating_methods),
|
820 |
+
collections.defaultdict,
|
821 |
+
*_list_methods(collections.defaultdict, dict_non_mutating_methods),
|
822 |
+
collections.OrderedDict,
|
823 |
+
*_list_methods(collections.OrderedDict, dict_non_mutating_methods),
|
824 |
+
collections.UserDict,
|
825 |
+
*_list_methods(collections.UserDict, dict_non_mutating_methods),
|
826 |
+
collections.UserList,
|
827 |
+
*_list_methods(collections.UserList, list_non_mutating_methods),
|
828 |
+
collections.UserString,
|
829 |
+
*_list_methods(collections.UserString, dir(str)),
|
830 |
+
collections.Counter,
|
831 |
+
*_list_methods(collections.Counter, dict_non_mutating_methods),
|
832 |
+
collections.Counter.elements,
|
833 |
+
collections.Counter.most_common,
|
834 |
+
}
|
835 |
+
|
836 |
+
BUILTIN_GETATTR: Set[MayHaveGetattr] = {
|
837 |
+
*BUILTIN_GETITEM,
|
838 |
+
set,
|
839 |
+
frozenset,
|
840 |
+
object,
|
841 |
+
type, # `type` handles a lot of generic cases, e.g. numbers as in `int.real`.
|
842 |
+
*NUMERICS,
|
843 |
+
dict_keys,
|
844 |
+
MethodDescriptorType,
|
845 |
+
ModuleType,
|
846 |
+
}
|
847 |
+
|
848 |
+
|
849 |
+
BUILTIN_OPERATIONS = {*BUILTIN_GETATTR}
|
850 |
+
|
851 |
+
EVALUATION_POLICIES = {
|
852 |
+
"minimal": EvaluationPolicy(
|
853 |
+
allow_builtins_access=True,
|
854 |
+
allow_locals_access=False,
|
855 |
+
allow_globals_access=False,
|
856 |
+
allow_item_access=False,
|
857 |
+
allow_attr_access=False,
|
858 |
+
allowed_calls=set(),
|
859 |
+
allow_any_calls=False,
|
860 |
+
allow_all_operations=False,
|
861 |
+
),
|
862 |
+
"limited": SelectivePolicy(
|
863 |
+
allowed_getitem=BUILTIN_GETITEM,
|
864 |
+
allowed_getitem_external=SUPPORTED_EXTERNAL_GETITEM,
|
865 |
+
allowed_getattr=BUILTIN_GETATTR,
|
866 |
+
allowed_getattr_external={
|
867 |
+
# pandas Series/Frame implements custom `__getattr__`
|
868 |
+
("pandas", "DataFrame"),
|
869 |
+
("pandas", "Series"),
|
870 |
+
},
|
871 |
+
allowed_operations=BUILTIN_OPERATIONS,
|
872 |
+
allow_builtins_access=True,
|
873 |
+
allow_locals_access=True,
|
874 |
+
allow_globals_access=True,
|
875 |
+
allowed_calls=ALLOWED_CALLS,
|
876 |
+
),
|
877 |
+
"unsafe": EvaluationPolicy(
|
878 |
+
allow_builtins_access=True,
|
879 |
+
allow_locals_access=True,
|
880 |
+
allow_globals_access=True,
|
881 |
+
allow_attr_access=True,
|
882 |
+
allow_item_access=True,
|
883 |
+
allow_any_calls=True,
|
884 |
+
allow_all_operations=True,
|
885 |
+
),
|
886 |
+
}
|
887 |
+
|
888 |
+
|
889 |
+
__all__ = [
|
890 |
+
"guarded_eval",
|
891 |
+
"eval_node",
|
892 |
+
"GuardRejection",
|
893 |
+
"EvaluationContext",
|
894 |
+
"_unbind_method",
|
895 |
+
]
|
openalex_env_map/lib/python3.10/site-packages/IPython/core/history.py
ADDED
@@ -0,0 +1,989 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
""" History related magics and functionality """
|
2 |
+
|
3 |
+
# Copyright (c) IPython Development Team.
|
4 |
+
# Distributed under the terms of the Modified BSD License.
|
5 |
+
|
6 |
+
|
7 |
+
import atexit
|
8 |
+
import datetime
|
9 |
+
import re
|
10 |
+
import sqlite3
|
11 |
+
import threading
|
12 |
+
from pathlib import Path
|
13 |
+
|
14 |
+
from decorator import decorator
|
15 |
+
from traitlets import (
|
16 |
+
Any,
|
17 |
+
Bool,
|
18 |
+
Dict,
|
19 |
+
Instance,
|
20 |
+
Integer,
|
21 |
+
List,
|
22 |
+
TraitError,
|
23 |
+
Unicode,
|
24 |
+
Union,
|
25 |
+
default,
|
26 |
+
observe,
|
27 |
+
)
|
28 |
+
from traitlets.config.configurable import LoggingConfigurable
|
29 |
+
|
30 |
+
from IPython.paths import locate_profile
|
31 |
+
from IPython.utils.decorators import undoc
|
32 |
+
|
33 |
+
#-----------------------------------------------------------------------------
|
34 |
+
# Classes and functions
|
35 |
+
#-----------------------------------------------------------------------------
|
36 |
+
|
37 |
+
@undoc
|
38 |
+
class DummyDB(object):
|
39 |
+
"""Dummy DB that will act as a black hole for history.
|
40 |
+
|
41 |
+
Only used in the absence of sqlite"""
|
42 |
+
def execute(*args, **kwargs):
|
43 |
+
return []
|
44 |
+
|
45 |
+
def commit(self, *args, **kwargs):
|
46 |
+
pass
|
47 |
+
|
48 |
+
def __enter__(self, *args, **kwargs):
|
49 |
+
pass
|
50 |
+
|
51 |
+
def __exit__(self, *args, **kwargs):
|
52 |
+
pass
|
53 |
+
|
54 |
+
|
55 |
+
@decorator
|
56 |
+
def only_when_enabled(f, self, *a, **kw):
|
57 |
+
"""Decorator: return an empty list in the absence of sqlite."""
|
58 |
+
if not self.enabled:
|
59 |
+
return []
|
60 |
+
else:
|
61 |
+
return f(self, *a, **kw)
|
62 |
+
|
63 |
+
|
64 |
+
# use 16kB as threshold for whether a corrupt history db should be saved
|
65 |
+
# that should be at least 100 entries or so
|
66 |
+
_SAVE_DB_SIZE = 16384
|
67 |
+
|
68 |
+
@decorator
|
69 |
+
def catch_corrupt_db(f, self, *a, **kw):
|
70 |
+
"""A decorator which wraps HistoryAccessor method calls to catch errors from
|
71 |
+
a corrupt SQLite database, move the old database out of the way, and create
|
72 |
+
a new one.
|
73 |
+
|
74 |
+
We avoid clobbering larger databases because this may be triggered due to filesystem issues,
|
75 |
+
not just a corrupt file.
|
76 |
+
"""
|
77 |
+
try:
|
78 |
+
return f(self, *a, **kw)
|
79 |
+
except (sqlite3.DatabaseError, sqlite3.OperationalError) as e:
|
80 |
+
self._corrupt_db_counter += 1
|
81 |
+
self.log.error("Failed to open SQLite history %s (%s).", self.hist_file, e)
|
82 |
+
if self.hist_file != ':memory:':
|
83 |
+
if self._corrupt_db_counter > self._corrupt_db_limit:
|
84 |
+
self.hist_file = ':memory:'
|
85 |
+
self.log.error("Failed to load history too many times, history will not be saved.")
|
86 |
+
elif self.hist_file.is_file():
|
87 |
+
# move the file out of the way
|
88 |
+
base = str(self.hist_file.parent / self.hist_file.stem)
|
89 |
+
ext = self.hist_file.suffix
|
90 |
+
size = self.hist_file.stat().st_size
|
91 |
+
if size >= _SAVE_DB_SIZE:
|
92 |
+
# if there's significant content, avoid clobbering
|
93 |
+
now = datetime.datetime.now().isoformat().replace(':', '.')
|
94 |
+
newpath = base + '-corrupt-' + now + ext
|
95 |
+
# don't clobber previous corrupt backups
|
96 |
+
for i in range(100):
|
97 |
+
if not Path(newpath).exists():
|
98 |
+
break
|
99 |
+
else:
|
100 |
+
newpath = base + '-corrupt-' + now + (u'-%i' % i) + ext
|
101 |
+
else:
|
102 |
+
# not much content, possibly empty; don't worry about clobbering
|
103 |
+
# maybe we should just delete it?
|
104 |
+
newpath = base + '-corrupt' + ext
|
105 |
+
self.hist_file.rename(newpath)
|
106 |
+
self.log.error("History file was moved to %s and a new file created.", newpath)
|
107 |
+
self.init_db()
|
108 |
+
return []
|
109 |
+
else:
|
110 |
+
# Failed with :memory:, something serious is wrong
|
111 |
+
raise
|
112 |
+
|
113 |
+
|
114 |
+
class HistoryAccessorBase(LoggingConfigurable):
|
115 |
+
"""An abstract class for History Accessors """
|
116 |
+
|
117 |
+
def get_tail(self, n=10, raw=True, output=False, include_latest=False):
|
118 |
+
raise NotImplementedError
|
119 |
+
|
120 |
+
def search(self, pattern="*", raw=True, search_raw=True,
|
121 |
+
output=False, n=None, unique=False):
|
122 |
+
raise NotImplementedError
|
123 |
+
|
124 |
+
def get_range(self, session, start=1, stop=None, raw=True,output=False):
|
125 |
+
raise NotImplementedError
|
126 |
+
|
127 |
+
def get_range_by_str(self, rangestr, raw=True, output=False):
|
128 |
+
raise NotImplementedError
|
129 |
+
|
130 |
+
|
131 |
+
class HistoryAccessor(HistoryAccessorBase):
|
132 |
+
"""Access the history database without adding to it.
|
133 |
+
|
134 |
+
This is intended for use by standalone history tools. IPython shells use
|
135 |
+
HistoryManager, below, which is a subclass of this."""
|
136 |
+
|
137 |
+
# counter for init_db retries, so we don't keep trying over and over
|
138 |
+
_corrupt_db_counter = 0
|
139 |
+
# after two failures, fallback on :memory:
|
140 |
+
_corrupt_db_limit = 2
|
141 |
+
|
142 |
+
# String holding the path to the history file
|
143 |
+
hist_file = Union(
|
144 |
+
[Instance(Path), Unicode()],
|
145 |
+
help="""Path to file to use for SQLite history database.
|
146 |
+
|
147 |
+
By default, IPython will put the history database in the IPython
|
148 |
+
profile directory. If you would rather share one history among
|
149 |
+
profiles, you can set this value in each, so that they are consistent.
|
150 |
+
|
151 |
+
Due to an issue with fcntl, SQLite is known to misbehave on some NFS
|
152 |
+
mounts. If you see IPython hanging, try setting this to something on a
|
153 |
+
local disk, e.g::
|
154 |
+
|
155 |
+
ipython --HistoryManager.hist_file=/tmp/ipython_hist.sqlite
|
156 |
+
|
157 |
+
you can also use the specific value `:memory:` (including the colon
|
158 |
+
at both end but not the back ticks), to avoid creating an history file.
|
159 |
+
|
160 |
+
""",
|
161 |
+
).tag(config=True)
|
162 |
+
|
163 |
+
enabled = Bool(True,
|
164 |
+
help="""enable the SQLite history
|
165 |
+
|
166 |
+
set enabled=False to disable the SQLite history,
|
167 |
+
in which case there will be no stored history, no SQLite connection,
|
168 |
+
and no background saving thread. This may be necessary in some
|
169 |
+
threaded environments where IPython is embedded.
|
170 |
+
""",
|
171 |
+
).tag(config=True)
|
172 |
+
|
173 |
+
connection_options = Dict(
|
174 |
+
help="""Options for configuring the SQLite connection
|
175 |
+
|
176 |
+
These options are passed as keyword args to sqlite3.connect
|
177 |
+
when establishing database connections.
|
178 |
+
"""
|
179 |
+
).tag(config=True)
|
180 |
+
|
181 |
+
@default("connection_options")
|
182 |
+
def _default_connection_options(self):
|
183 |
+
return dict(check_same_thread=False)
|
184 |
+
|
185 |
+
# The SQLite database
|
186 |
+
db = Any()
|
187 |
+
@observe('db')
|
188 |
+
def _db_changed(self, change):
|
189 |
+
"""validate the db, since it can be an Instance of two different types"""
|
190 |
+
new = change['new']
|
191 |
+
connection_types = (DummyDB, sqlite3.Connection)
|
192 |
+
if not isinstance(new, connection_types):
|
193 |
+
msg = "%s.db must be sqlite3 Connection or DummyDB, not %r" % \
|
194 |
+
(self.__class__.__name__, new)
|
195 |
+
raise TraitError(msg)
|
196 |
+
|
197 |
+
def __init__(self, profile="default", hist_file="", **traits):
|
198 |
+
"""Create a new history accessor.
|
199 |
+
|
200 |
+
Parameters
|
201 |
+
----------
|
202 |
+
profile : str
|
203 |
+
The name of the profile from which to open history.
|
204 |
+
hist_file : str
|
205 |
+
Path to an SQLite history database stored by IPython. If specified,
|
206 |
+
hist_file overrides profile.
|
207 |
+
config : :class:`~traitlets.config.loader.Config`
|
208 |
+
Config object. hist_file can also be set through this.
|
209 |
+
"""
|
210 |
+
super(HistoryAccessor, self).__init__(**traits)
|
211 |
+
# defer setting hist_file from kwarg until after init,
|
212 |
+
# otherwise the default kwarg value would clobber any value
|
213 |
+
# set by config
|
214 |
+
if hist_file:
|
215 |
+
self.hist_file = hist_file
|
216 |
+
|
217 |
+
try:
|
218 |
+
self.hist_file
|
219 |
+
except TraitError:
|
220 |
+
# No one has set the hist_file, yet.
|
221 |
+
self.hist_file = self._get_hist_file_name(profile)
|
222 |
+
|
223 |
+
self.init_db()
|
224 |
+
|
225 |
+
def _get_hist_file_name(self, profile='default'):
|
226 |
+
"""Find the history file for the given profile name.
|
227 |
+
|
228 |
+
This is overridden by the HistoryManager subclass, to use the shell's
|
229 |
+
active profile.
|
230 |
+
|
231 |
+
Parameters
|
232 |
+
----------
|
233 |
+
profile : str
|
234 |
+
The name of a profile which has a history file.
|
235 |
+
"""
|
236 |
+
return Path(locate_profile(profile)) / "history.sqlite"
|
237 |
+
|
238 |
+
@catch_corrupt_db
|
239 |
+
def init_db(self):
|
240 |
+
"""Connect to the database, and create tables if necessary."""
|
241 |
+
if not self.enabled:
|
242 |
+
self.db = DummyDB()
|
243 |
+
return
|
244 |
+
|
245 |
+
# use detect_types so that timestamps return datetime objects
|
246 |
+
kwargs = dict(detect_types=sqlite3.PARSE_DECLTYPES|sqlite3.PARSE_COLNAMES)
|
247 |
+
kwargs.update(self.connection_options)
|
248 |
+
self.db = sqlite3.connect(str(self.hist_file), **kwargs)
|
249 |
+
with self.db:
|
250 |
+
self.db.execute(
|
251 |
+
"""CREATE TABLE IF NOT EXISTS sessions (session integer
|
252 |
+
primary key autoincrement, start timestamp,
|
253 |
+
end timestamp, num_cmds integer, remark text)"""
|
254 |
+
)
|
255 |
+
self.db.execute(
|
256 |
+
"""CREATE TABLE IF NOT EXISTS history
|
257 |
+
(session integer, line integer, source text, source_raw text,
|
258 |
+
PRIMARY KEY (session, line))"""
|
259 |
+
)
|
260 |
+
# Output history is optional, but ensure the table's there so it can be
|
261 |
+
# enabled later.
|
262 |
+
self.db.execute(
|
263 |
+
"""CREATE TABLE IF NOT EXISTS output_history
|
264 |
+
(session integer, line integer, output text,
|
265 |
+
PRIMARY KEY (session, line))"""
|
266 |
+
)
|
267 |
+
# success! reset corrupt db count
|
268 |
+
self._corrupt_db_counter = 0
|
269 |
+
|
270 |
+
def writeout_cache(self):
|
271 |
+
"""Overridden by HistoryManager to dump the cache before certain
|
272 |
+
database lookups."""
|
273 |
+
pass
|
274 |
+
|
275 |
+
## -------------------------------
|
276 |
+
## Methods for retrieving history:
|
277 |
+
## -------------------------------
|
278 |
+
def _run_sql(self, sql, params, raw=True, output=False, latest=False):
|
279 |
+
"""Prepares and runs an SQL query for the history database.
|
280 |
+
|
281 |
+
Parameters
|
282 |
+
----------
|
283 |
+
sql : str
|
284 |
+
Any filtering expressions to go after SELECT ... FROM ...
|
285 |
+
params : tuple
|
286 |
+
Parameters passed to the SQL query (to replace "?")
|
287 |
+
raw, output : bool
|
288 |
+
See :meth:`get_range`
|
289 |
+
latest : bool
|
290 |
+
Select rows with max (session, line)
|
291 |
+
|
292 |
+
Returns
|
293 |
+
-------
|
294 |
+
Tuples as :meth:`get_range`
|
295 |
+
"""
|
296 |
+
toget = 'source_raw' if raw else 'source'
|
297 |
+
sqlfrom = "history"
|
298 |
+
if output:
|
299 |
+
sqlfrom = "history LEFT JOIN output_history USING (session, line)"
|
300 |
+
toget = "history.%s, output_history.output" % toget
|
301 |
+
if latest:
|
302 |
+
toget += ", MAX(session * 128 * 1024 + line)"
|
303 |
+
this_querry = "SELECT session, line, %s FROM %s " % (toget, sqlfrom) + sql
|
304 |
+
cur = self.db.execute(this_querry, params)
|
305 |
+
if latest:
|
306 |
+
cur = (row[:-1] for row in cur)
|
307 |
+
if output: # Regroup into 3-tuples, and parse JSON
|
308 |
+
return ((ses, lin, (inp, out)) for ses, lin, inp, out in cur)
|
309 |
+
return cur
|
310 |
+
|
311 |
+
@only_when_enabled
|
312 |
+
@catch_corrupt_db
|
313 |
+
def get_session_info(self, session):
|
314 |
+
"""Get info about a session.
|
315 |
+
|
316 |
+
Parameters
|
317 |
+
----------
|
318 |
+
session : int
|
319 |
+
Session number to retrieve.
|
320 |
+
|
321 |
+
Returns
|
322 |
+
-------
|
323 |
+
session_id : int
|
324 |
+
Session ID number
|
325 |
+
start : datetime
|
326 |
+
Timestamp for the start of the session.
|
327 |
+
end : datetime
|
328 |
+
Timestamp for the end of the session, or None if IPython crashed.
|
329 |
+
num_cmds : int
|
330 |
+
Number of commands run, or None if IPython crashed.
|
331 |
+
remark : unicode
|
332 |
+
A manually set description.
|
333 |
+
"""
|
334 |
+
query = "SELECT * from sessions where session == ?"
|
335 |
+
return self.db.execute(query, (session,)).fetchone()
|
336 |
+
|
337 |
+
@catch_corrupt_db
|
338 |
+
def get_last_session_id(self):
|
339 |
+
"""Get the last session ID currently in the database.
|
340 |
+
|
341 |
+
Within IPython, this should be the same as the value stored in
|
342 |
+
:attr:`HistoryManager.session_number`.
|
343 |
+
"""
|
344 |
+
for record in self.get_tail(n=1, include_latest=True):
|
345 |
+
return record[0]
|
346 |
+
|
347 |
+
@catch_corrupt_db
|
348 |
+
def get_tail(self, n=10, raw=True, output=False, include_latest=False):
|
349 |
+
"""Get the last n lines from the history database.
|
350 |
+
|
351 |
+
Parameters
|
352 |
+
----------
|
353 |
+
n : int
|
354 |
+
The number of lines to get
|
355 |
+
raw, output : bool
|
356 |
+
See :meth:`get_range`
|
357 |
+
include_latest : bool
|
358 |
+
If False (default), n+1 lines are fetched, and the latest one
|
359 |
+
is discarded. This is intended to be used where the function
|
360 |
+
is called by a user command, which it should not return.
|
361 |
+
|
362 |
+
Returns
|
363 |
+
-------
|
364 |
+
Tuples as :meth:`get_range`
|
365 |
+
"""
|
366 |
+
self.writeout_cache()
|
367 |
+
if not include_latest:
|
368 |
+
n += 1
|
369 |
+
cur = self._run_sql(
|
370 |
+
"ORDER BY session DESC, line DESC LIMIT ?", (n,), raw=raw, output=output
|
371 |
+
)
|
372 |
+
if not include_latest:
|
373 |
+
return reversed(list(cur)[1:])
|
374 |
+
return reversed(list(cur))
|
375 |
+
|
376 |
+
@catch_corrupt_db
|
377 |
+
def search(self, pattern="*", raw=True, search_raw=True,
|
378 |
+
output=False, n=None, unique=False):
|
379 |
+
"""Search the database using unix glob-style matching (wildcards
|
380 |
+
* and ?).
|
381 |
+
|
382 |
+
Parameters
|
383 |
+
----------
|
384 |
+
pattern : str
|
385 |
+
The wildcarded pattern to match when searching
|
386 |
+
search_raw : bool
|
387 |
+
If True, search the raw input, otherwise, the parsed input
|
388 |
+
raw, output : bool
|
389 |
+
See :meth:`get_range`
|
390 |
+
n : None or int
|
391 |
+
If an integer is given, it defines the limit of
|
392 |
+
returned entries.
|
393 |
+
unique : bool
|
394 |
+
When it is true, return only unique entries.
|
395 |
+
|
396 |
+
Returns
|
397 |
+
-------
|
398 |
+
Tuples as :meth:`get_range`
|
399 |
+
"""
|
400 |
+
tosearch = "source_raw" if search_raw else "source"
|
401 |
+
if output:
|
402 |
+
tosearch = "history." + tosearch
|
403 |
+
self.writeout_cache()
|
404 |
+
sqlform = "WHERE %s GLOB ?" % tosearch
|
405 |
+
params = (pattern,)
|
406 |
+
if unique:
|
407 |
+
sqlform += ' GROUP BY {0}'.format(tosearch)
|
408 |
+
if n is not None:
|
409 |
+
sqlform += " ORDER BY session DESC, line DESC LIMIT ?"
|
410 |
+
params += (n,)
|
411 |
+
elif unique:
|
412 |
+
sqlform += " ORDER BY session, line"
|
413 |
+
cur = self._run_sql(sqlform, params, raw=raw, output=output, latest=unique)
|
414 |
+
if n is not None:
|
415 |
+
return reversed(list(cur))
|
416 |
+
return cur
|
417 |
+
|
418 |
+
@catch_corrupt_db
|
419 |
+
def get_range(self, session, start=1, stop=None, raw=True,output=False):
|
420 |
+
"""Retrieve input by session.
|
421 |
+
|
422 |
+
Parameters
|
423 |
+
----------
|
424 |
+
session : int
|
425 |
+
Session number to retrieve.
|
426 |
+
start : int
|
427 |
+
First line to retrieve.
|
428 |
+
stop : int
|
429 |
+
End of line range (excluded from output itself). If None, retrieve
|
430 |
+
to the end of the session.
|
431 |
+
raw : bool
|
432 |
+
If True, return untranslated input
|
433 |
+
output : bool
|
434 |
+
If True, attempt to include output. This will be 'real' Python
|
435 |
+
objects for the current session, or text reprs from previous
|
436 |
+
sessions if db_log_output was enabled at the time. Where no output
|
437 |
+
is found, None is used.
|
438 |
+
|
439 |
+
Returns
|
440 |
+
-------
|
441 |
+
entries
|
442 |
+
An iterator over the desired lines. Each line is a 3-tuple, either
|
443 |
+
(session, line, input) if output is False, or
|
444 |
+
(session, line, (input, output)) if output is True.
|
445 |
+
"""
|
446 |
+
if stop:
|
447 |
+
lineclause = "line >= ? AND line < ?"
|
448 |
+
params = (session, start, stop)
|
449 |
+
else:
|
450 |
+
lineclause = "line>=?"
|
451 |
+
params = (session, start)
|
452 |
+
|
453 |
+
return self._run_sql("WHERE session==? AND %s" % lineclause,
|
454 |
+
params, raw=raw, output=output)
|
455 |
+
|
456 |
+
def get_range_by_str(self, rangestr, raw=True, output=False):
|
457 |
+
"""Get lines of history from a string of ranges, as used by magic
|
458 |
+
commands %hist, %save, %macro, etc.
|
459 |
+
|
460 |
+
Parameters
|
461 |
+
----------
|
462 |
+
rangestr : str
|
463 |
+
A string specifying ranges, e.g. "5 ~2/1-4". If empty string is used,
|
464 |
+
this will return everything from current session's history.
|
465 |
+
|
466 |
+
See the documentation of :func:`%history` for the full details.
|
467 |
+
|
468 |
+
raw, output : bool
|
469 |
+
As :meth:`get_range`
|
470 |
+
|
471 |
+
Returns
|
472 |
+
-------
|
473 |
+
Tuples as :meth:`get_range`
|
474 |
+
"""
|
475 |
+
for sess, s, e in extract_hist_ranges(rangestr):
|
476 |
+
for line in self.get_range(sess, s, e, raw=raw, output=output):
|
477 |
+
yield line
|
478 |
+
|
479 |
+
|
480 |
+
class HistoryManager(HistoryAccessor):
|
481 |
+
"""A class to organize all history-related functionality in one place.
|
482 |
+
"""
|
483 |
+
# Public interface
|
484 |
+
|
485 |
+
# An instance of the IPython shell we are attached to
|
486 |
+
shell = Instance('IPython.core.interactiveshell.InteractiveShellABC',
|
487 |
+
allow_none=True)
|
488 |
+
# Lists to hold processed and raw history. These start with a blank entry
|
489 |
+
# so that we can index them starting from 1
|
490 |
+
input_hist_parsed = List([""])
|
491 |
+
input_hist_raw = List([""])
|
492 |
+
# A list of directories visited during session
|
493 |
+
dir_hist: List = List()
|
494 |
+
|
495 |
+
@default("dir_hist")
|
496 |
+
def _dir_hist_default(self):
|
497 |
+
try:
|
498 |
+
return [Path.cwd()]
|
499 |
+
except OSError:
|
500 |
+
return []
|
501 |
+
|
502 |
+
# A dict of output history, keyed with ints from the shell's
|
503 |
+
# execution count.
|
504 |
+
output_hist = Dict()
|
505 |
+
# The text/plain repr of outputs.
|
506 |
+
output_hist_reprs = Dict()
|
507 |
+
|
508 |
+
# The number of the current session in the history database
|
509 |
+
session_number = Integer()
|
510 |
+
|
511 |
+
db_log_output = Bool(False,
|
512 |
+
help="Should the history database include output? (default: no)"
|
513 |
+
).tag(config=True)
|
514 |
+
db_cache_size = Integer(0,
|
515 |
+
help="Write to database every x commands (higher values save disk access & power).\n"
|
516 |
+
"Values of 1 or less effectively disable caching."
|
517 |
+
).tag(config=True)
|
518 |
+
# The input and output caches
|
519 |
+
db_input_cache: List = List()
|
520 |
+
db_output_cache: List = List()
|
521 |
+
|
522 |
+
# History saving in separate thread
|
523 |
+
save_thread = Instance('IPython.core.history.HistorySavingThread',
|
524 |
+
allow_none=True)
|
525 |
+
save_flag = Instance(threading.Event, allow_none=True)
|
526 |
+
|
527 |
+
# Private interface
|
528 |
+
# Variables used to store the three last inputs from the user. On each new
|
529 |
+
# history update, we populate the user's namespace with these, shifted as
|
530 |
+
# necessary.
|
531 |
+
_i00 = Unicode("")
|
532 |
+
_i = Unicode("")
|
533 |
+
_ii = Unicode("")
|
534 |
+
_iii = Unicode("")
|
535 |
+
|
536 |
+
# A regex matching all forms of the exit command, so that we don't store
|
537 |
+
# them in the history (it's annoying to rewind the first entry and land on
|
538 |
+
# an exit call).
|
539 |
+
_exit_re = re.compile(r"(exit|quit)(\s*\(.*\))?$")
|
540 |
+
|
541 |
+
def __init__(self, shell=None, config=None, **traits):
|
542 |
+
"""Create a new history manager associated with a shell instance.
|
543 |
+
"""
|
544 |
+
super(HistoryManager, self).__init__(shell=shell, config=config,
|
545 |
+
**traits)
|
546 |
+
self.save_flag = threading.Event()
|
547 |
+
self.db_input_cache_lock = threading.Lock()
|
548 |
+
self.db_output_cache_lock = threading.Lock()
|
549 |
+
|
550 |
+
try:
|
551 |
+
self.new_session()
|
552 |
+
except sqlite3.OperationalError:
|
553 |
+
self.log.error("Failed to create history session in %s. History will not be saved.",
|
554 |
+
self.hist_file, exc_info=True)
|
555 |
+
self.hist_file = ':memory:'
|
556 |
+
|
557 |
+
if self.enabled and self.hist_file != ':memory:':
|
558 |
+
self.save_thread = HistorySavingThread(self)
|
559 |
+
try:
|
560 |
+
self.save_thread.start()
|
561 |
+
except RuntimeError:
|
562 |
+
self.log.error(
|
563 |
+
"Failed to start history saving thread. History will not be saved.",
|
564 |
+
exc_info=True,
|
565 |
+
)
|
566 |
+
self.hist_file = ":memory:"
|
567 |
+
|
568 |
+
def _get_hist_file_name(self, profile=None):
|
569 |
+
"""Get default history file name based on the Shell's profile.
|
570 |
+
|
571 |
+
The profile parameter is ignored, but must exist for compatibility with
|
572 |
+
the parent class."""
|
573 |
+
profile_dir = self.shell.profile_dir.location
|
574 |
+
return Path(profile_dir) / "history.sqlite"
|
575 |
+
|
576 |
+
@only_when_enabled
|
577 |
+
def new_session(self, conn=None):
|
578 |
+
"""Get a new session number."""
|
579 |
+
if conn is None:
|
580 |
+
conn = self.db
|
581 |
+
|
582 |
+
with conn:
|
583 |
+
cur = conn.execute(
|
584 |
+
"""INSERT INTO sessions VALUES (NULL, ?, NULL,
|
585 |
+
NULL, '') """,
|
586 |
+
(datetime.datetime.now().isoformat(" "),),
|
587 |
+
)
|
588 |
+
self.session_number = cur.lastrowid
|
589 |
+
|
590 |
+
def end_session(self):
|
591 |
+
"""Close the database session, filling in the end time and line count."""
|
592 |
+
self.writeout_cache()
|
593 |
+
with self.db:
|
594 |
+
self.db.execute(
|
595 |
+
"""UPDATE sessions SET end=?, num_cmds=? WHERE
|
596 |
+
session==?""",
|
597 |
+
(
|
598 |
+
datetime.datetime.now().isoformat(" "),
|
599 |
+
len(self.input_hist_parsed) - 1,
|
600 |
+
self.session_number,
|
601 |
+
),
|
602 |
+
)
|
603 |
+
self.session_number = 0
|
604 |
+
|
605 |
+
def name_session(self, name):
|
606 |
+
"""Give the current session a name in the history database."""
|
607 |
+
with self.db:
|
608 |
+
self.db.execute("UPDATE sessions SET remark=? WHERE session==?",
|
609 |
+
(name, self.session_number))
|
610 |
+
|
611 |
+
def reset(self, new_session=True):
|
612 |
+
"""Clear the session history, releasing all object references, and
|
613 |
+
optionally open a new session."""
|
614 |
+
self.output_hist.clear()
|
615 |
+
# The directory history can't be completely empty
|
616 |
+
self.dir_hist[:] = [Path.cwd()]
|
617 |
+
|
618 |
+
if new_session:
|
619 |
+
if self.session_number:
|
620 |
+
self.end_session()
|
621 |
+
self.input_hist_parsed[:] = [""]
|
622 |
+
self.input_hist_raw[:] = [""]
|
623 |
+
self.new_session()
|
624 |
+
|
625 |
+
# ------------------------------
|
626 |
+
# Methods for retrieving history
|
627 |
+
# ------------------------------
|
628 |
+
def get_session_info(self, session=0):
|
629 |
+
"""Get info about a session.
|
630 |
+
|
631 |
+
Parameters
|
632 |
+
----------
|
633 |
+
session : int
|
634 |
+
Session number to retrieve. The current session is 0, and negative
|
635 |
+
numbers count back from current session, so -1 is the previous session.
|
636 |
+
|
637 |
+
Returns
|
638 |
+
-------
|
639 |
+
session_id : int
|
640 |
+
Session ID number
|
641 |
+
start : datetime
|
642 |
+
Timestamp for the start of the session.
|
643 |
+
end : datetime
|
644 |
+
Timestamp for the end of the session, or None if IPython crashed.
|
645 |
+
num_cmds : int
|
646 |
+
Number of commands run, or None if IPython crashed.
|
647 |
+
remark : unicode
|
648 |
+
A manually set description.
|
649 |
+
"""
|
650 |
+
if session <= 0:
|
651 |
+
session += self.session_number
|
652 |
+
|
653 |
+
return super(HistoryManager, self).get_session_info(session=session)
|
654 |
+
|
655 |
+
@catch_corrupt_db
|
656 |
+
def get_tail(self, n=10, raw=True, output=False, include_latest=False):
|
657 |
+
"""Get the last n lines from the history database.
|
658 |
+
|
659 |
+
Most recent entry last.
|
660 |
+
|
661 |
+
Completion will be reordered so that that the last ones are when
|
662 |
+
possible from current session.
|
663 |
+
|
664 |
+
Parameters
|
665 |
+
----------
|
666 |
+
n : int
|
667 |
+
The number of lines to get
|
668 |
+
raw, output : bool
|
669 |
+
See :meth:`get_range`
|
670 |
+
include_latest : bool
|
671 |
+
If False (default), n+1 lines are fetched, and the latest one
|
672 |
+
is discarded. This is intended to be used where the function
|
673 |
+
is called by a user command, which it should not return.
|
674 |
+
|
675 |
+
Returns
|
676 |
+
-------
|
677 |
+
Tuples as :meth:`get_range`
|
678 |
+
"""
|
679 |
+
self.writeout_cache()
|
680 |
+
if not include_latest:
|
681 |
+
n += 1
|
682 |
+
# cursor/line/entry
|
683 |
+
this_cur = list(
|
684 |
+
self._run_sql(
|
685 |
+
"WHERE session == ? ORDER BY line DESC LIMIT ? ",
|
686 |
+
(self.session_number, n),
|
687 |
+
raw=raw,
|
688 |
+
output=output,
|
689 |
+
)
|
690 |
+
)
|
691 |
+
other_cur = list(
|
692 |
+
self._run_sql(
|
693 |
+
"WHERE session != ? ORDER BY session DESC, line DESC LIMIT ?",
|
694 |
+
(self.session_number, n),
|
695 |
+
raw=raw,
|
696 |
+
output=output,
|
697 |
+
)
|
698 |
+
)
|
699 |
+
|
700 |
+
everything = this_cur + other_cur
|
701 |
+
|
702 |
+
everything = everything[:n]
|
703 |
+
|
704 |
+
if not include_latest:
|
705 |
+
return list(everything)[:0:-1]
|
706 |
+
return list(everything)[::-1]
|
707 |
+
|
708 |
+
def _get_range_session(self, start=1, stop=None, raw=True, output=False):
|
709 |
+
"""Get input and output history from the current session. Called by
|
710 |
+
get_range, and takes similar parameters."""
|
711 |
+
input_hist = self.input_hist_raw if raw else self.input_hist_parsed
|
712 |
+
|
713 |
+
n = len(input_hist)
|
714 |
+
if start < 0:
|
715 |
+
start += n
|
716 |
+
if not stop or (stop > n):
|
717 |
+
stop = n
|
718 |
+
elif stop < 0:
|
719 |
+
stop += n
|
720 |
+
|
721 |
+
for i in range(start, stop):
|
722 |
+
if output:
|
723 |
+
line = (input_hist[i], self.output_hist_reprs.get(i))
|
724 |
+
else:
|
725 |
+
line = input_hist[i]
|
726 |
+
yield (0, i, line)
|
727 |
+
|
728 |
+
def get_range(self, session=0, start=1, stop=None, raw=True,output=False):
|
729 |
+
"""Retrieve input by session.
|
730 |
+
|
731 |
+
Parameters
|
732 |
+
----------
|
733 |
+
session : int
|
734 |
+
Session number to retrieve. The current session is 0, and negative
|
735 |
+
numbers count back from current session, so -1 is previous session.
|
736 |
+
start : int
|
737 |
+
First line to retrieve.
|
738 |
+
stop : int
|
739 |
+
End of line range (excluded from output itself). If None, retrieve
|
740 |
+
to the end of the session.
|
741 |
+
raw : bool
|
742 |
+
If True, return untranslated input
|
743 |
+
output : bool
|
744 |
+
If True, attempt to include output. This will be 'real' Python
|
745 |
+
objects for the current session, or text reprs from previous
|
746 |
+
sessions if db_log_output was enabled at the time. Where no output
|
747 |
+
is found, None is used.
|
748 |
+
|
749 |
+
Returns
|
750 |
+
-------
|
751 |
+
entries
|
752 |
+
An iterator over the desired lines. Each line is a 3-tuple, either
|
753 |
+
(session, line, input) if output is False, or
|
754 |
+
(session, line, (input, output)) if output is True.
|
755 |
+
"""
|
756 |
+
if session <= 0:
|
757 |
+
session += self.session_number
|
758 |
+
if session==self.session_number: # Current session
|
759 |
+
return self._get_range_session(start, stop, raw, output)
|
760 |
+
return super(HistoryManager, self).get_range(session, start, stop, raw,
|
761 |
+
output)
|
762 |
+
|
763 |
+
## ----------------------------
|
764 |
+
## Methods for storing history:
|
765 |
+
## ----------------------------
|
766 |
+
def store_inputs(self, line_num, source, source_raw=None):
|
767 |
+
"""Store source and raw input in history and create input cache
|
768 |
+
variables ``_i*``.
|
769 |
+
|
770 |
+
Parameters
|
771 |
+
----------
|
772 |
+
line_num : int
|
773 |
+
The prompt number of this input.
|
774 |
+
source : str
|
775 |
+
Python input.
|
776 |
+
source_raw : str, optional
|
777 |
+
If given, this is the raw input without any IPython transformations
|
778 |
+
applied to it. If not given, ``source`` is used.
|
779 |
+
"""
|
780 |
+
if source_raw is None:
|
781 |
+
source_raw = source
|
782 |
+
source = source.rstrip('\n')
|
783 |
+
source_raw = source_raw.rstrip('\n')
|
784 |
+
|
785 |
+
# do not store exit/quit commands
|
786 |
+
if self._exit_re.match(source_raw.strip()):
|
787 |
+
return
|
788 |
+
|
789 |
+
self.input_hist_parsed.append(source)
|
790 |
+
self.input_hist_raw.append(source_raw)
|
791 |
+
|
792 |
+
with self.db_input_cache_lock:
|
793 |
+
self.db_input_cache.append((line_num, source, source_raw))
|
794 |
+
# Trigger to flush cache and write to DB.
|
795 |
+
if len(self.db_input_cache) >= self.db_cache_size:
|
796 |
+
self.save_flag.set()
|
797 |
+
|
798 |
+
# update the auto _i variables
|
799 |
+
self._iii = self._ii
|
800 |
+
self._ii = self._i
|
801 |
+
self._i = self._i00
|
802 |
+
self._i00 = source_raw
|
803 |
+
|
804 |
+
# hackish access to user namespace to create _i1,_i2... dynamically
|
805 |
+
new_i = '_i%s' % line_num
|
806 |
+
to_main = {'_i': self._i,
|
807 |
+
'_ii': self._ii,
|
808 |
+
'_iii': self._iii,
|
809 |
+
new_i : self._i00 }
|
810 |
+
|
811 |
+
if self.shell is not None:
|
812 |
+
self.shell.push(to_main, interactive=False)
|
813 |
+
|
814 |
+
def store_output(self, line_num):
|
815 |
+
"""If database output logging is enabled, this saves all the
|
816 |
+
outputs from the indicated prompt number to the database. It's
|
817 |
+
called by run_cell after code has been executed.
|
818 |
+
|
819 |
+
Parameters
|
820 |
+
----------
|
821 |
+
line_num : int
|
822 |
+
The line number from which to save outputs
|
823 |
+
"""
|
824 |
+
if (not self.db_log_output) or (line_num not in self.output_hist_reprs):
|
825 |
+
return
|
826 |
+
output = self.output_hist_reprs[line_num]
|
827 |
+
|
828 |
+
with self.db_output_cache_lock:
|
829 |
+
self.db_output_cache.append((line_num, output))
|
830 |
+
if self.db_cache_size <= 1:
|
831 |
+
self.save_flag.set()
|
832 |
+
|
833 |
+
def _writeout_input_cache(self, conn):
|
834 |
+
with conn:
|
835 |
+
for line in self.db_input_cache:
|
836 |
+
conn.execute("INSERT INTO history VALUES (?, ?, ?, ?)",
|
837 |
+
(self.session_number,)+line)
|
838 |
+
|
839 |
+
def _writeout_output_cache(self, conn):
|
840 |
+
with conn:
|
841 |
+
for line in self.db_output_cache:
|
842 |
+
conn.execute("INSERT INTO output_history VALUES (?, ?, ?)",
|
843 |
+
(self.session_number,)+line)
|
844 |
+
|
845 |
+
@only_when_enabled
|
846 |
+
def writeout_cache(self, conn=None):
|
847 |
+
"""Write any entries in the cache to the database."""
|
848 |
+
if conn is None:
|
849 |
+
conn = self.db
|
850 |
+
|
851 |
+
with self.db_input_cache_lock:
|
852 |
+
try:
|
853 |
+
self._writeout_input_cache(conn)
|
854 |
+
except sqlite3.IntegrityError:
|
855 |
+
self.new_session(conn)
|
856 |
+
print("ERROR! Session/line number was not unique in",
|
857 |
+
"database. History logging moved to new session",
|
858 |
+
self.session_number)
|
859 |
+
try:
|
860 |
+
# Try writing to the new session. If this fails, don't
|
861 |
+
# recurse
|
862 |
+
self._writeout_input_cache(conn)
|
863 |
+
except sqlite3.IntegrityError:
|
864 |
+
pass
|
865 |
+
finally:
|
866 |
+
self.db_input_cache = []
|
867 |
+
|
868 |
+
with self.db_output_cache_lock:
|
869 |
+
try:
|
870 |
+
self._writeout_output_cache(conn)
|
871 |
+
except sqlite3.IntegrityError:
|
872 |
+
print("!! Session/line number for output was not unique",
|
873 |
+
"in database. Output will not be stored.")
|
874 |
+
finally:
|
875 |
+
self.db_output_cache = []
|
876 |
+
|
877 |
+
|
878 |
+
class HistorySavingThread(threading.Thread):
|
879 |
+
"""This thread takes care of writing history to the database, so that
|
880 |
+
the UI isn't held up while that happens.
|
881 |
+
|
882 |
+
It waits for the HistoryManager's save_flag to be set, then writes out
|
883 |
+
the history cache. The main thread is responsible for setting the flag when
|
884 |
+
the cache size reaches a defined threshold."""
|
885 |
+
daemon = True
|
886 |
+
stop_now = False
|
887 |
+
enabled = True
|
888 |
+
def __init__(self, history_manager):
|
889 |
+
super(HistorySavingThread, self).__init__(name="IPythonHistorySavingThread")
|
890 |
+
self.history_manager = history_manager
|
891 |
+
self.enabled = history_manager.enabled
|
892 |
+
|
893 |
+
@only_when_enabled
|
894 |
+
def run(self):
|
895 |
+
atexit.register(self.stop)
|
896 |
+
# We need a separate db connection per thread:
|
897 |
+
try:
|
898 |
+
self.db = sqlite3.connect(
|
899 |
+
str(self.history_manager.hist_file),
|
900 |
+
**self.history_manager.connection_options,
|
901 |
+
)
|
902 |
+
while True:
|
903 |
+
self.history_manager.save_flag.wait()
|
904 |
+
if self.stop_now:
|
905 |
+
self.db.close()
|
906 |
+
return
|
907 |
+
self.history_manager.save_flag.clear()
|
908 |
+
self.history_manager.writeout_cache(self.db)
|
909 |
+
except Exception as e:
|
910 |
+
print(("The history saving thread hit an unexpected error (%s)."
|
911 |
+
"History will not be written to the database.") % repr(e))
|
912 |
+
finally:
|
913 |
+
atexit.unregister(self.stop)
|
914 |
+
|
915 |
+
def stop(self):
|
916 |
+
"""This can be called from the main thread to safely stop this thread.
|
917 |
+
|
918 |
+
Note that it does not attempt to write out remaining history before
|
919 |
+
exiting. That should be done by calling the HistoryManager's
|
920 |
+
end_session method."""
|
921 |
+
self.stop_now = True
|
922 |
+
self.history_manager.save_flag.set()
|
923 |
+
self.join()
|
924 |
+
|
925 |
+
|
926 |
+
# To match, e.g. ~5/8-~2/3
|
927 |
+
range_re = re.compile(r"""
|
928 |
+
((?P<startsess>~?\d+)/)?
|
929 |
+
(?P<start>\d+)?
|
930 |
+
((?P<sep>[\-:])
|
931 |
+
((?P<endsess>~?\d+)/)?
|
932 |
+
(?P<end>\d+))?
|
933 |
+
$""", re.VERBOSE)
|
934 |
+
|
935 |
+
|
936 |
+
def extract_hist_ranges(ranges_str):
|
937 |
+
"""Turn a string of history ranges into 3-tuples of (session, start, stop).
|
938 |
+
|
939 |
+
Empty string results in a `[(0, 1, None)]`, i.e. "everything from current
|
940 |
+
session".
|
941 |
+
|
942 |
+
Examples
|
943 |
+
--------
|
944 |
+
>>> list(extract_hist_ranges("~8/5-~7/4 2"))
|
945 |
+
[(-8, 5, None), (-7, 1, 5), (0, 2, 3)]
|
946 |
+
"""
|
947 |
+
if ranges_str == "":
|
948 |
+
yield (0, 1, None) # Everything from current session
|
949 |
+
return
|
950 |
+
|
951 |
+
for range_str in ranges_str.split():
|
952 |
+
rmatch = range_re.match(range_str)
|
953 |
+
if not rmatch:
|
954 |
+
continue
|
955 |
+
start = rmatch.group("start")
|
956 |
+
if start:
|
957 |
+
start = int(start)
|
958 |
+
end = rmatch.group("end")
|
959 |
+
# If no end specified, get (a, a + 1)
|
960 |
+
end = int(end) if end else start + 1
|
961 |
+
else: # start not specified
|
962 |
+
if not rmatch.group('startsess'): # no startsess
|
963 |
+
continue
|
964 |
+
start = 1
|
965 |
+
end = None # provide the entire session hist
|
966 |
+
|
967 |
+
if rmatch.group("sep") == "-": # 1-3 == 1:4 --> [1, 2, 3]
|
968 |
+
end += 1
|
969 |
+
startsess = rmatch.group("startsess") or "0"
|
970 |
+
endsess = rmatch.group("endsess") or startsess
|
971 |
+
startsess = int(startsess.replace("~","-"))
|
972 |
+
endsess = int(endsess.replace("~","-"))
|
973 |
+
assert endsess >= startsess, "start session must be earlier than end session"
|
974 |
+
|
975 |
+
if endsess == startsess:
|
976 |
+
yield (startsess, start, end)
|
977 |
+
continue
|
978 |
+
# Multiple sessions in one range:
|
979 |
+
yield (startsess, start, None)
|
980 |
+
for sess in range(startsess+1, endsess):
|
981 |
+
yield (sess, 1, None)
|
982 |
+
yield (endsess, 1, end)
|
983 |
+
|
984 |
+
|
985 |
+
def _format_lineno(session, line):
|
986 |
+
"""Helper function to format line numbers properly."""
|
987 |
+
if session == 0:
|
988 |
+
return str(line)
|
989 |
+
return "%s#%s" % (session, line)
|
openalex_env_map/lib/python3.10/site-packages/IPython/core/historyapp.py
ADDED
@@ -0,0 +1,158 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# encoding: utf-8
|
2 |
+
"""
|
3 |
+
An application for managing IPython history.
|
4 |
+
|
5 |
+
To be invoked as the `ipython history` subcommand.
|
6 |
+
"""
|
7 |
+
|
8 |
+
import sqlite3
|
9 |
+
from pathlib import Path
|
10 |
+
|
11 |
+
from traitlets.config.application import Application
|
12 |
+
from .application import BaseIPythonApplication
|
13 |
+
from traitlets import Bool, Int, Dict
|
14 |
+
from ..utils.io import ask_yes_no
|
15 |
+
|
16 |
+
trim_hist_help = """Trim the IPython history database to the last 1000 entries.
|
17 |
+
|
18 |
+
This actually copies the last 1000 entries to a new database, and then replaces
|
19 |
+
the old file with the new. Use the `--keep=` argument to specify a number
|
20 |
+
other than 1000.
|
21 |
+
"""
|
22 |
+
|
23 |
+
clear_hist_help = """Clear the IPython history database, deleting all entries.
|
24 |
+
|
25 |
+
Because this is a destructive operation, IPython will prompt the user if they
|
26 |
+
really want to do this. Passing a `-f` flag will force clearing without a
|
27 |
+
prompt.
|
28 |
+
|
29 |
+
This is an handy alias to `ipython history trim --keep=0`
|
30 |
+
"""
|
31 |
+
|
32 |
+
|
33 |
+
class HistoryTrim(BaseIPythonApplication):
|
34 |
+
description = trim_hist_help
|
35 |
+
|
36 |
+
backup = Bool(False, help="Keep the old history file as history.sqlite.<N>").tag(
|
37 |
+
config=True
|
38 |
+
)
|
39 |
+
|
40 |
+
keep = Int(1000, help="Number of recent lines to keep in the database.").tag(
|
41 |
+
config=True
|
42 |
+
)
|
43 |
+
|
44 |
+
flags = Dict( # type: ignore
|
45 |
+
dict(backup=({"HistoryTrim": {"backup": True}}, backup.help))
|
46 |
+
)
|
47 |
+
|
48 |
+
aliases = Dict(dict(keep="HistoryTrim.keep")) # type: ignore
|
49 |
+
|
50 |
+
def start(self):
|
51 |
+
profile_dir = Path(self.profile_dir.location)
|
52 |
+
hist_file = profile_dir / "history.sqlite"
|
53 |
+
con = sqlite3.connect(hist_file)
|
54 |
+
|
55 |
+
# Grab the recent history from the current database.
|
56 |
+
inputs = list(con.execute('SELECT session, line, source, source_raw FROM '
|
57 |
+
'history ORDER BY session DESC, line DESC LIMIT ?', (self.keep+1,)))
|
58 |
+
if len(inputs) <= self.keep:
|
59 |
+
print("There are already at most %d entries in the history database." % self.keep)
|
60 |
+
print("Not doing anything. Use --keep= argument to keep fewer entries")
|
61 |
+
return
|
62 |
+
|
63 |
+
print("Trimming history to the most recent %d entries." % self.keep)
|
64 |
+
|
65 |
+
inputs.pop() # Remove the extra element we got to check the length.
|
66 |
+
inputs.reverse()
|
67 |
+
if inputs:
|
68 |
+
first_session = inputs[0][0]
|
69 |
+
outputs = list(con.execute('SELECT session, line, output FROM '
|
70 |
+
'output_history WHERE session >= ?', (first_session,)))
|
71 |
+
sessions = list(con.execute('SELECT session, start, end, num_cmds, remark FROM '
|
72 |
+
'sessions WHERE session >= ?', (first_session,)))
|
73 |
+
con.close()
|
74 |
+
|
75 |
+
# Create the new history database.
|
76 |
+
new_hist_file = profile_dir / "history.sqlite.new"
|
77 |
+
i = 0
|
78 |
+
while new_hist_file.exists():
|
79 |
+
# Make sure we don't interfere with an existing file.
|
80 |
+
i += 1
|
81 |
+
new_hist_file = profile_dir / ("history.sqlite.new" + str(i))
|
82 |
+
new_db = sqlite3.connect(new_hist_file)
|
83 |
+
new_db.execute("""CREATE TABLE IF NOT EXISTS sessions (session integer
|
84 |
+
primary key autoincrement, start timestamp,
|
85 |
+
end timestamp, num_cmds integer, remark text)""")
|
86 |
+
new_db.execute("""CREATE TABLE IF NOT EXISTS history
|
87 |
+
(session integer, line integer, source text, source_raw text,
|
88 |
+
PRIMARY KEY (session, line))""")
|
89 |
+
new_db.execute("""CREATE TABLE IF NOT EXISTS output_history
|
90 |
+
(session integer, line integer, output text,
|
91 |
+
PRIMARY KEY (session, line))""")
|
92 |
+
new_db.commit()
|
93 |
+
|
94 |
+
|
95 |
+
if inputs:
|
96 |
+
with new_db:
|
97 |
+
# Add the recent history into the new database.
|
98 |
+
new_db.executemany('insert into sessions values (?,?,?,?,?)', sessions)
|
99 |
+
new_db.executemany('insert into history values (?,?,?,?)', inputs)
|
100 |
+
new_db.executemany('insert into output_history values (?,?,?)', outputs)
|
101 |
+
new_db.close()
|
102 |
+
|
103 |
+
if self.backup:
|
104 |
+
i = 1
|
105 |
+
backup_hist_file = profile_dir / ("history.sqlite.old.%d" % i)
|
106 |
+
while backup_hist_file.exists():
|
107 |
+
i += 1
|
108 |
+
backup_hist_file = profile_dir / ("history.sqlite.old.%d" % i)
|
109 |
+
hist_file.rename(backup_hist_file)
|
110 |
+
print("Backed up longer history file to", backup_hist_file)
|
111 |
+
else:
|
112 |
+
hist_file.unlink()
|
113 |
+
|
114 |
+
new_hist_file.rename(hist_file)
|
115 |
+
|
116 |
+
|
117 |
+
class HistoryClear(HistoryTrim):
|
118 |
+
description = clear_hist_help
|
119 |
+
keep = Int(0, help="Number of recent lines to keep in the database.")
|
120 |
+
|
121 |
+
force = Bool(False, help="Don't prompt user for confirmation").tag(config=True)
|
122 |
+
|
123 |
+
flags = Dict( # type: ignore
|
124 |
+
dict(
|
125 |
+
force=({"HistoryClear": {"force": True}}, force.help),
|
126 |
+
f=({"HistoryTrim": {"force": True}}, force.help),
|
127 |
+
)
|
128 |
+
)
|
129 |
+
aliases = Dict() # type: ignore
|
130 |
+
|
131 |
+
def start(self):
|
132 |
+
if self.force or ask_yes_no(
|
133 |
+
"Really delete all ipython history? ", default="no", interrupt="no"
|
134 |
+
):
|
135 |
+
HistoryTrim.start(self)
|
136 |
+
|
137 |
+
|
138 |
+
class HistoryApp(Application):
|
139 |
+
name = "ipython-history"
|
140 |
+
description = "Manage the IPython history database."
|
141 |
+
|
142 |
+
subcommands = Dict(dict(
|
143 |
+
trim = (HistoryTrim, HistoryTrim.description.splitlines()[0]),
|
144 |
+
clear = (HistoryClear, HistoryClear.description.splitlines()[0]),
|
145 |
+
))
|
146 |
+
|
147 |
+
def start(self):
|
148 |
+
if self.subapp is None:
|
149 |
+
print(
|
150 |
+
"No subcommand specified. Must specify one of: "
|
151 |
+
+ ", ".join(map(repr, self.subcommands))
|
152 |
+
+ ".\n"
|
153 |
+
)
|
154 |
+
self.print_description()
|
155 |
+
self.print_subcommands()
|
156 |
+
self.exit(1)
|
157 |
+
else:
|
158 |
+
return self.subapp.start()
|
openalex_env_map/lib/python3.10/site-packages/IPython/core/hooks.py
ADDED
@@ -0,0 +1,173 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"""Hooks for IPython.
|
2 |
+
|
3 |
+
In Python, it is possible to overwrite any method of any object if you really
|
4 |
+
want to. But IPython exposes a few 'hooks', methods which are *designed* to
|
5 |
+
be overwritten by users for customization purposes. This module defines the
|
6 |
+
default versions of all such hooks, which get used by IPython if not
|
7 |
+
overridden by the user.
|
8 |
+
|
9 |
+
Hooks are simple functions, but they should be declared with ``self`` as their
|
10 |
+
first argument, because when activated they are registered into IPython as
|
11 |
+
instance methods. The self argument will be the IPython running instance
|
12 |
+
itself, so hooks have full access to the entire IPython object.
|
13 |
+
|
14 |
+
If you wish to define a new hook and activate it, you can make an :doc:`extension
|
15 |
+
</config/extensions/index>` or a :ref:`startup script <startup_files>`. For
|
16 |
+
example, you could use a startup file like this::
|
17 |
+
|
18 |
+
import os
|
19 |
+
|
20 |
+
def calljed(self,filename, linenum):
|
21 |
+
"My editor hook calls the jed editor directly."
|
22 |
+
print("Calling my own editor, jed ...")
|
23 |
+
if os.system('jed +%d %s' % (linenum,filename)) != 0:
|
24 |
+
raise TryNext()
|
25 |
+
|
26 |
+
def load_ipython_extension(ip):
|
27 |
+
ip.set_hook('editor', calljed)
|
28 |
+
|
29 |
+
"""
|
30 |
+
|
31 |
+
#*****************************************************************************
|
32 |
+
# Copyright (C) 2005 Fernando Perez. <[email protected]>
|
33 |
+
#
|
34 |
+
# Distributed under the terms of the BSD License. The full license is in
|
35 |
+
# the file COPYING, distributed as part of this software.
|
36 |
+
#*****************************************************************************
|
37 |
+
|
38 |
+
import os
|
39 |
+
import subprocess
|
40 |
+
import sys
|
41 |
+
|
42 |
+
from .error import TryNext
|
43 |
+
|
44 |
+
# List here all the default hooks. For now it's just the editor functions
|
45 |
+
# but over time we'll move here all the public API for user-accessible things.
|
46 |
+
|
47 |
+
__all__ = [
|
48 |
+
"editor",
|
49 |
+
"synchronize_with_editor",
|
50 |
+
"show_in_pager",
|
51 |
+
"pre_prompt_hook",
|
52 |
+
"clipboard_get",
|
53 |
+
]
|
54 |
+
|
55 |
+
deprecated = {'pre_run_code_hook': "a callback for the 'pre_execute' or 'pre_run_cell' event",
|
56 |
+
'late_startup_hook': "a callback for the 'shell_initialized' event",
|
57 |
+
'shutdown_hook': "the atexit module",
|
58 |
+
}
|
59 |
+
|
60 |
+
def editor(self, filename, linenum=None, wait=True):
|
61 |
+
"""Open the default editor at the given filename and linenumber.
|
62 |
+
|
63 |
+
This is IPython's default editor hook, you can use it as an example to
|
64 |
+
write your own modified one. To set your own editor function as the
|
65 |
+
new editor hook, call ip.set_hook('editor',yourfunc)."""
|
66 |
+
|
67 |
+
# IPython configures a default editor at startup by reading $EDITOR from
|
68 |
+
# the environment, and falling back on vi (unix) or notepad (win32).
|
69 |
+
editor = self.editor
|
70 |
+
|
71 |
+
# marker for at which line to open the file (for existing objects)
|
72 |
+
if linenum is None or editor=='notepad':
|
73 |
+
linemark = ''
|
74 |
+
else:
|
75 |
+
linemark = '+%d' % int(linenum)
|
76 |
+
|
77 |
+
# Enclose in quotes if necessary and legal
|
78 |
+
if ' ' in editor and os.path.isfile(editor) and editor[0] != '"':
|
79 |
+
editor = '"%s"' % editor
|
80 |
+
|
81 |
+
# Call the actual editor
|
82 |
+
proc = subprocess.Popen('%s %s %s' % (editor, linemark, filename),
|
83 |
+
shell=True)
|
84 |
+
if wait and proc.wait() != 0:
|
85 |
+
raise TryNext()
|
86 |
+
|
87 |
+
|
88 |
+
def synchronize_with_editor(self, filename, linenum, column):
|
89 |
+
pass
|
90 |
+
|
91 |
+
|
92 |
+
class CommandChainDispatcher:
|
93 |
+
""" Dispatch calls to a chain of commands until some func can handle it
|
94 |
+
|
95 |
+
Usage: instantiate, execute "add" to add commands (with optional
|
96 |
+
priority), execute normally via f() calling mechanism.
|
97 |
+
|
98 |
+
"""
|
99 |
+
def __init__(self,commands=None):
|
100 |
+
if commands is None:
|
101 |
+
self.chain = []
|
102 |
+
else:
|
103 |
+
self.chain = commands
|
104 |
+
|
105 |
+
|
106 |
+
def __call__(self,*args, **kw):
|
107 |
+
""" Command chain is called just like normal func.
|
108 |
+
|
109 |
+
This will call all funcs in chain with the same args as were given to
|
110 |
+
this function, and return the result of first func that didn't raise
|
111 |
+
TryNext"""
|
112 |
+
last_exc = TryNext()
|
113 |
+
for prio,cmd in self.chain:
|
114 |
+
# print("prio",prio,"cmd",cmd) # dbg
|
115 |
+
try:
|
116 |
+
return cmd(*args, **kw)
|
117 |
+
except TryNext as exc:
|
118 |
+
last_exc = exc
|
119 |
+
# if no function will accept it, raise TryNext up to the caller
|
120 |
+
raise last_exc
|
121 |
+
|
122 |
+
def __str__(self):
|
123 |
+
return str(self.chain)
|
124 |
+
|
125 |
+
def add(self, func, priority=0):
|
126 |
+
""" Add a func to the cmd chain with given priority """
|
127 |
+
self.chain.append((priority, func))
|
128 |
+
self.chain.sort(key=lambda x: x[0])
|
129 |
+
|
130 |
+
def __iter__(self):
|
131 |
+
""" Return all objects in chain.
|
132 |
+
|
133 |
+
Handy if the objects are not callable.
|
134 |
+
"""
|
135 |
+
return iter(self.chain)
|
136 |
+
|
137 |
+
|
138 |
+
def show_in_pager(self, data, start, screen_lines):
|
139 |
+
""" Run a string through pager """
|
140 |
+
# raising TryNext here will use the default paging functionality
|
141 |
+
raise TryNext
|
142 |
+
|
143 |
+
|
144 |
+
def pre_prompt_hook(self):
|
145 |
+
""" Run before displaying the next prompt
|
146 |
+
|
147 |
+
Use this e.g. to display output from asynchronous operations (in order
|
148 |
+
to not mess up text entry)
|
149 |
+
"""
|
150 |
+
|
151 |
+
return None
|
152 |
+
|
153 |
+
|
154 |
+
def clipboard_get(self):
|
155 |
+
""" Get text from the clipboard.
|
156 |
+
"""
|
157 |
+
from ..lib.clipboard import (
|
158 |
+
osx_clipboard_get,
|
159 |
+
tkinter_clipboard_get,
|
160 |
+
win32_clipboard_get,
|
161 |
+
wayland_clipboard_get,
|
162 |
+
)
|
163 |
+
if sys.platform == 'win32':
|
164 |
+
chain = [win32_clipboard_get, tkinter_clipboard_get]
|
165 |
+
elif sys.platform == 'darwin':
|
166 |
+
chain = [osx_clipboard_get, tkinter_clipboard_get]
|
167 |
+
else:
|
168 |
+
chain = [wayland_clipboard_get, tkinter_clipboard_get]
|
169 |
+
dispatcher = CommandChainDispatcher()
|
170 |
+
for func in chain:
|
171 |
+
dispatcher.add(func)
|
172 |
+
text = dispatcher()
|
173 |
+
return text
|
openalex_env_map/lib/python3.10/site-packages/IPython/core/inputsplitter.py
ADDED
@@ -0,0 +1,799 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"""DEPRECATED: Input handling and transformation machinery.
|
2 |
+
|
3 |
+
This module was deprecated in IPython 7.0, in favour of inputtransformer2.
|
4 |
+
|
5 |
+
The first class in this module, :class:`InputSplitter`, is designed to tell when
|
6 |
+
input from a line-oriented frontend is complete and should be executed, and when
|
7 |
+
the user should be prompted for another line of code instead. The name 'input
|
8 |
+
splitter' is largely for historical reasons.
|
9 |
+
|
10 |
+
A companion, :class:`IPythonInputSplitter`, provides the same functionality but
|
11 |
+
with full support for the extended IPython syntax (magics, system calls, etc).
|
12 |
+
The code to actually do these transformations is in :mod:`IPython.core.inputtransformer`.
|
13 |
+
:class:`IPythonInputSplitter` feeds the raw code to the transformers in order
|
14 |
+
and stores the results.
|
15 |
+
|
16 |
+
For more details, see the class docstrings below.
|
17 |
+
"""
|
18 |
+
|
19 |
+
from __future__ import annotations
|
20 |
+
|
21 |
+
from warnings import warn
|
22 |
+
|
23 |
+
warn('IPython.core.inputsplitter is deprecated since IPython 7 in favor of `IPython.core.inputtransformer2`',
|
24 |
+
DeprecationWarning)
|
25 |
+
|
26 |
+
# Copyright (c) IPython Development Team.
|
27 |
+
# Distributed under the terms of the Modified BSD License.
|
28 |
+
import ast
|
29 |
+
import codeop
|
30 |
+
import io
|
31 |
+
import re
|
32 |
+
import sys
|
33 |
+
import tokenize
|
34 |
+
import warnings
|
35 |
+
|
36 |
+
from typing import List, Tuple, Union, Optional, TYPE_CHECKING
|
37 |
+
from types import CodeType
|
38 |
+
|
39 |
+
from IPython.core.inputtransformer import (leading_indent,
|
40 |
+
classic_prompt,
|
41 |
+
ipy_prompt,
|
42 |
+
cellmagic,
|
43 |
+
assemble_logical_lines,
|
44 |
+
help_end,
|
45 |
+
escaped_commands,
|
46 |
+
assign_from_magic,
|
47 |
+
assign_from_system,
|
48 |
+
assemble_python_lines,
|
49 |
+
)
|
50 |
+
from IPython.utils import tokenutil
|
51 |
+
|
52 |
+
# These are available in this module for backwards compatibility.
|
53 |
+
from IPython.core.inputtransformer import (ESC_SHELL, ESC_SH_CAP, ESC_HELP,
|
54 |
+
ESC_HELP2, ESC_MAGIC, ESC_MAGIC2,
|
55 |
+
ESC_QUOTE, ESC_QUOTE2, ESC_PAREN, ESC_SEQUENCES)
|
56 |
+
|
57 |
+
if TYPE_CHECKING:
|
58 |
+
from typing_extensions import Self
|
59 |
+
#-----------------------------------------------------------------------------
|
60 |
+
# Utilities
|
61 |
+
#-----------------------------------------------------------------------------
|
62 |
+
|
63 |
+
# FIXME: These are general-purpose utilities that later can be moved to the
|
64 |
+
# general ward. Kept here for now because we're being very strict about test
|
65 |
+
# coverage with this code, and this lets us ensure that we keep 100% coverage
|
66 |
+
# while developing.
|
67 |
+
|
68 |
+
# compiled regexps for autoindent management
|
69 |
+
dedent_re = re.compile('|'.join([
|
70 |
+
r'^\s+raise(\s.*)?$', # raise statement (+ space + other stuff, maybe)
|
71 |
+
r'^\s+raise\([^\)]*\).*$', # wacky raise with immediate open paren
|
72 |
+
r'^\s+return(\s.*)?$', # normal return (+ space + other stuff, maybe)
|
73 |
+
r'^\s+return\([^\)]*\).*$', # wacky return with immediate open paren
|
74 |
+
r'^\s+pass\s*$', # pass (optionally followed by trailing spaces)
|
75 |
+
r'^\s+break\s*$', # break (optionally followed by trailing spaces)
|
76 |
+
r'^\s+continue\s*$', # continue (optionally followed by trailing spaces)
|
77 |
+
]))
|
78 |
+
ini_spaces_re = re.compile(r'^([ \t\r\f\v]+)')
|
79 |
+
|
80 |
+
# regexp to match pure comment lines so we don't accidentally insert 'if 1:'
|
81 |
+
# before pure comments
|
82 |
+
comment_line_re = re.compile(r'^\s*\#')
|
83 |
+
|
84 |
+
|
85 |
+
def num_ini_spaces(s):
|
86 |
+
"""Return the number of initial spaces in a string.
|
87 |
+
|
88 |
+
Note that tabs are counted as a single space. For now, we do *not* support
|
89 |
+
mixing of tabs and spaces in the user's input.
|
90 |
+
|
91 |
+
Parameters
|
92 |
+
----------
|
93 |
+
s : string
|
94 |
+
|
95 |
+
Returns
|
96 |
+
-------
|
97 |
+
n : int
|
98 |
+
"""
|
99 |
+
warnings.warn(
|
100 |
+
"`num_ini_spaces` is Pending Deprecation since IPython 8.17."
|
101 |
+
"It is considered for removal in in future version. "
|
102 |
+
"Please open an issue if you believe it should be kept.",
|
103 |
+
stacklevel=2,
|
104 |
+
category=PendingDeprecationWarning,
|
105 |
+
)
|
106 |
+
ini_spaces = ini_spaces_re.match(s)
|
107 |
+
if ini_spaces:
|
108 |
+
return ini_spaces.end()
|
109 |
+
else:
|
110 |
+
return 0
|
111 |
+
|
112 |
+
# Fake token types for partial_tokenize:
|
113 |
+
INCOMPLETE_STRING = tokenize.N_TOKENS
|
114 |
+
IN_MULTILINE_STATEMENT = tokenize.N_TOKENS + 1
|
115 |
+
|
116 |
+
# The 2 classes below have the same API as TokenInfo, but don't try to look up
|
117 |
+
# a token type name that they won't find.
|
118 |
+
class IncompleteString:
|
119 |
+
type = exact_type = INCOMPLETE_STRING
|
120 |
+
def __init__(self, s, start, end, line):
|
121 |
+
self.s = s
|
122 |
+
self.start = start
|
123 |
+
self.end = end
|
124 |
+
self.line = line
|
125 |
+
|
126 |
+
class InMultilineStatement:
|
127 |
+
type = exact_type = IN_MULTILINE_STATEMENT
|
128 |
+
def __init__(self, pos, line):
|
129 |
+
self.s = ''
|
130 |
+
self.start = self.end = pos
|
131 |
+
self.line = line
|
132 |
+
|
133 |
+
def partial_tokens(s):
|
134 |
+
"""Iterate over tokens from a possibly-incomplete string of code.
|
135 |
+
|
136 |
+
This adds two special token types: INCOMPLETE_STRING and
|
137 |
+
IN_MULTILINE_STATEMENT. These can only occur as the last token yielded, and
|
138 |
+
represent the two main ways for code to be incomplete.
|
139 |
+
"""
|
140 |
+
readline = io.StringIO(s).readline
|
141 |
+
token = tokenize.TokenInfo(tokenize.NEWLINE, '', (1, 0), (1, 0), '')
|
142 |
+
try:
|
143 |
+
for token in tokenutil.generate_tokens_catch_errors(readline):
|
144 |
+
yield token
|
145 |
+
except tokenize.TokenError as e:
|
146 |
+
# catch EOF error
|
147 |
+
lines = s.splitlines(keepends=True)
|
148 |
+
end = len(lines), len(lines[-1])
|
149 |
+
if 'multi-line string' in e.args[0]:
|
150 |
+
l, c = start = token.end
|
151 |
+
s = lines[l-1][c:] + ''.join(lines[l:])
|
152 |
+
yield IncompleteString(s, start, end, lines[-1])
|
153 |
+
elif 'multi-line statement' in e.args[0]:
|
154 |
+
yield InMultilineStatement(end, lines[-1])
|
155 |
+
else:
|
156 |
+
raise
|
157 |
+
|
158 |
+
def find_next_indent(code) -> int:
|
159 |
+
"""Find the number of spaces for the next line of indentation"""
|
160 |
+
tokens = list(partial_tokens(code))
|
161 |
+
if tokens[-1].type == tokenize.ENDMARKER:
|
162 |
+
tokens.pop()
|
163 |
+
if not tokens:
|
164 |
+
return 0
|
165 |
+
|
166 |
+
while tokens[-1].type in {
|
167 |
+
tokenize.DEDENT,
|
168 |
+
tokenize.NEWLINE,
|
169 |
+
tokenize.COMMENT,
|
170 |
+
tokenize.ERRORTOKEN,
|
171 |
+
}:
|
172 |
+
tokens.pop()
|
173 |
+
|
174 |
+
# Starting in Python 3.12, the tokenize module adds implicit newlines at the end
|
175 |
+
# of input. We need to remove those if we're in a multiline statement
|
176 |
+
if tokens[-1].type == IN_MULTILINE_STATEMENT:
|
177 |
+
while tokens[-2].type in {tokenize.NL}:
|
178 |
+
tokens.pop(-2)
|
179 |
+
|
180 |
+
|
181 |
+
if tokens[-1].type == INCOMPLETE_STRING:
|
182 |
+
# Inside a multiline string
|
183 |
+
return 0
|
184 |
+
|
185 |
+
# Find the indents used before
|
186 |
+
prev_indents = [0]
|
187 |
+
def _add_indent(n):
|
188 |
+
if n != prev_indents[-1]:
|
189 |
+
prev_indents.append(n)
|
190 |
+
|
191 |
+
tokiter = iter(tokens)
|
192 |
+
for tok in tokiter:
|
193 |
+
if tok.type in {tokenize.INDENT, tokenize.DEDENT}:
|
194 |
+
_add_indent(tok.end[1])
|
195 |
+
elif (tok.type == tokenize.NL):
|
196 |
+
try:
|
197 |
+
_add_indent(next(tokiter).start[1])
|
198 |
+
except StopIteration:
|
199 |
+
break
|
200 |
+
|
201 |
+
last_indent = prev_indents.pop()
|
202 |
+
|
203 |
+
# If we've just opened a multiline statement (e.g. 'a = ['), indent more
|
204 |
+
if tokens[-1].type == IN_MULTILINE_STATEMENT:
|
205 |
+
if tokens[-2].exact_type in {tokenize.LPAR, tokenize.LSQB, tokenize.LBRACE}:
|
206 |
+
return last_indent + 4
|
207 |
+
return last_indent
|
208 |
+
|
209 |
+
if tokens[-1].exact_type == tokenize.COLON:
|
210 |
+
# Line ends with colon - indent
|
211 |
+
return last_indent + 4
|
212 |
+
|
213 |
+
if last_indent:
|
214 |
+
# Examine the last line for dedent cues - statements like return or
|
215 |
+
# raise which normally end a block of code.
|
216 |
+
last_line_starts = 0
|
217 |
+
for i, tok in enumerate(tokens):
|
218 |
+
if tok.type == tokenize.NEWLINE:
|
219 |
+
last_line_starts = i + 1
|
220 |
+
|
221 |
+
last_line_tokens = tokens[last_line_starts:]
|
222 |
+
names = [t.string for t in last_line_tokens if t.type == tokenize.NAME]
|
223 |
+
if names and names[0] in {'raise', 'return', 'pass', 'break', 'continue'}:
|
224 |
+
# Find the most recent indentation less than the current level
|
225 |
+
for indent in reversed(prev_indents):
|
226 |
+
if indent < last_indent:
|
227 |
+
return indent
|
228 |
+
|
229 |
+
return last_indent
|
230 |
+
|
231 |
+
|
232 |
+
def last_blank(src):
|
233 |
+
"""Determine if the input source ends in a blank.
|
234 |
+
|
235 |
+
A blank is either a newline or a line consisting of whitespace.
|
236 |
+
|
237 |
+
Parameters
|
238 |
+
----------
|
239 |
+
src : string
|
240 |
+
A single or multiline string.
|
241 |
+
"""
|
242 |
+
if not src: return False
|
243 |
+
ll = src.splitlines()[-1]
|
244 |
+
return (ll == '') or ll.isspace()
|
245 |
+
|
246 |
+
|
247 |
+
last_two_blanks_re = re.compile(r'\n\s*\n\s*$', re.MULTILINE)
|
248 |
+
last_two_blanks_re2 = re.compile(r'.+\n\s*\n\s+$', re.MULTILINE)
|
249 |
+
|
250 |
+
def last_two_blanks(src):
|
251 |
+
"""Determine if the input source ends in two blanks.
|
252 |
+
|
253 |
+
A blank is either a newline or a line consisting of whitespace.
|
254 |
+
|
255 |
+
Parameters
|
256 |
+
----------
|
257 |
+
src : string
|
258 |
+
A single or multiline string.
|
259 |
+
"""
|
260 |
+
if not src: return False
|
261 |
+
# The logic here is tricky: I couldn't get a regexp to work and pass all
|
262 |
+
# the tests, so I took a different approach: split the source by lines,
|
263 |
+
# grab the last two and prepend '###\n' as a stand-in for whatever was in
|
264 |
+
# the body before the last two lines. Then, with that structure, it's
|
265 |
+
# possible to analyze with two regexps. Not the most elegant solution, but
|
266 |
+
# it works. If anyone tries to change this logic, make sure to validate
|
267 |
+
# the whole test suite first!
|
268 |
+
new_src = '\n'.join(['###\n'] + src.splitlines()[-2:])
|
269 |
+
return (bool(last_two_blanks_re.match(new_src)) or
|
270 |
+
bool(last_two_blanks_re2.match(new_src)) )
|
271 |
+
|
272 |
+
|
273 |
+
def remove_comments(src):
|
274 |
+
"""Remove all comments from input source.
|
275 |
+
|
276 |
+
Note: comments are NOT recognized inside of strings!
|
277 |
+
|
278 |
+
Parameters
|
279 |
+
----------
|
280 |
+
src : string
|
281 |
+
A single or multiline input string.
|
282 |
+
|
283 |
+
Returns
|
284 |
+
-------
|
285 |
+
String with all Python comments removed.
|
286 |
+
"""
|
287 |
+
|
288 |
+
return re.sub('#.*', '', src)
|
289 |
+
|
290 |
+
|
291 |
+
def get_input_encoding():
|
292 |
+
"""Return the default standard input encoding.
|
293 |
+
|
294 |
+
If sys.stdin has no encoding, 'ascii' is returned."""
|
295 |
+
# There are strange environments for which sys.stdin.encoding is None. We
|
296 |
+
# ensure that a valid encoding is returned.
|
297 |
+
encoding = getattr(sys.stdin, 'encoding', None)
|
298 |
+
if encoding is None:
|
299 |
+
encoding = 'ascii'
|
300 |
+
return encoding
|
301 |
+
|
302 |
+
#-----------------------------------------------------------------------------
|
303 |
+
# Classes and functions for normal Python syntax handling
|
304 |
+
#-----------------------------------------------------------------------------
|
305 |
+
|
306 |
+
class InputSplitter(object):
|
307 |
+
r"""An object that can accumulate lines of Python source before execution.
|
308 |
+
|
309 |
+
This object is designed to be fed python source line-by-line, using
|
310 |
+
:meth:`push`. It will return on each push whether the currently pushed
|
311 |
+
code could be executed already. In addition, it provides a method called
|
312 |
+
:meth:`push_accepts_more` that can be used to query whether more input
|
313 |
+
can be pushed into a single interactive block.
|
314 |
+
|
315 |
+
This is a simple example of how an interactive terminal-based client can use
|
316 |
+
this tool::
|
317 |
+
|
318 |
+
isp = InputSplitter()
|
319 |
+
while isp.push_accepts_more():
|
320 |
+
indent = ' '*isp.indent_spaces
|
321 |
+
prompt = '>>> ' + indent
|
322 |
+
line = indent + raw_input(prompt)
|
323 |
+
isp.push(line)
|
324 |
+
print('Input source was:\n', isp.source_reset())
|
325 |
+
"""
|
326 |
+
# A cache for storing the current indentation
|
327 |
+
# The first value stores the most recently processed source input
|
328 |
+
# The second value is the number of spaces for the current indentation
|
329 |
+
# If self.source matches the first value, the second value is a valid
|
330 |
+
# current indentation. Otherwise, the cache is invalid and the indentation
|
331 |
+
# must be recalculated.
|
332 |
+
_indent_spaces_cache: Union[Tuple[None, None], Tuple[str, int]] = None, None
|
333 |
+
# String, indicating the default input encoding. It is computed by default
|
334 |
+
# at initialization time via get_input_encoding(), but it can be reset by a
|
335 |
+
# client with specific knowledge of the encoding.
|
336 |
+
encoding = ''
|
337 |
+
# String where the current full source input is stored, properly encoded.
|
338 |
+
# Reading this attribute is the normal way of querying the currently pushed
|
339 |
+
# source code, that has been properly encoded.
|
340 |
+
source: str = ""
|
341 |
+
# Code object corresponding to the current source. It is automatically
|
342 |
+
# synced to the source, so it can be queried at any time to obtain the code
|
343 |
+
# object; it will be None if the source doesn't compile to valid Python.
|
344 |
+
code: Optional[CodeType] = None
|
345 |
+
|
346 |
+
# Private attributes
|
347 |
+
|
348 |
+
# List with lines of input accumulated so far
|
349 |
+
_buffer: List[str]
|
350 |
+
# Command compiler
|
351 |
+
_compile: codeop.CommandCompiler
|
352 |
+
# Boolean indicating whether the current block is complete
|
353 |
+
_is_complete: Optional[bool] = None
|
354 |
+
# Boolean indicating whether the current block has an unrecoverable syntax error
|
355 |
+
_is_invalid: bool = False
|
356 |
+
|
357 |
+
def __init__(self) -> None:
|
358 |
+
"""Create a new InputSplitter instance."""
|
359 |
+
self._buffer = []
|
360 |
+
self._compile = codeop.CommandCompiler()
|
361 |
+
self.encoding = get_input_encoding()
|
362 |
+
|
363 |
+
def reset(self):
|
364 |
+
"""Reset the input buffer and associated state."""
|
365 |
+
self._buffer[:] = []
|
366 |
+
self.source = ''
|
367 |
+
self.code = None
|
368 |
+
self._is_complete = False
|
369 |
+
self._is_invalid = False
|
370 |
+
|
371 |
+
def source_reset(self):
|
372 |
+
"""Return the input source and perform a full reset.
|
373 |
+
"""
|
374 |
+
out = self.source
|
375 |
+
self.reset()
|
376 |
+
return out
|
377 |
+
|
378 |
+
def check_complete(self, source):
|
379 |
+
"""Return whether a block of code is ready to execute, or should be continued
|
380 |
+
|
381 |
+
This is a non-stateful API, and will reset the state of this InputSplitter.
|
382 |
+
|
383 |
+
Parameters
|
384 |
+
----------
|
385 |
+
source : string
|
386 |
+
Python input code, which can be multiline.
|
387 |
+
|
388 |
+
Returns
|
389 |
+
-------
|
390 |
+
status : str
|
391 |
+
One of 'complete', 'incomplete', or 'invalid' if source is not a
|
392 |
+
prefix of valid code.
|
393 |
+
indent_spaces : int or None
|
394 |
+
The number of spaces by which to indent the next line of code. If
|
395 |
+
status is not 'incomplete', this is None.
|
396 |
+
"""
|
397 |
+
self.reset()
|
398 |
+
try:
|
399 |
+
self.push(source)
|
400 |
+
except SyntaxError:
|
401 |
+
# Transformers in IPythonInputSplitter can raise SyntaxError,
|
402 |
+
# which push() will not catch.
|
403 |
+
return 'invalid', None
|
404 |
+
else:
|
405 |
+
if self._is_invalid:
|
406 |
+
return 'invalid', None
|
407 |
+
elif self.push_accepts_more():
|
408 |
+
return 'incomplete', self.get_indent_spaces()
|
409 |
+
else:
|
410 |
+
return 'complete', None
|
411 |
+
finally:
|
412 |
+
self.reset()
|
413 |
+
|
414 |
+
def push(self, lines:str) -> bool:
|
415 |
+
"""Push one or more lines of input.
|
416 |
+
|
417 |
+
This stores the given lines and returns a status code indicating
|
418 |
+
whether the code forms a complete Python block or not.
|
419 |
+
|
420 |
+
Any exceptions generated in compilation are swallowed, but if an
|
421 |
+
exception was produced, the method returns True.
|
422 |
+
|
423 |
+
Parameters
|
424 |
+
----------
|
425 |
+
lines : string
|
426 |
+
One or more lines of Python input.
|
427 |
+
|
428 |
+
Returns
|
429 |
+
-------
|
430 |
+
is_complete : boolean
|
431 |
+
True if the current input source (the result of the current input
|
432 |
+
plus prior inputs) forms a complete Python execution block. Note that
|
433 |
+
this value is also stored as a private attribute (``_is_complete``), so it
|
434 |
+
can be queried at any time.
|
435 |
+
"""
|
436 |
+
assert isinstance(lines, str)
|
437 |
+
self._store(lines)
|
438 |
+
source = self.source
|
439 |
+
|
440 |
+
# Before calling _compile(), reset the code object to None so that if an
|
441 |
+
# exception is raised in compilation, we don't mislead by having
|
442 |
+
# inconsistent code/source attributes.
|
443 |
+
self.code, self._is_complete = None, None
|
444 |
+
self._is_invalid = False
|
445 |
+
|
446 |
+
# Honor termination lines properly
|
447 |
+
if source.endswith('\\\n'):
|
448 |
+
return False
|
449 |
+
|
450 |
+
try:
|
451 |
+
with warnings.catch_warnings():
|
452 |
+
warnings.simplefilter('error', SyntaxWarning)
|
453 |
+
self.code = self._compile(source, symbol="exec")
|
454 |
+
# Invalid syntax can produce any of a number of different errors from
|
455 |
+
# inside the compiler, so we have to catch them all. Syntax errors
|
456 |
+
# immediately produce a 'ready' block, so the invalid Python can be
|
457 |
+
# sent to the kernel for evaluation with possible ipython
|
458 |
+
# special-syntax conversion.
|
459 |
+
except (SyntaxError, OverflowError, ValueError, TypeError,
|
460 |
+
MemoryError, SyntaxWarning):
|
461 |
+
self._is_complete = True
|
462 |
+
self._is_invalid = True
|
463 |
+
else:
|
464 |
+
# Compilation didn't produce any exceptions (though it may not have
|
465 |
+
# given a complete code object)
|
466 |
+
self._is_complete = self.code is not None
|
467 |
+
|
468 |
+
return self._is_complete
|
469 |
+
|
470 |
+
def push_accepts_more(self):
|
471 |
+
"""Return whether a block of interactive input can accept more input.
|
472 |
+
|
473 |
+
This method is meant to be used by line-oriented frontends, who need to
|
474 |
+
guess whether a block is complete or not based solely on prior and
|
475 |
+
current input lines. The InputSplitter considers it has a complete
|
476 |
+
interactive block and will not accept more input when either:
|
477 |
+
|
478 |
+
* A SyntaxError is raised
|
479 |
+
|
480 |
+
* The code is complete and consists of a single line or a single
|
481 |
+
non-compound statement
|
482 |
+
|
483 |
+
* The code is complete and has a blank line at the end
|
484 |
+
|
485 |
+
If the current input produces a syntax error, this method immediately
|
486 |
+
returns False but does *not* raise the syntax error exception, as
|
487 |
+
typically clients will want to send invalid syntax to an execution
|
488 |
+
backend which might convert the invalid syntax into valid Python via
|
489 |
+
one of the dynamic IPython mechanisms.
|
490 |
+
"""
|
491 |
+
|
492 |
+
# With incomplete input, unconditionally accept more
|
493 |
+
# A syntax error also sets _is_complete to True - see push()
|
494 |
+
if not self._is_complete:
|
495 |
+
#print("Not complete") # debug
|
496 |
+
return True
|
497 |
+
|
498 |
+
# The user can make any (complete) input execute by leaving a blank line
|
499 |
+
last_line = self.source.splitlines()[-1]
|
500 |
+
if (not last_line) or last_line.isspace():
|
501 |
+
#print("Blank line") # debug
|
502 |
+
return False
|
503 |
+
|
504 |
+
# If there's just a single line or AST node, and we're flush left, as is
|
505 |
+
# the case after a simple statement such as 'a=1', we want to execute it
|
506 |
+
# straight away.
|
507 |
+
if self.get_indent_spaces() == 0:
|
508 |
+
if len(self.source.splitlines()) <= 1:
|
509 |
+
return False
|
510 |
+
|
511 |
+
try:
|
512 |
+
code_ast = ast.parse("".join(self._buffer))
|
513 |
+
except Exception:
|
514 |
+
#print("Can't parse AST") # debug
|
515 |
+
return False
|
516 |
+
else:
|
517 |
+
if len(code_ast.body) == 1 and \
|
518 |
+
not hasattr(code_ast.body[0], 'body'):
|
519 |
+
#print("Simple statement") # debug
|
520 |
+
return False
|
521 |
+
|
522 |
+
# General fallback - accept more code
|
523 |
+
return True
|
524 |
+
|
525 |
+
def get_indent_spaces(self) -> int:
|
526 |
+
sourcefor, n = self._indent_spaces_cache
|
527 |
+
if sourcefor == self.source:
|
528 |
+
assert n is not None
|
529 |
+
return n
|
530 |
+
|
531 |
+
# self.source always has a trailing newline
|
532 |
+
n = find_next_indent(self.source[:-1])
|
533 |
+
self._indent_spaces_cache = (self.source, n)
|
534 |
+
return n
|
535 |
+
|
536 |
+
# Backwards compatibility. I think all code that used .indent_spaces was
|
537 |
+
# inside IPython, but we can leave this here until IPython 7 in case any
|
538 |
+
# other modules are using it. -TK, November 2017
|
539 |
+
indent_spaces = property(get_indent_spaces)
|
540 |
+
|
541 |
+
def _store(self, lines, buffer=None, store='source'):
|
542 |
+
"""Store one or more lines of input.
|
543 |
+
|
544 |
+
If input lines are not newline-terminated, a newline is automatically
|
545 |
+
appended."""
|
546 |
+
|
547 |
+
if buffer is None:
|
548 |
+
buffer = self._buffer
|
549 |
+
|
550 |
+
if lines.endswith('\n'):
|
551 |
+
buffer.append(lines)
|
552 |
+
else:
|
553 |
+
buffer.append(lines+'\n')
|
554 |
+
setattr(self, store, self._set_source(buffer))
|
555 |
+
|
556 |
+
def _set_source(self, buffer):
|
557 |
+
return u''.join(buffer)
|
558 |
+
|
559 |
+
|
560 |
+
class IPythonInputSplitter(InputSplitter):
|
561 |
+
"""An input splitter that recognizes all of IPython's special syntax."""
|
562 |
+
|
563 |
+
# String with raw, untransformed input.
|
564 |
+
source_raw = ''
|
565 |
+
|
566 |
+
# Flag to track when a transformer has stored input that it hasn't given
|
567 |
+
# back yet.
|
568 |
+
transformer_accumulating = False
|
569 |
+
|
570 |
+
# Flag to track when assemble_python_lines has stored input that it hasn't
|
571 |
+
# given back yet.
|
572 |
+
within_python_line = False
|
573 |
+
|
574 |
+
# Private attributes
|
575 |
+
|
576 |
+
# List with lines of raw input accumulated so far.
|
577 |
+
_buffer_raw: List[str]
|
578 |
+
|
579 |
+
def __init__(self, line_input_checker=True, physical_line_transforms=None,
|
580 |
+
logical_line_transforms=None, python_line_transforms=None):
|
581 |
+
super(IPythonInputSplitter, self).__init__()
|
582 |
+
self._buffer_raw = []
|
583 |
+
self._validate = True
|
584 |
+
|
585 |
+
if physical_line_transforms is not None:
|
586 |
+
self.physical_line_transforms = physical_line_transforms
|
587 |
+
else:
|
588 |
+
self.physical_line_transforms = [
|
589 |
+
leading_indent(),
|
590 |
+
classic_prompt(),
|
591 |
+
ipy_prompt(),
|
592 |
+
cellmagic(end_on_blank_line=line_input_checker),
|
593 |
+
]
|
594 |
+
|
595 |
+
self.assemble_logical_lines = assemble_logical_lines()
|
596 |
+
if logical_line_transforms is not None:
|
597 |
+
self.logical_line_transforms = logical_line_transforms
|
598 |
+
else:
|
599 |
+
self.logical_line_transforms = [
|
600 |
+
help_end(),
|
601 |
+
escaped_commands(),
|
602 |
+
assign_from_magic(),
|
603 |
+
assign_from_system(),
|
604 |
+
]
|
605 |
+
|
606 |
+
self.assemble_python_lines = assemble_python_lines()
|
607 |
+
if python_line_transforms is not None:
|
608 |
+
self.python_line_transforms = python_line_transforms
|
609 |
+
else:
|
610 |
+
# We don't use any of these at present
|
611 |
+
self.python_line_transforms = []
|
612 |
+
|
613 |
+
@property
|
614 |
+
def transforms(self):
|
615 |
+
"Quick access to all transformers."
|
616 |
+
return self.physical_line_transforms + \
|
617 |
+
[self.assemble_logical_lines] + self.logical_line_transforms + \
|
618 |
+
[self.assemble_python_lines] + self.python_line_transforms
|
619 |
+
|
620 |
+
@property
|
621 |
+
def transforms_in_use(self):
|
622 |
+
"""Transformers, excluding logical line transformers if we're in a
|
623 |
+
Python line."""
|
624 |
+
t = self.physical_line_transforms[:]
|
625 |
+
if not self.within_python_line:
|
626 |
+
t += [self.assemble_logical_lines] + self.logical_line_transforms
|
627 |
+
return t + [self.assemble_python_lines] + self.python_line_transforms
|
628 |
+
|
629 |
+
def reset(self):
|
630 |
+
"""Reset the input buffer and associated state."""
|
631 |
+
super(IPythonInputSplitter, self).reset()
|
632 |
+
self._buffer_raw[:] = []
|
633 |
+
self.source_raw = ''
|
634 |
+
self.transformer_accumulating = False
|
635 |
+
self.within_python_line = False
|
636 |
+
|
637 |
+
for t in self.transforms:
|
638 |
+
try:
|
639 |
+
t.reset()
|
640 |
+
except SyntaxError:
|
641 |
+
# Nothing that calls reset() expects to handle transformer
|
642 |
+
# errors
|
643 |
+
pass
|
644 |
+
|
645 |
+
def flush_transformers(self: Self):
|
646 |
+
def _flush(transform, outs: List[str]):
|
647 |
+
"""yield transformed lines
|
648 |
+
|
649 |
+
always strings, never None
|
650 |
+
|
651 |
+
transform: the current transform
|
652 |
+
outs: an iterable of previously transformed inputs.
|
653 |
+
Each may be multiline, which will be passed
|
654 |
+
one line at a time to transform.
|
655 |
+
"""
|
656 |
+
for out in outs:
|
657 |
+
for line in out.splitlines():
|
658 |
+
# push one line at a time
|
659 |
+
tmp = transform.push(line)
|
660 |
+
if tmp is not None:
|
661 |
+
yield tmp
|
662 |
+
|
663 |
+
# reset the transform
|
664 |
+
tmp = transform.reset()
|
665 |
+
if tmp is not None:
|
666 |
+
yield tmp
|
667 |
+
|
668 |
+
out: List[str] = []
|
669 |
+
for t in self.transforms_in_use:
|
670 |
+
out = _flush(t, out)
|
671 |
+
|
672 |
+
out = list(out)
|
673 |
+
if out:
|
674 |
+
self._store('\n'.join(out))
|
675 |
+
|
676 |
+
def raw_reset(self):
|
677 |
+
"""Return raw input only and perform a full reset.
|
678 |
+
"""
|
679 |
+
out = self.source_raw
|
680 |
+
self.reset()
|
681 |
+
return out
|
682 |
+
|
683 |
+
def source_reset(self):
|
684 |
+
try:
|
685 |
+
self.flush_transformers()
|
686 |
+
return self.source
|
687 |
+
finally:
|
688 |
+
self.reset()
|
689 |
+
|
690 |
+
def push_accepts_more(self):
|
691 |
+
if self.transformer_accumulating:
|
692 |
+
return True
|
693 |
+
else:
|
694 |
+
return super(IPythonInputSplitter, self).push_accepts_more()
|
695 |
+
|
696 |
+
def transform_cell(self, cell):
|
697 |
+
"""Process and translate a cell of input.
|
698 |
+
"""
|
699 |
+
self.reset()
|
700 |
+
try:
|
701 |
+
self.push(cell)
|
702 |
+
self.flush_transformers()
|
703 |
+
return self.source
|
704 |
+
finally:
|
705 |
+
self.reset()
|
706 |
+
|
707 |
+
def push(self, lines:str) -> bool:
|
708 |
+
"""Push one or more lines of IPython input.
|
709 |
+
|
710 |
+
This stores the given lines and returns a status code indicating
|
711 |
+
whether the code forms a complete Python block or not, after processing
|
712 |
+
all input lines for special IPython syntax.
|
713 |
+
|
714 |
+
Any exceptions generated in compilation are swallowed, but if an
|
715 |
+
exception was produced, the method returns True.
|
716 |
+
|
717 |
+
Parameters
|
718 |
+
----------
|
719 |
+
lines : string
|
720 |
+
One or more lines of Python input.
|
721 |
+
|
722 |
+
Returns
|
723 |
+
-------
|
724 |
+
is_complete : boolean
|
725 |
+
True if the current input source (the result of the current input
|
726 |
+
plus prior inputs) forms a complete Python execution block. Note that
|
727 |
+
this value is also stored as a private attribute (_is_complete), so it
|
728 |
+
can be queried at any time.
|
729 |
+
"""
|
730 |
+
assert isinstance(lines, str)
|
731 |
+
# We must ensure all input is pure unicode
|
732 |
+
# ''.splitlines() --> [], but we need to push the empty line to transformers
|
733 |
+
lines_list = lines.splitlines()
|
734 |
+
if not lines_list:
|
735 |
+
lines_list = ['']
|
736 |
+
|
737 |
+
# Store raw source before applying any transformations to it. Note
|
738 |
+
# that this must be done *after* the reset() call that would otherwise
|
739 |
+
# flush the buffer.
|
740 |
+
self._store(lines, self._buffer_raw, 'source_raw')
|
741 |
+
|
742 |
+
transformed_lines_list = []
|
743 |
+
for line in lines_list:
|
744 |
+
transformed = self._transform_line(line)
|
745 |
+
if transformed is not None:
|
746 |
+
transformed_lines_list.append(transformed)
|
747 |
+
|
748 |
+
if transformed_lines_list:
|
749 |
+
transformed_lines = '\n'.join(transformed_lines_list)
|
750 |
+
return super(IPythonInputSplitter, self).push(transformed_lines)
|
751 |
+
else:
|
752 |
+
# Got nothing back from transformers - they must be waiting for
|
753 |
+
# more input.
|
754 |
+
return False
|
755 |
+
|
756 |
+
def _transform_line(self, line):
|
757 |
+
"""Push a line of input code through the various transformers.
|
758 |
+
|
759 |
+
Returns any output from the transformers, or None if a transformer
|
760 |
+
is accumulating lines.
|
761 |
+
|
762 |
+
Sets self.transformer_accumulating as a side effect.
|
763 |
+
"""
|
764 |
+
def _accumulating(dbg):
|
765 |
+
#print(dbg)
|
766 |
+
self.transformer_accumulating = True
|
767 |
+
return None
|
768 |
+
|
769 |
+
for transformer in self.physical_line_transforms:
|
770 |
+
line = transformer.push(line)
|
771 |
+
if line is None:
|
772 |
+
return _accumulating(transformer)
|
773 |
+
|
774 |
+
if not self.within_python_line:
|
775 |
+
line = self.assemble_logical_lines.push(line)
|
776 |
+
if line is None:
|
777 |
+
return _accumulating('acc logical line')
|
778 |
+
|
779 |
+
for transformer in self.logical_line_transforms:
|
780 |
+
line = transformer.push(line)
|
781 |
+
if line is None:
|
782 |
+
return _accumulating(transformer)
|
783 |
+
|
784 |
+
line = self.assemble_python_lines.push(line)
|
785 |
+
if line is None:
|
786 |
+
self.within_python_line = True
|
787 |
+
return _accumulating('acc python line')
|
788 |
+
else:
|
789 |
+
self.within_python_line = False
|
790 |
+
|
791 |
+
for transformer in self.python_line_transforms:
|
792 |
+
line = transformer.push(line)
|
793 |
+
if line is None:
|
794 |
+
return _accumulating(transformer)
|
795 |
+
|
796 |
+
#print("transformers clear") #debug
|
797 |
+
self.transformer_accumulating = False
|
798 |
+
return line
|
799 |
+
|
openalex_env_map/lib/python3.10/site-packages/IPython/core/inputtransformer.py
ADDED
@@ -0,0 +1,577 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"""DEPRECATED: Input transformer classes to support IPython special syntax.
|
2 |
+
|
3 |
+
This module was deprecated in IPython 7.0, in favour of inputtransformer2.
|
4 |
+
|
5 |
+
This includes the machinery to recognise and transform ``%magic`` commands,
|
6 |
+
``!system`` commands, ``help?`` querying, prompt stripping, and so forth.
|
7 |
+
"""
|
8 |
+
import abc
|
9 |
+
import functools
|
10 |
+
import re
|
11 |
+
import tokenize
|
12 |
+
import warnings
|
13 |
+
from tokenize import untokenize, TokenError
|
14 |
+
from io import StringIO
|
15 |
+
|
16 |
+
from IPython.core.splitinput import LineInfo
|
17 |
+
from IPython.utils import tokenutil
|
18 |
+
|
19 |
+
#-----------------------------------------------------------------------------
|
20 |
+
# Globals
|
21 |
+
#-----------------------------------------------------------------------------
|
22 |
+
|
23 |
+
# The escape sequences that define the syntax transformations IPython will
|
24 |
+
# apply to user input. These can NOT be just changed here: many regular
|
25 |
+
# expressions and other parts of the code may use their hardcoded values, and
|
26 |
+
# for all intents and purposes they constitute the 'IPython syntax', so they
|
27 |
+
# should be considered fixed.
|
28 |
+
|
29 |
+
ESC_SHELL = '!' # Send line to underlying system shell
|
30 |
+
ESC_SH_CAP = '!!' # Send line to system shell and capture output
|
31 |
+
ESC_HELP = '?' # Find information about object
|
32 |
+
ESC_HELP2 = '??' # Find extra-detailed information about object
|
33 |
+
ESC_MAGIC = '%' # Call magic function
|
34 |
+
ESC_MAGIC2 = '%%' # Call cell-magic function
|
35 |
+
ESC_QUOTE = ',' # Split args on whitespace, quote each as string and call
|
36 |
+
ESC_QUOTE2 = ';' # Quote all args as a single string, call
|
37 |
+
ESC_PAREN = '/' # Call first argument with rest of line as arguments
|
38 |
+
|
39 |
+
ESC_SEQUENCES = [ESC_SHELL, ESC_SH_CAP, ESC_HELP ,\
|
40 |
+
ESC_HELP2, ESC_MAGIC, ESC_MAGIC2,\
|
41 |
+
ESC_QUOTE, ESC_QUOTE2, ESC_PAREN ]
|
42 |
+
|
43 |
+
|
44 |
+
class InputTransformer(metaclass=abc.ABCMeta):
|
45 |
+
"""Abstract base class for line-based input transformers."""
|
46 |
+
|
47 |
+
def __init__(self):
|
48 |
+
warnings.warn(
|
49 |
+
"`InputTransformer` has been deprecated since IPython 7.0"
|
50 |
+
" and emit a warnig since IPython 8.31, it"
|
51 |
+
" will be removed in the future",
|
52 |
+
DeprecationWarning,
|
53 |
+
stacklevel=2,
|
54 |
+
)
|
55 |
+
|
56 |
+
@abc.abstractmethod
|
57 |
+
def push(self, line):
|
58 |
+
"""Send a line of input to the transformer, returning the transformed
|
59 |
+
input or None if the transformer is waiting for more input.
|
60 |
+
|
61 |
+
Must be overridden by subclasses.
|
62 |
+
|
63 |
+
Implementations may raise ``SyntaxError`` if the input is invalid. No
|
64 |
+
other exceptions may be raised.
|
65 |
+
"""
|
66 |
+
pass
|
67 |
+
|
68 |
+
@abc.abstractmethod
|
69 |
+
def reset(self):
|
70 |
+
"""Return, transformed any lines that the transformer has accumulated,
|
71 |
+
and reset its internal state.
|
72 |
+
|
73 |
+
Must be overridden by subclasses.
|
74 |
+
"""
|
75 |
+
pass
|
76 |
+
|
77 |
+
@classmethod
|
78 |
+
def wrap(cls, func):
|
79 |
+
"""Can be used by subclasses as a decorator, to return a factory that
|
80 |
+
will allow instantiation with the decorated object.
|
81 |
+
"""
|
82 |
+
@functools.wraps(func)
|
83 |
+
def transformer_factory(**kwargs):
|
84 |
+
return cls(func, **kwargs) # type: ignore [call-arg]
|
85 |
+
|
86 |
+
return transformer_factory
|
87 |
+
|
88 |
+
class StatelessInputTransformer(InputTransformer):
|
89 |
+
"""Wrapper for a stateless input transformer implemented as a function."""
|
90 |
+
def __init__(self, func):
|
91 |
+
super().__init__()
|
92 |
+
warnings.warn(
|
93 |
+
"`StatelessInputTransformer` has been deprecated since IPython 7.0"
|
94 |
+
" and emit a warnig since IPython 8.31, it"
|
95 |
+
" will be removed in the future",
|
96 |
+
DeprecationWarning,
|
97 |
+
stacklevel=2,
|
98 |
+
)
|
99 |
+
self.func = func
|
100 |
+
|
101 |
+
def __repr__(self):
|
102 |
+
return "StatelessInputTransformer(func={0!r})".format(self.func)
|
103 |
+
|
104 |
+
def push(self, line):
|
105 |
+
"""Send a line of input to the transformer, returning the
|
106 |
+
transformed input."""
|
107 |
+
return self.func(line)
|
108 |
+
|
109 |
+
def reset(self):
|
110 |
+
"""No-op - exists for compatibility."""
|
111 |
+
pass
|
112 |
+
|
113 |
+
class CoroutineInputTransformer(InputTransformer):
|
114 |
+
"""Wrapper for an input transformer implemented as a coroutine."""
|
115 |
+
def __init__(self, coro, **kwargs):
|
116 |
+
# Prime it
|
117 |
+
super().__init__()
|
118 |
+
warnings.warn(
|
119 |
+
"`CoroutineInputTransformer` has been deprecated since IPython 7.0"
|
120 |
+
" and emit a warnig since IPython 8.31, it"
|
121 |
+
" will be removed in the future",
|
122 |
+
DeprecationWarning,
|
123 |
+
stacklevel=2,
|
124 |
+
)
|
125 |
+
self.coro = coro(**kwargs)
|
126 |
+
next(self.coro)
|
127 |
+
|
128 |
+
def __repr__(self):
|
129 |
+
return "CoroutineInputTransformer(coro={0!r})".format(self.coro)
|
130 |
+
|
131 |
+
def push(self, line):
|
132 |
+
"""Send a line of input to the transformer, returning the
|
133 |
+
transformed input or None if the transformer is waiting for more
|
134 |
+
input.
|
135 |
+
"""
|
136 |
+
return self.coro.send(line)
|
137 |
+
|
138 |
+
def reset(self):
|
139 |
+
"""Return, transformed any lines that the transformer has
|
140 |
+
accumulated, and reset its internal state.
|
141 |
+
"""
|
142 |
+
return self.coro.send(None)
|
143 |
+
|
144 |
+
class TokenInputTransformer(InputTransformer):
|
145 |
+
"""Wrapper for a token-based input transformer.
|
146 |
+
|
147 |
+
func should accept a list of tokens (5-tuples, see tokenize docs), and
|
148 |
+
return an iterable which can be passed to tokenize.untokenize().
|
149 |
+
"""
|
150 |
+
def __init__(self, func):
|
151 |
+
warnings.warn(
|
152 |
+
"`CoroutineInputTransformer` has been deprecated since IPython 7.0"
|
153 |
+
" and emit a warnig since IPython 8.31, it"
|
154 |
+
" will be removed in the future",
|
155 |
+
DeprecationWarning,
|
156 |
+
stacklevel=2,
|
157 |
+
)
|
158 |
+
self.func = func
|
159 |
+
self.buf = []
|
160 |
+
self.reset_tokenizer()
|
161 |
+
|
162 |
+
def reset_tokenizer(self):
|
163 |
+
it = iter(self.buf)
|
164 |
+
self.tokenizer = tokenutil.generate_tokens_catch_errors(it.__next__)
|
165 |
+
|
166 |
+
def push(self, line):
|
167 |
+
self.buf.append(line + '\n')
|
168 |
+
if all(l.isspace() for l in self.buf):
|
169 |
+
return self.reset()
|
170 |
+
|
171 |
+
tokens = []
|
172 |
+
stop_at_NL = False
|
173 |
+
try:
|
174 |
+
for intok in self.tokenizer:
|
175 |
+
tokens.append(intok)
|
176 |
+
t = intok[0]
|
177 |
+
if t == tokenize.NEWLINE or (stop_at_NL and t == tokenize.NL):
|
178 |
+
# Stop before we try to pull a line we don't have yet
|
179 |
+
break
|
180 |
+
elif t == tokenize.ERRORTOKEN:
|
181 |
+
stop_at_NL = True
|
182 |
+
except TokenError:
|
183 |
+
# Multi-line statement - stop and try again with the next line
|
184 |
+
self.reset_tokenizer()
|
185 |
+
return None
|
186 |
+
|
187 |
+
return self.output(tokens)
|
188 |
+
|
189 |
+
def output(self, tokens):
|
190 |
+
self.buf.clear()
|
191 |
+
self.reset_tokenizer()
|
192 |
+
return untokenize(self.func(tokens)).rstrip('\n')
|
193 |
+
|
194 |
+
def reset(self):
|
195 |
+
l = ''.join(self.buf)
|
196 |
+
self.buf.clear()
|
197 |
+
self.reset_tokenizer()
|
198 |
+
if l:
|
199 |
+
return l.rstrip('\n')
|
200 |
+
|
201 |
+
class assemble_python_lines(TokenInputTransformer):
|
202 |
+
def __init__(self):
|
203 |
+
super().__init__(None)
|
204 |
+
|
205 |
+
def output(self, tokens):
|
206 |
+
return self.reset()
|
207 |
+
|
208 |
+
@CoroutineInputTransformer.wrap
|
209 |
+
def assemble_logical_lines():
|
210 |
+
r"""Join lines following explicit line continuations (\)"""
|
211 |
+
line = ''
|
212 |
+
while True:
|
213 |
+
line = (yield line)
|
214 |
+
if not line or line.isspace():
|
215 |
+
continue
|
216 |
+
|
217 |
+
parts = []
|
218 |
+
while line is not None:
|
219 |
+
if line.endswith('\\') and (not has_comment(line)):
|
220 |
+
parts.append(line[:-1])
|
221 |
+
line = (yield None) # Get another line
|
222 |
+
else:
|
223 |
+
parts.append(line)
|
224 |
+
break
|
225 |
+
|
226 |
+
# Output
|
227 |
+
line = ''.join(parts)
|
228 |
+
|
229 |
+
# Utilities
|
230 |
+
def _make_help_call(target: str, esc: str, lspace: str) -> str:
|
231 |
+
"""Prepares a pinfo(2)/psearch call from a target name and the escape
|
232 |
+
(i.e. ? or ??)"""
|
233 |
+
method = 'pinfo2' if esc == '??' \
|
234 |
+
else 'psearch' if '*' in target \
|
235 |
+
else 'pinfo'
|
236 |
+
arg = " ".join([method, target])
|
237 |
+
#Prepare arguments for get_ipython().run_line_magic(magic_name, magic_args)
|
238 |
+
t_magic_name, _, t_magic_arg_s = arg.partition(' ')
|
239 |
+
t_magic_name = t_magic_name.lstrip(ESC_MAGIC)
|
240 |
+
return "%sget_ipython().run_line_magic(%r, %r)" % (
|
241 |
+
lspace,
|
242 |
+
t_magic_name,
|
243 |
+
t_magic_arg_s,
|
244 |
+
)
|
245 |
+
|
246 |
+
|
247 |
+
# These define the transformations for the different escape characters.
|
248 |
+
def _tr_system(line_info: LineInfo):
|
249 |
+
"Translate lines escaped with: !"
|
250 |
+
cmd = line_info.line.lstrip().lstrip(ESC_SHELL)
|
251 |
+
return '%sget_ipython().system(%r)' % (line_info.pre, cmd)
|
252 |
+
|
253 |
+
|
254 |
+
def _tr_system2(line_info: LineInfo):
|
255 |
+
"Translate lines escaped with: !!"
|
256 |
+
cmd = line_info.line.lstrip()[2:]
|
257 |
+
return '%sget_ipython().getoutput(%r)' % (line_info.pre, cmd)
|
258 |
+
|
259 |
+
|
260 |
+
def _tr_help(line_info: LineInfo):
|
261 |
+
"Translate lines escaped with: ?/??"
|
262 |
+
# A naked help line should just fire the intro help screen
|
263 |
+
if not line_info.line[1:]:
|
264 |
+
return 'get_ipython().show_usage()'
|
265 |
+
|
266 |
+
return _make_help_call(line_info.ifun, line_info.esc, line_info.pre)
|
267 |
+
|
268 |
+
|
269 |
+
def _tr_magic(line_info: LineInfo):
|
270 |
+
"Translate lines escaped with: %"
|
271 |
+
tpl = '%sget_ipython().run_line_magic(%r, %r)'
|
272 |
+
if line_info.line.startswith(ESC_MAGIC2):
|
273 |
+
return line_info.line
|
274 |
+
cmd = ' '.join([line_info.ifun, line_info.the_rest]).strip()
|
275 |
+
#Prepare arguments for get_ipython().run_line_magic(magic_name, magic_args)
|
276 |
+
t_magic_name, _, t_magic_arg_s = cmd.partition(' ')
|
277 |
+
t_magic_name = t_magic_name.lstrip(ESC_MAGIC)
|
278 |
+
return tpl % (line_info.pre, t_magic_name, t_magic_arg_s)
|
279 |
+
|
280 |
+
|
281 |
+
def _tr_quote(line_info: LineInfo):
|
282 |
+
"Translate lines escaped with: ,"
|
283 |
+
return '%s%s("%s")' % (line_info.pre, line_info.ifun,
|
284 |
+
'", "'.join(line_info.the_rest.split()) )
|
285 |
+
|
286 |
+
|
287 |
+
def _tr_quote2(line_info: LineInfo):
|
288 |
+
"Translate lines escaped with: ;"
|
289 |
+
return '%s%s("%s")' % (line_info.pre, line_info.ifun,
|
290 |
+
line_info.the_rest)
|
291 |
+
|
292 |
+
|
293 |
+
def _tr_paren(line_info: LineInfo):
|
294 |
+
"Translate lines escaped with: /"
|
295 |
+
return '%s%s(%s)' % (line_info.pre, line_info.ifun,
|
296 |
+
", ".join(line_info.the_rest.split()))
|
297 |
+
|
298 |
+
tr = { ESC_SHELL : _tr_system,
|
299 |
+
ESC_SH_CAP : _tr_system2,
|
300 |
+
ESC_HELP : _tr_help,
|
301 |
+
ESC_HELP2 : _tr_help,
|
302 |
+
ESC_MAGIC : _tr_magic,
|
303 |
+
ESC_QUOTE : _tr_quote,
|
304 |
+
ESC_QUOTE2 : _tr_quote2,
|
305 |
+
ESC_PAREN : _tr_paren }
|
306 |
+
|
307 |
+
@StatelessInputTransformer.wrap
|
308 |
+
def escaped_commands(line: str):
|
309 |
+
"""Transform escaped commands - %magic, !system, ?help + various autocalls."""
|
310 |
+
if not line or line.isspace():
|
311 |
+
return line
|
312 |
+
lineinf = LineInfo(line)
|
313 |
+
if lineinf.esc not in tr:
|
314 |
+
return line
|
315 |
+
|
316 |
+
return tr[lineinf.esc](lineinf)
|
317 |
+
|
318 |
+
_initial_space_re = re.compile(r'\s*')
|
319 |
+
|
320 |
+
_help_end_re = re.compile(r"""(%{0,2}
|
321 |
+
(?!\d)[\w*]+ # Variable name
|
322 |
+
(\.(?!\d)[\w*]+)* # .etc.etc
|
323 |
+
)
|
324 |
+
(\?\??)$ # ? or ??
|
325 |
+
""",
|
326 |
+
re.VERBOSE)
|
327 |
+
|
328 |
+
# Extra pseudotokens for multiline strings and data structures
|
329 |
+
_MULTILINE_STRING = object()
|
330 |
+
_MULTILINE_STRUCTURE = object()
|
331 |
+
|
332 |
+
def _line_tokens(line):
|
333 |
+
"""Helper for has_comment and ends_in_comment_or_string."""
|
334 |
+
readline = StringIO(line).readline
|
335 |
+
toktypes = set()
|
336 |
+
try:
|
337 |
+
for t in tokenutil.generate_tokens_catch_errors(readline):
|
338 |
+
toktypes.add(t[0])
|
339 |
+
except TokenError as e:
|
340 |
+
# There are only two cases where a TokenError is raised.
|
341 |
+
if 'multi-line string' in e.args[0]:
|
342 |
+
toktypes.add(_MULTILINE_STRING)
|
343 |
+
else:
|
344 |
+
toktypes.add(_MULTILINE_STRUCTURE)
|
345 |
+
return toktypes
|
346 |
+
|
347 |
+
def has_comment(src):
|
348 |
+
"""Indicate whether an input line has (i.e. ends in, or is) a comment.
|
349 |
+
|
350 |
+
This uses tokenize, so it can distinguish comments from # inside strings.
|
351 |
+
|
352 |
+
Parameters
|
353 |
+
----------
|
354 |
+
src : string
|
355 |
+
A single line input string.
|
356 |
+
|
357 |
+
Returns
|
358 |
+
-------
|
359 |
+
comment : bool
|
360 |
+
True if source has a comment.
|
361 |
+
"""
|
362 |
+
return (tokenize.COMMENT in _line_tokens(src))
|
363 |
+
|
364 |
+
def ends_in_comment_or_string(src):
|
365 |
+
"""Indicates whether or not an input line ends in a comment or within
|
366 |
+
a multiline string.
|
367 |
+
|
368 |
+
Parameters
|
369 |
+
----------
|
370 |
+
src : string
|
371 |
+
A single line input string.
|
372 |
+
|
373 |
+
Returns
|
374 |
+
-------
|
375 |
+
comment : bool
|
376 |
+
True if source ends in a comment or multiline string.
|
377 |
+
"""
|
378 |
+
toktypes = _line_tokens(src)
|
379 |
+
return (tokenize.COMMENT in toktypes) or (_MULTILINE_STRING in toktypes)
|
380 |
+
|
381 |
+
|
382 |
+
@StatelessInputTransformer.wrap
|
383 |
+
def help_end(line: str):
|
384 |
+
"""Translate lines with ?/?? at the end"""
|
385 |
+
m = _help_end_re.search(line)
|
386 |
+
if m is None or ends_in_comment_or_string(line):
|
387 |
+
return line
|
388 |
+
target = m.group(1)
|
389 |
+
esc = m.group(3)
|
390 |
+
match = _initial_space_re.match(line)
|
391 |
+
assert match is not None
|
392 |
+
lspace = match.group(0)
|
393 |
+
|
394 |
+
return _make_help_call(target, esc, lspace)
|
395 |
+
|
396 |
+
|
397 |
+
@CoroutineInputTransformer.wrap
|
398 |
+
def cellmagic(end_on_blank_line: bool = False):
|
399 |
+
"""Captures & transforms cell magics.
|
400 |
+
|
401 |
+
After a cell magic is started, this stores up any lines it gets until it is
|
402 |
+
reset (sent None).
|
403 |
+
"""
|
404 |
+
tpl = 'get_ipython().run_cell_magic(%r, %r, %r)'
|
405 |
+
cellmagic_help_re = re.compile(r'%%\w+\?')
|
406 |
+
line = ''
|
407 |
+
while True:
|
408 |
+
line = (yield line)
|
409 |
+
# consume leading empty lines
|
410 |
+
while not line:
|
411 |
+
line = (yield line)
|
412 |
+
|
413 |
+
if not line.startswith(ESC_MAGIC2):
|
414 |
+
# This isn't a cell magic, idle waiting for reset then start over
|
415 |
+
while line is not None:
|
416 |
+
line = (yield line)
|
417 |
+
continue
|
418 |
+
|
419 |
+
if cellmagic_help_re.match(line):
|
420 |
+
# This case will be handled by help_end
|
421 |
+
continue
|
422 |
+
|
423 |
+
first = line
|
424 |
+
body = []
|
425 |
+
line = (yield None)
|
426 |
+
while (line is not None) and \
|
427 |
+
((line.strip() != '') or not end_on_blank_line):
|
428 |
+
body.append(line)
|
429 |
+
line = (yield None)
|
430 |
+
|
431 |
+
# Output
|
432 |
+
magic_name, _, first = first.partition(' ')
|
433 |
+
magic_name = magic_name.lstrip(ESC_MAGIC2)
|
434 |
+
line = tpl % (magic_name, first, u'\n'.join(body))
|
435 |
+
|
436 |
+
|
437 |
+
def _strip_prompts(prompt_re, initial_re=None, turnoff_re=None):
|
438 |
+
"""Remove matching input prompts from a block of input.
|
439 |
+
|
440 |
+
Parameters
|
441 |
+
----------
|
442 |
+
prompt_re : regular expression
|
443 |
+
A regular expression matching any input prompt (including continuation)
|
444 |
+
initial_re : regular expression, optional
|
445 |
+
A regular expression matching only the initial prompt, but not continuation.
|
446 |
+
If no initial expression is given, prompt_re will be used everywhere.
|
447 |
+
Used mainly for plain Python prompts, where the continuation prompt
|
448 |
+
``...`` is a valid Python expression in Python 3, so shouldn't be stripped.
|
449 |
+
|
450 |
+
Notes
|
451 |
+
-----
|
452 |
+
If `initial_re` and `prompt_re differ`,
|
453 |
+
only `initial_re` will be tested against the first line.
|
454 |
+
If any prompt is found on the first two lines,
|
455 |
+
prompts will be stripped from the rest of the block.
|
456 |
+
"""
|
457 |
+
if initial_re is None:
|
458 |
+
initial_re = prompt_re
|
459 |
+
line = ''
|
460 |
+
while True:
|
461 |
+
line = (yield line)
|
462 |
+
|
463 |
+
# First line of cell
|
464 |
+
if line is None:
|
465 |
+
continue
|
466 |
+
out, n1 = initial_re.subn('', line, count=1)
|
467 |
+
if turnoff_re and not n1:
|
468 |
+
if turnoff_re.match(line):
|
469 |
+
# We're in e.g. a cell magic; disable this transformer for
|
470 |
+
# the rest of the cell.
|
471 |
+
while line is not None:
|
472 |
+
line = (yield line)
|
473 |
+
continue
|
474 |
+
|
475 |
+
line = (yield out)
|
476 |
+
|
477 |
+
if line is None:
|
478 |
+
continue
|
479 |
+
# check for any prompt on the second line of the cell,
|
480 |
+
# because people often copy from just after the first prompt,
|
481 |
+
# so we might not see it in the first line.
|
482 |
+
out, n2 = prompt_re.subn('', line, count=1)
|
483 |
+
line = (yield out)
|
484 |
+
|
485 |
+
if n1 or n2:
|
486 |
+
# Found a prompt in the first two lines - check for it in
|
487 |
+
# the rest of the cell as well.
|
488 |
+
while line is not None:
|
489 |
+
line = (yield prompt_re.sub('', line, count=1))
|
490 |
+
|
491 |
+
else:
|
492 |
+
# Prompts not in input - wait for reset
|
493 |
+
while line is not None:
|
494 |
+
line = (yield line)
|
495 |
+
|
496 |
+
@CoroutineInputTransformer.wrap
|
497 |
+
def classic_prompt():
|
498 |
+
"""Strip the >>>/... prompts of the Python interactive shell."""
|
499 |
+
# FIXME: non-capturing version (?:...) usable?
|
500 |
+
prompt_re = re.compile(r'^(>>>|\.\.\.)( |$)')
|
501 |
+
initial_re = re.compile(r'^>>>( |$)')
|
502 |
+
# Any %magic/!system is IPython syntax, so we needn't look for >>> prompts
|
503 |
+
turnoff_re = re.compile(r'^[%!]')
|
504 |
+
return _strip_prompts(prompt_re, initial_re, turnoff_re)
|
505 |
+
|
506 |
+
@CoroutineInputTransformer.wrap
|
507 |
+
def ipy_prompt():
|
508 |
+
"""Strip IPython's In [1]:/...: prompts."""
|
509 |
+
# FIXME: non-capturing version (?:...) usable?
|
510 |
+
prompt_re = re.compile(r'^(In \[\d+\]: |\s*\.{3,}: ?)')
|
511 |
+
# Disable prompt stripping inside cell magics
|
512 |
+
turnoff_re = re.compile(r'^%%')
|
513 |
+
return _strip_prompts(prompt_re, turnoff_re=turnoff_re)
|
514 |
+
|
515 |
+
|
516 |
+
@CoroutineInputTransformer.wrap
|
517 |
+
def leading_indent():
|
518 |
+
"""Remove leading indentation.
|
519 |
+
|
520 |
+
If the first line starts with a spaces or tabs, the same whitespace will be
|
521 |
+
removed from each following line until it is reset.
|
522 |
+
"""
|
523 |
+
space_re = re.compile(r'^[ \t]+')
|
524 |
+
line = ''
|
525 |
+
while True:
|
526 |
+
line = (yield line)
|
527 |
+
|
528 |
+
if line is None:
|
529 |
+
continue
|
530 |
+
|
531 |
+
m = space_re.match(line)
|
532 |
+
if m:
|
533 |
+
space = m.group(0)
|
534 |
+
while line is not None:
|
535 |
+
if line.startswith(space):
|
536 |
+
line = line[len(space):]
|
537 |
+
line = (yield line)
|
538 |
+
else:
|
539 |
+
# No leading spaces - wait for reset
|
540 |
+
while line is not None:
|
541 |
+
line = (yield line)
|
542 |
+
|
543 |
+
|
544 |
+
_assign_pat = \
|
545 |
+
r'''(?P<lhs>(\s*)
|
546 |
+
([\w\.]+) # Initial identifier
|
547 |
+
(\s*,\s*
|
548 |
+
\*?[\w\.]+)* # Further identifiers for unpacking
|
549 |
+
\s*?,? # Trailing comma
|
550 |
+
)
|
551 |
+
\s*=\s*
|
552 |
+
'''
|
553 |
+
|
554 |
+
assign_system_re = re.compile(r'{}!\s*(?P<cmd>.*)'.format(_assign_pat), re.VERBOSE)
|
555 |
+
assign_system_template = '%s = get_ipython().getoutput(%r)'
|
556 |
+
@StatelessInputTransformer.wrap
|
557 |
+
def assign_from_system(line):
|
558 |
+
"""Transform assignment from system commands (e.g. files = !ls)"""
|
559 |
+
m = assign_system_re.match(line)
|
560 |
+
if m is None:
|
561 |
+
return line
|
562 |
+
|
563 |
+
return assign_system_template % m.group('lhs', 'cmd')
|
564 |
+
|
565 |
+
assign_magic_re = re.compile(r'{}%\s*(?P<cmd>.*)'.format(_assign_pat), re.VERBOSE)
|
566 |
+
assign_magic_template = '%s = get_ipython().run_line_magic(%r, %r)'
|
567 |
+
@StatelessInputTransformer.wrap
|
568 |
+
def assign_from_magic(line):
|
569 |
+
"""Transform assignment from magic commands (e.g. a = %who_ls)"""
|
570 |
+
m = assign_magic_re.match(line)
|
571 |
+
if m is None:
|
572 |
+
return line
|
573 |
+
#Prepare arguments for get_ipython().run_line_magic(magic_name, magic_args)
|
574 |
+
m_lhs, m_cmd = m.group('lhs', 'cmd')
|
575 |
+
t_magic_name, _, t_magic_arg_s = m_cmd.partition(' ')
|
576 |
+
t_magic_name = t_magic_name.lstrip(ESC_MAGIC)
|
577 |
+
return assign_magic_template % (m_lhs, t_magic_name, t_magic_arg_s)
|
openalex_env_map/lib/python3.10/site-packages/IPython/core/inputtransformer2.py
ADDED
@@ -0,0 +1,830 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"""Input transformer machinery to support IPython special syntax.
|
2 |
+
|
3 |
+
This includes the machinery to recognise and transform ``%magic`` commands,
|
4 |
+
``!system`` commands, ``help?`` querying, prompt stripping, and so forth.
|
5 |
+
|
6 |
+
Added: IPython 7.0. Replaces inputsplitter and inputtransformer which were
|
7 |
+
deprecated in 7.0.
|
8 |
+
"""
|
9 |
+
|
10 |
+
# Copyright (c) IPython Development Team.
|
11 |
+
# Distributed under the terms of the Modified BSD License.
|
12 |
+
|
13 |
+
import ast
|
14 |
+
from codeop import CommandCompiler, Compile
|
15 |
+
import re
|
16 |
+
import sys
|
17 |
+
import tokenize
|
18 |
+
from typing import List, Tuple, Optional, Any
|
19 |
+
import warnings
|
20 |
+
|
21 |
+
from IPython.utils import tokenutil
|
22 |
+
|
23 |
+
_indent_re = re.compile(r'^[ \t]+')
|
24 |
+
|
25 |
+
def leading_empty_lines(lines):
|
26 |
+
"""Remove leading empty lines
|
27 |
+
|
28 |
+
If the leading lines are empty or contain only whitespace, they will be
|
29 |
+
removed.
|
30 |
+
"""
|
31 |
+
if not lines:
|
32 |
+
return lines
|
33 |
+
for i, line in enumerate(lines):
|
34 |
+
if line and not line.isspace():
|
35 |
+
return lines[i:]
|
36 |
+
return lines
|
37 |
+
|
38 |
+
def leading_indent(lines):
|
39 |
+
"""Remove leading indentation.
|
40 |
+
|
41 |
+
If the first line starts with a spaces or tabs, the same whitespace will be
|
42 |
+
removed from each following line in the cell.
|
43 |
+
"""
|
44 |
+
if not lines:
|
45 |
+
return lines
|
46 |
+
m = _indent_re.match(lines[0])
|
47 |
+
if not m:
|
48 |
+
return lines
|
49 |
+
space = m.group(0)
|
50 |
+
n = len(space)
|
51 |
+
return [l[n:] if l.startswith(space) else l
|
52 |
+
for l in lines]
|
53 |
+
|
54 |
+
class PromptStripper:
|
55 |
+
"""Remove matching input prompts from a block of input.
|
56 |
+
|
57 |
+
Parameters
|
58 |
+
----------
|
59 |
+
prompt_re : regular expression
|
60 |
+
A regular expression matching any input prompt (including continuation,
|
61 |
+
e.g. ``...``)
|
62 |
+
initial_re : regular expression, optional
|
63 |
+
A regular expression matching only the initial prompt, but not continuation.
|
64 |
+
If no initial expression is given, prompt_re will be used everywhere.
|
65 |
+
Used mainly for plain Python prompts (``>>>``), where the continuation prompt
|
66 |
+
``...`` is a valid Python expression in Python 3, so shouldn't be stripped.
|
67 |
+
|
68 |
+
Notes
|
69 |
+
-----
|
70 |
+
|
71 |
+
If initial_re and prompt_re differ,
|
72 |
+
only initial_re will be tested against the first line.
|
73 |
+
If any prompt is found on the first two lines,
|
74 |
+
prompts will be stripped from the rest of the block.
|
75 |
+
"""
|
76 |
+
def __init__(self, prompt_re, initial_re=None):
|
77 |
+
self.prompt_re = prompt_re
|
78 |
+
self.initial_re = initial_re or prompt_re
|
79 |
+
|
80 |
+
def _strip(self, lines):
|
81 |
+
return [self.prompt_re.sub('', l, count=1) for l in lines]
|
82 |
+
|
83 |
+
def __call__(self, lines):
|
84 |
+
if not lines:
|
85 |
+
return lines
|
86 |
+
if self.initial_re.match(lines[0]) or \
|
87 |
+
(len(lines) > 1 and self.prompt_re.match(lines[1])):
|
88 |
+
return self._strip(lines)
|
89 |
+
return lines
|
90 |
+
|
91 |
+
classic_prompt = PromptStripper(
|
92 |
+
prompt_re=re.compile(r'^(>>>|\.\.\.)( |$)'),
|
93 |
+
initial_re=re.compile(r'^>>>( |$)')
|
94 |
+
)
|
95 |
+
|
96 |
+
ipython_prompt = PromptStripper(
|
97 |
+
re.compile(
|
98 |
+
r"""
|
99 |
+
^( # Match from the beginning of a line, either:
|
100 |
+
|
101 |
+
# 1. First-line prompt:
|
102 |
+
((\[nav\]|\[ins\])?\ )? # Vi editing mode prompt, if it's there
|
103 |
+
In\ # The 'In' of the prompt, with a space
|
104 |
+
\[\d+\]: # Command index, as displayed in the prompt
|
105 |
+
\ # With a mandatory trailing space
|
106 |
+
|
107 |
+
| # ... or ...
|
108 |
+
|
109 |
+
# 2. The three dots of the multiline prompt
|
110 |
+
\s* # All leading whitespace characters
|
111 |
+
\.{3,}: # The three (or more) dots
|
112 |
+
\ ? # With an optional trailing space
|
113 |
+
|
114 |
+
)
|
115 |
+
""",
|
116 |
+
re.VERBOSE,
|
117 |
+
)
|
118 |
+
)
|
119 |
+
|
120 |
+
|
121 |
+
def cell_magic(lines):
|
122 |
+
if not lines or not lines[0].startswith('%%'):
|
123 |
+
return lines
|
124 |
+
if re.match(r'%%\w+\?', lines[0]):
|
125 |
+
# This case will be handled by help_end
|
126 |
+
return lines
|
127 |
+
magic_name, _, first_line = lines[0][2:].rstrip().partition(' ')
|
128 |
+
body = ''.join(lines[1:])
|
129 |
+
return ['get_ipython().run_cell_magic(%r, %r, %r)\n'
|
130 |
+
% (magic_name, first_line, body)]
|
131 |
+
|
132 |
+
|
133 |
+
def _find_assign_op(token_line) -> Optional[int]:
|
134 |
+
"""Get the index of the first assignment in the line ('=' not inside brackets)
|
135 |
+
|
136 |
+
Note: We don't try to support multiple special assignment (a = b = %foo)
|
137 |
+
"""
|
138 |
+
paren_level = 0
|
139 |
+
for i, ti in enumerate(token_line):
|
140 |
+
s = ti.string
|
141 |
+
if s == '=' and paren_level == 0:
|
142 |
+
return i
|
143 |
+
if s in {'(','[','{'}:
|
144 |
+
paren_level += 1
|
145 |
+
elif s in {')', ']', '}'}:
|
146 |
+
if paren_level > 0:
|
147 |
+
paren_level -= 1
|
148 |
+
return None
|
149 |
+
|
150 |
+
def find_end_of_continued_line(lines, start_line: int):
|
151 |
+
"""Find the last line of a line explicitly extended using backslashes.
|
152 |
+
|
153 |
+
Uses 0-indexed line numbers.
|
154 |
+
"""
|
155 |
+
end_line = start_line
|
156 |
+
while lines[end_line].endswith('\\\n'):
|
157 |
+
end_line += 1
|
158 |
+
if end_line >= len(lines):
|
159 |
+
break
|
160 |
+
return end_line
|
161 |
+
|
162 |
+
def assemble_continued_line(lines, start: Tuple[int, int], end_line: int):
|
163 |
+
r"""Assemble a single line from multiple continued line pieces
|
164 |
+
|
165 |
+
Continued lines are lines ending in ``\``, and the line following the last
|
166 |
+
``\`` in the block.
|
167 |
+
|
168 |
+
For example, this code continues over multiple lines::
|
169 |
+
|
170 |
+
if (assign_ix is not None) \
|
171 |
+
and (len(line) >= assign_ix + 2) \
|
172 |
+
and (line[assign_ix+1].string == '%') \
|
173 |
+
and (line[assign_ix+2].type == tokenize.NAME):
|
174 |
+
|
175 |
+
This statement contains four continued line pieces.
|
176 |
+
Assembling these pieces into a single line would give::
|
177 |
+
|
178 |
+
if (assign_ix is not None) and (len(line) >= assign_ix + 2) and (line[...
|
179 |
+
|
180 |
+
This uses 0-indexed line numbers. *start* is (lineno, colno).
|
181 |
+
|
182 |
+
Used to allow ``%magic`` and ``!system`` commands to be continued over
|
183 |
+
multiple lines.
|
184 |
+
"""
|
185 |
+
parts = [lines[start[0]][start[1]:]] + lines[start[0]+1:end_line+1]
|
186 |
+
return ' '.join([p.rstrip()[:-1] for p in parts[:-1]] # Strip backslash+newline
|
187 |
+
+ [parts[-1].rstrip()]) # Strip newline from last line
|
188 |
+
|
189 |
+
class TokenTransformBase:
|
190 |
+
"""Base class for transformations which examine tokens.
|
191 |
+
|
192 |
+
Special syntax should not be transformed when it occurs inside strings or
|
193 |
+
comments. This is hard to reliably avoid with regexes. The solution is to
|
194 |
+
tokenise the code as Python, and recognise the special syntax in the tokens.
|
195 |
+
|
196 |
+
IPython's special syntax is not valid Python syntax, so tokenising may go
|
197 |
+
wrong after the special syntax starts. These classes therefore find and
|
198 |
+
transform *one* instance of special syntax at a time into regular Python
|
199 |
+
syntax. After each transformation, tokens are regenerated to find the next
|
200 |
+
piece of special syntax.
|
201 |
+
|
202 |
+
Subclasses need to implement one class method (find)
|
203 |
+
and one regular method (transform).
|
204 |
+
|
205 |
+
The priority attribute can select which transformation to apply if multiple
|
206 |
+
transformers match in the same place. Lower numbers have higher priority.
|
207 |
+
This allows "%magic?" to be turned into a help call rather than a magic call.
|
208 |
+
"""
|
209 |
+
# Lower numbers -> higher priority (for matches in the same location)
|
210 |
+
priority = 10
|
211 |
+
|
212 |
+
def sortby(self):
|
213 |
+
return self.start_line, self.start_col, self.priority
|
214 |
+
|
215 |
+
def __init__(self, start):
|
216 |
+
self.start_line = start[0] - 1 # Shift from 1-index to 0-index
|
217 |
+
self.start_col = start[1]
|
218 |
+
|
219 |
+
@classmethod
|
220 |
+
def find(cls, tokens_by_line):
|
221 |
+
"""Find one instance of special syntax in the provided tokens.
|
222 |
+
|
223 |
+
Tokens are grouped into logical lines for convenience,
|
224 |
+
so it is easy to e.g. look at the first token of each line.
|
225 |
+
*tokens_by_line* is a list of lists of tokenize.TokenInfo objects.
|
226 |
+
|
227 |
+
This should return an instance of its class, pointing to the start
|
228 |
+
position it has found, or None if it found no match.
|
229 |
+
"""
|
230 |
+
raise NotImplementedError
|
231 |
+
|
232 |
+
def transform(self, lines: List[str]):
|
233 |
+
"""Transform one instance of special syntax found by ``find()``
|
234 |
+
|
235 |
+
Takes a list of strings representing physical lines,
|
236 |
+
returns a similar list of transformed lines.
|
237 |
+
"""
|
238 |
+
raise NotImplementedError
|
239 |
+
|
240 |
+
class MagicAssign(TokenTransformBase):
|
241 |
+
"""Transformer for assignments from magics (a = %foo)"""
|
242 |
+
@classmethod
|
243 |
+
def find(cls, tokens_by_line):
|
244 |
+
"""Find the first magic assignment (a = %foo) in the cell.
|
245 |
+
"""
|
246 |
+
for line in tokens_by_line:
|
247 |
+
assign_ix = _find_assign_op(line)
|
248 |
+
if (assign_ix is not None) \
|
249 |
+
and (len(line) >= assign_ix + 2) \
|
250 |
+
and (line[assign_ix+1].string == '%') \
|
251 |
+
and (line[assign_ix+2].type == tokenize.NAME):
|
252 |
+
return cls(line[assign_ix+1].start)
|
253 |
+
|
254 |
+
def transform(self, lines: List[str]):
|
255 |
+
"""Transform a magic assignment found by the ``find()`` classmethod.
|
256 |
+
"""
|
257 |
+
start_line, start_col = self.start_line, self.start_col
|
258 |
+
lhs = lines[start_line][:start_col]
|
259 |
+
end_line = find_end_of_continued_line(lines, start_line)
|
260 |
+
rhs = assemble_continued_line(lines, (start_line, start_col), end_line)
|
261 |
+
assert rhs.startswith('%'), rhs
|
262 |
+
magic_name, _, args = rhs[1:].partition(' ')
|
263 |
+
|
264 |
+
lines_before = lines[:start_line]
|
265 |
+
call = "get_ipython().run_line_magic({!r}, {!r})".format(magic_name, args)
|
266 |
+
new_line = lhs + call + '\n'
|
267 |
+
lines_after = lines[end_line+1:]
|
268 |
+
|
269 |
+
return lines_before + [new_line] + lines_after
|
270 |
+
|
271 |
+
|
272 |
+
class SystemAssign(TokenTransformBase):
|
273 |
+
"""Transformer for assignments from system commands (a = !foo)"""
|
274 |
+
@classmethod
|
275 |
+
def find_pre_312(cls, tokens_by_line):
|
276 |
+
for line in tokens_by_line:
|
277 |
+
assign_ix = _find_assign_op(line)
|
278 |
+
if (assign_ix is not None) \
|
279 |
+
and not line[assign_ix].line.strip().startswith('=') \
|
280 |
+
and (len(line) >= assign_ix + 2) \
|
281 |
+
and (line[assign_ix + 1].type == tokenize.ERRORTOKEN):
|
282 |
+
ix = assign_ix + 1
|
283 |
+
|
284 |
+
while ix < len(line) and line[ix].type == tokenize.ERRORTOKEN:
|
285 |
+
if line[ix].string == '!':
|
286 |
+
return cls(line[ix].start)
|
287 |
+
elif not line[ix].string.isspace():
|
288 |
+
break
|
289 |
+
ix += 1
|
290 |
+
|
291 |
+
@classmethod
|
292 |
+
def find_post_312(cls, tokens_by_line):
|
293 |
+
for line in tokens_by_line:
|
294 |
+
assign_ix = _find_assign_op(line)
|
295 |
+
if (
|
296 |
+
(assign_ix is not None)
|
297 |
+
and not line[assign_ix].line.strip().startswith("=")
|
298 |
+
and (len(line) >= assign_ix + 2)
|
299 |
+
and (line[assign_ix + 1].type == tokenize.OP)
|
300 |
+
and (line[assign_ix + 1].string == "!")
|
301 |
+
):
|
302 |
+
return cls(line[assign_ix + 1].start)
|
303 |
+
|
304 |
+
@classmethod
|
305 |
+
def find(cls, tokens_by_line):
|
306 |
+
"""Find the first system assignment (a = !foo) in the cell."""
|
307 |
+
if sys.version_info < (3, 12):
|
308 |
+
return cls.find_pre_312(tokens_by_line)
|
309 |
+
return cls.find_post_312(tokens_by_line)
|
310 |
+
|
311 |
+
def transform(self, lines: List[str]):
|
312 |
+
"""Transform a system assignment found by the ``find()`` classmethod.
|
313 |
+
"""
|
314 |
+
start_line, start_col = self.start_line, self.start_col
|
315 |
+
|
316 |
+
lhs = lines[start_line][:start_col]
|
317 |
+
end_line = find_end_of_continued_line(lines, start_line)
|
318 |
+
rhs = assemble_continued_line(lines, (start_line, start_col), end_line)
|
319 |
+
assert rhs.startswith('!'), rhs
|
320 |
+
cmd = rhs[1:]
|
321 |
+
|
322 |
+
lines_before = lines[:start_line]
|
323 |
+
call = "get_ipython().getoutput({!r})".format(cmd)
|
324 |
+
new_line = lhs + call + '\n'
|
325 |
+
lines_after = lines[end_line + 1:]
|
326 |
+
|
327 |
+
return lines_before + [new_line] + lines_after
|
328 |
+
|
329 |
+
# The escape sequences that define the syntax transformations IPython will
|
330 |
+
# apply to user input. These can NOT be just changed here: many regular
|
331 |
+
# expressions and other parts of the code may use their hardcoded values, and
|
332 |
+
# for all intents and purposes they constitute the 'IPython syntax', so they
|
333 |
+
# should be considered fixed.
|
334 |
+
|
335 |
+
ESC_SHELL = '!' # Send line to underlying system shell
|
336 |
+
ESC_SH_CAP = '!!' # Send line to system shell and capture output
|
337 |
+
ESC_HELP = '?' # Find information about object
|
338 |
+
ESC_HELP2 = '??' # Find extra-detailed information about object
|
339 |
+
ESC_MAGIC = '%' # Call magic function
|
340 |
+
ESC_MAGIC2 = '%%' # Call cell-magic function
|
341 |
+
ESC_QUOTE = ',' # Split args on whitespace, quote each as string and call
|
342 |
+
ESC_QUOTE2 = ';' # Quote all args as a single string, call
|
343 |
+
ESC_PAREN = '/' # Call first argument with rest of line as arguments
|
344 |
+
|
345 |
+
ESCAPE_SINGLES = {'!', '?', '%', ',', ';', '/'}
|
346 |
+
ESCAPE_DOUBLES = {'!!', '??'} # %% (cell magic) is handled separately
|
347 |
+
|
348 |
+
def _make_help_call(target, esc):
|
349 |
+
"""Prepares a pinfo(2)/psearch call from a target name and the escape
|
350 |
+
(i.e. ? or ??)"""
|
351 |
+
method = 'pinfo2' if esc == '??' \
|
352 |
+
else 'psearch' if '*' in target \
|
353 |
+
else 'pinfo'
|
354 |
+
arg = " ".join([method, target])
|
355 |
+
#Prepare arguments for get_ipython().run_line_magic(magic_name, magic_args)
|
356 |
+
t_magic_name, _, t_magic_arg_s = arg.partition(' ')
|
357 |
+
t_magic_name = t_magic_name.lstrip(ESC_MAGIC)
|
358 |
+
return "get_ipython().run_line_magic(%r, %r)" % (t_magic_name, t_magic_arg_s)
|
359 |
+
|
360 |
+
|
361 |
+
def _tr_help(content):
|
362 |
+
"""Translate lines escaped with: ?
|
363 |
+
|
364 |
+
A naked help line should fire the intro help screen (shell.show_usage())
|
365 |
+
"""
|
366 |
+
if not content:
|
367 |
+
return 'get_ipython().show_usage()'
|
368 |
+
|
369 |
+
return _make_help_call(content, '?')
|
370 |
+
|
371 |
+
def _tr_help2(content):
|
372 |
+
"""Translate lines escaped with: ??
|
373 |
+
|
374 |
+
A naked help line should fire the intro help screen (shell.show_usage())
|
375 |
+
"""
|
376 |
+
if not content:
|
377 |
+
return 'get_ipython().show_usage()'
|
378 |
+
|
379 |
+
return _make_help_call(content, '??')
|
380 |
+
|
381 |
+
def _tr_magic(content):
|
382 |
+
"Translate lines escaped with a percent sign: %"
|
383 |
+
name, _, args = content.partition(' ')
|
384 |
+
return 'get_ipython().run_line_magic(%r, %r)' % (name, args)
|
385 |
+
|
386 |
+
def _tr_quote(content):
|
387 |
+
"Translate lines escaped with a comma: ,"
|
388 |
+
name, _, args = content.partition(' ')
|
389 |
+
return '%s("%s")' % (name, '", "'.join(args.split()) )
|
390 |
+
|
391 |
+
def _tr_quote2(content):
|
392 |
+
"Translate lines escaped with a semicolon: ;"
|
393 |
+
name, _, args = content.partition(' ')
|
394 |
+
return '%s("%s")' % (name, args)
|
395 |
+
|
396 |
+
def _tr_paren(content):
|
397 |
+
"Translate lines escaped with a slash: /"
|
398 |
+
name, _, args = content.partition(" ")
|
399 |
+
if name == "":
|
400 |
+
raise SyntaxError(f'"{ESC_SHELL}" must be followed by a callable name')
|
401 |
+
|
402 |
+
return '%s(%s)' % (name, ", ".join(args.split()))
|
403 |
+
|
404 |
+
tr = { ESC_SHELL : 'get_ipython().system({!r})'.format,
|
405 |
+
ESC_SH_CAP : 'get_ipython().getoutput({!r})'.format,
|
406 |
+
ESC_HELP : _tr_help,
|
407 |
+
ESC_HELP2 : _tr_help2,
|
408 |
+
ESC_MAGIC : _tr_magic,
|
409 |
+
ESC_QUOTE : _tr_quote,
|
410 |
+
ESC_QUOTE2 : _tr_quote2,
|
411 |
+
ESC_PAREN : _tr_paren }
|
412 |
+
|
413 |
+
class EscapedCommand(TokenTransformBase):
|
414 |
+
"""Transformer for escaped commands like %foo, !foo, or /foo"""
|
415 |
+
@classmethod
|
416 |
+
def find(cls, tokens_by_line):
|
417 |
+
"""Find the first escaped command (%foo, !foo, etc.) in the cell.
|
418 |
+
"""
|
419 |
+
for line in tokens_by_line:
|
420 |
+
if not line:
|
421 |
+
continue
|
422 |
+
ix = 0
|
423 |
+
ll = len(line)
|
424 |
+
while ll > ix and line[ix].type in {tokenize.INDENT, tokenize.DEDENT}:
|
425 |
+
ix += 1
|
426 |
+
if ix >= ll:
|
427 |
+
continue
|
428 |
+
if line[ix].string in ESCAPE_SINGLES:
|
429 |
+
return cls(line[ix].start)
|
430 |
+
|
431 |
+
def transform(self, lines):
|
432 |
+
"""Transform an escaped line found by the ``find()`` classmethod.
|
433 |
+
"""
|
434 |
+
start_line, start_col = self.start_line, self.start_col
|
435 |
+
|
436 |
+
indent = lines[start_line][:start_col]
|
437 |
+
end_line = find_end_of_continued_line(lines, start_line)
|
438 |
+
line = assemble_continued_line(lines, (start_line, start_col), end_line)
|
439 |
+
|
440 |
+
if len(line) > 1 and line[:2] in ESCAPE_DOUBLES:
|
441 |
+
escape, content = line[:2], line[2:]
|
442 |
+
else:
|
443 |
+
escape, content = line[:1], line[1:]
|
444 |
+
|
445 |
+
if escape in tr:
|
446 |
+
call = tr[escape](content)
|
447 |
+
else:
|
448 |
+
call = ''
|
449 |
+
|
450 |
+
lines_before = lines[:start_line]
|
451 |
+
new_line = indent + call + '\n'
|
452 |
+
lines_after = lines[end_line + 1:]
|
453 |
+
|
454 |
+
return lines_before + [new_line] + lines_after
|
455 |
+
|
456 |
+
|
457 |
+
_help_end_re = re.compile(
|
458 |
+
r"""(%{0,2}
|
459 |
+
(?!\d)[\w*]+ # Variable name
|
460 |
+
(\.(?!\d)[\w*]+|\[-?[0-9]+\])* # .etc.etc or [0], we only support literal integers.
|
461 |
+
)
|
462 |
+
(\?\??)$ # ? or ??
|
463 |
+
""",
|
464 |
+
re.VERBOSE,
|
465 |
+
)
|
466 |
+
|
467 |
+
|
468 |
+
class HelpEnd(TokenTransformBase):
|
469 |
+
"""Transformer for help syntax: obj? and obj??"""
|
470 |
+
# This needs to be higher priority (lower number) than EscapedCommand so
|
471 |
+
# that inspecting magics (%foo?) works.
|
472 |
+
priority = 5
|
473 |
+
|
474 |
+
def __init__(self, start, q_locn):
|
475 |
+
super().__init__(start)
|
476 |
+
self.q_line = q_locn[0] - 1 # Shift from 1-indexed to 0-indexed
|
477 |
+
self.q_col = q_locn[1]
|
478 |
+
|
479 |
+
@classmethod
|
480 |
+
def find(cls, tokens_by_line):
|
481 |
+
"""Find the first help command (foo?) in the cell.
|
482 |
+
"""
|
483 |
+
for line in tokens_by_line:
|
484 |
+
# Last token is NEWLINE; look at last but one
|
485 |
+
if len(line) > 2 and line[-2].string == '?':
|
486 |
+
# Find the first token that's not INDENT/DEDENT
|
487 |
+
ix = 0
|
488 |
+
while line[ix].type in {tokenize.INDENT, tokenize.DEDENT}:
|
489 |
+
ix += 1
|
490 |
+
return cls(line[ix].start, line[-2].start)
|
491 |
+
|
492 |
+
def transform(self, lines):
|
493 |
+
"""Transform a help command found by the ``find()`` classmethod.
|
494 |
+
"""
|
495 |
+
|
496 |
+
piece = "".join(lines[self.start_line : self.q_line + 1])
|
497 |
+
indent, content = piece[: self.start_col], piece[self.start_col :]
|
498 |
+
lines_before = lines[: self.start_line]
|
499 |
+
lines_after = lines[self.q_line + 1 :]
|
500 |
+
|
501 |
+
m = _help_end_re.search(content)
|
502 |
+
if not m:
|
503 |
+
raise SyntaxError(content)
|
504 |
+
assert m is not None, content
|
505 |
+
target = m.group(1)
|
506 |
+
esc = m.group(3)
|
507 |
+
|
508 |
+
|
509 |
+
call = _make_help_call(target, esc)
|
510 |
+
new_line = indent + call + '\n'
|
511 |
+
|
512 |
+
return lines_before + [new_line] + lines_after
|
513 |
+
|
514 |
+
def make_tokens_by_line(lines:List[str]):
|
515 |
+
"""Tokenize a series of lines and group tokens by line.
|
516 |
+
|
517 |
+
The tokens for a multiline Python string or expression are grouped as one
|
518 |
+
line. All lines except the last lines should keep their line ending ('\\n',
|
519 |
+
'\\r\\n') for this to properly work. Use `.splitlines(keeplineending=True)`
|
520 |
+
for example when passing block of text to this function.
|
521 |
+
|
522 |
+
"""
|
523 |
+
# NL tokens are used inside multiline expressions, but also after blank
|
524 |
+
# lines or comments. This is intentional - see https://bugs.python.org/issue17061
|
525 |
+
# We want to group the former case together but split the latter, so we
|
526 |
+
# track parentheses level, similar to the internals of tokenize.
|
527 |
+
|
528 |
+
# reexported from token on 3.7+
|
529 |
+
NEWLINE, NL = tokenize.NEWLINE, tokenize.NL # type: ignore
|
530 |
+
tokens_by_line: List[List[Any]] = [[]]
|
531 |
+
if len(lines) > 1 and not lines[0].endswith(("\n", "\r", "\r\n", "\x0b", "\x0c")):
|
532 |
+
warnings.warn(
|
533 |
+
"`make_tokens_by_line` received a list of lines which do not have lineending markers ('\\n', '\\r', '\\r\\n', '\\x0b', '\\x0c'), behavior will be unspecified",
|
534 |
+
stacklevel=2,
|
535 |
+
)
|
536 |
+
parenlev = 0
|
537 |
+
try:
|
538 |
+
for token in tokenutil.generate_tokens_catch_errors(
|
539 |
+
iter(lines).__next__, extra_errors_to_catch=["expected EOF"]
|
540 |
+
):
|
541 |
+
tokens_by_line[-1].append(token)
|
542 |
+
if (token.type == NEWLINE) \
|
543 |
+
or ((token.type == NL) and (parenlev <= 0)):
|
544 |
+
tokens_by_line.append([])
|
545 |
+
elif token.string in {'(', '[', '{'}:
|
546 |
+
parenlev += 1
|
547 |
+
elif token.string in {')', ']', '}'}:
|
548 |
+
if parenlev > 0:
|
549 |
+
parenlev -= 1
|
550 |
+
except tokenize.TokenError:
|
551 |
+
# Input ended in a multiline string or expression. That's OK for us.
|
552 |
+
pass
|
553 |
+
|
554 |
+
|
555 |
+
if not tokens_by_line[-1]:
|
556 |
+
tokens_by_line.pop()
|
557 |
+
|
558 |
+
|
559 |
+
return tokens_by_line
|
560 |
+
|
561 |
+
|
562 |
+
def has_sunken_brackets(tokens: List[tokenize.TokenInfo]):
|
563 |
+
"""Check if the depth of brackets in the list of tokens drops below 0"""
|
564 |
+
parenlev = 0
|
565 |
+
for token in tokens:
|
566 |
+
if token.string in {"(", "[", "{"}:
|
567 |
+
parenlev += 1
|
568 |
+
elif token.string in {")", "]", "}"}:
|
569 |
+
parenlev -= 1
|
570 |
+
if parenlev < 0:
|
571 |
+
return True
|
572 |
+
return False
|
573 |
+
|
574 |
+
|
575 |
+
def show_linewise_tokens(s: str):
|
576 |
+
"""For investigation and debugging"""
|
577 |
+
warnings.warn(
|
578 |
+
"show_linewise_tokens is deprecated since IPython 8.6",
|
579 |
+
DeprecationWarning,
|
580 |
+
stacklevel=2,
|
581 |
+
)
|
582 |
+
if not s.endswith("\n"):
|
583 |
+
s += "\n"
|
584 |
+
lines = s.splitlines(keepends=True)
|
585 |
+
for line in make_tokens_by_line(lines):
|
586 |
+
print("Line -------")
|
587 |
+
for tokinfo in line:
|
588 |
+
print(" ", tokinfo)
|
589 |
+
|
590 |
+
# Arbitrary limit to prevent getting stuck in infinite loops
|
591 |
+
TRANSFORM_LOOP_LIMIT = 500
|
592 |
+
|
593 |
+
class TransformerManager:
|
594 |
+
"""Applies various transformations to a cell or code block.
|
595 |
+
|
596 |
+
The key methods for external use are ``transform_cell()``
|
597 |
+
and ``check_complete()``.
|
598 |
+
"""
|
599 |
+
def __init__(self):
|
600 |
+
self.cleanup_transforms = [
|
601 |
+
leading_empty_lines,
|
602 |
+
leading_indent,
|
603 |
+
classic_prompt,
|
604 |
+
ipython_prompt,
|
605 |
+
]
|
606 |
+
self.line_transforms = [
|
607 |
+
cell_magic,
|
608 |
+
]
|
609 |
+
self.token_transformers = [
|
610 |
+
MagicAssign,
|
611 |
+
SystemAssign,
|
612 |
+
EscapedCommand,
|
613 |
+
HelpEnd,
|
614 |
+
]
|
615 |
+
|
616 |
+
def do_one_token_transform(self, lines):
|
617 |
+
"""Find and run the transform earliest in the code.
|
618 |
+
|
619 |
+
Returns (changed, lines).
|
620 |
+
|
621 |
+
This method is called repeatedly until changed is False, indicating
|
622 |
+
that all available transformations are complete.
|
623 |
+
|
624 |
+
The tokens following IPython special syntax might not be valid, so
|
625 |
+
the transformed code is retokenised every time to identify the next
|
626 |
+
piece of special syntax. Hopefully long code cells are mostly valid
|
627 |
+
Python, not using lots of IPython special syntax, so this shouldn't be
|
628 |
+
a performance issue.
|
629 |
+
"""
|
630 |
+
tokens_by_line = make_tokens_by_line(lines)
|
631 |
+
candidates = []
|
632 |
+
for transformer_cls in self.token_transformers:
|
633 |
+
transformer = transformer_cls.find(tokens_by_line)
|
634 |
+
if transformer:
|
635 |
+
candidates.append(transformer)
|
636 |
+
|
637 |
+
if not candidates:
|
638 |
+
# Nothing to transform
|
639 |
+
return False, lines
|
640 |
+
ordered_transformers = sorted(candidates, key=TokenTransformBase.sortby)
|
641 |
+
for transformer in ordered_transformers:
|
642 |
+
try:
|
643 |
+
return True, transformer.transform(lines)
|
644 |
+
except SyntaxError:
|
645 |
+
pass
|
646 |
+
return False, lines
|
647 |
+
|
648 |
+
def do_token_transforms(self, lines):
|
649 |
+
for _ in range(TRANSFORM_LOOP_LIMIT):
|
650 |
+
changed, lines = self.do_one_token_transform(lines)
|
651 |
+
if not changed:
|
652 |
+
return lines
|
653 |
+
|
654 |
+
raise RuntimeError("Input transformation still changing after "
|
655 |
+
"%d iterations. Aborting." % TRANSFORM_LOOP_LIMIT)
|
656 |
+
|
657 |
+
def transform_cell(self, cell: str) -> str:
|
658 |
+
"""Transforms a cell of input code"""
|
659 |
+
if not cell.endswith('\n'):
|
660 |
+
cell += '\n' # Ensure the cell has a trailing newline
|
661 |
+
lines = cell.splitlines(keepends=True)
|
662 |
+
for transform in self.cleanup_transforms + self.line_transforms:
|
663 |
+
lines = transform(lines)
|
664 |
+
|
665 |
+
lines = self.do_token_transforms(lines)
|
666 |
+
return ''.join(lines)
|
667 |
+
|
668 |
+
def check_complete(self, cell: str):
|
669 |
+
"""Return whether a block of code is ready to execute, or should be continued
|
670 |
+
|
671 |
+
Parameters
|
672 |
+
----------
|
673 |
+
cell : string
|
674 |
+
Python input code, which can be multiline.
|
675 |
+
|
676 |
+
Returns
|
677 |
+
-------
|
678 |
+
status : str
|
679 |
+
One of 'complete', 'incomplete', or 'invalid' if source is not a
|
680 |
+
prefix of valid code.
|
681 |
+
indent_spaces : int or None
|
682 |
+
The number of spaces by which to indent the next line of code. If
|
683 |
+
status is not 'incomplete', this is None.
|
684 |
+
"""
|
685 |
+
# Remember if the lines ends in a new line.
|
686 |
+
ends_with_newline = False
|
687 |
+
for character in reversed(cell):
|
688 |
+
if character == '\n':
|
689 |
+
ends_with_newline = True
|
690 |
+
break
|
691 |
+
elif character.strip():
|
692 |
+
break
|
693 |
+
else:
|
694 |
+
continue
|
695 |
+
|
696 |
+
if not ends_with_newline:
|
697 |
+
# Append an newline for consistent tokenization
|
698 |
+
# See https://bugs.python.org/issue33899
|
699 |
+
cell += '\n'
|
700 |
+
|
701 |
+
lines = cell.splitlines(keepends=True)
|
702 |
+
|
703 |
+
if not lines:
|
704 |
+
return 'complete', None
|
705 |
+
|
706 |
+
for line in reversed(lines):
|
707 |
+
if not line.strip():
|
708 |
+
continue
|
709 |
+
elif line.strip("\n").endswith("\\"):
|
710 |
+
return "incomplete", find_last_indent(lines)
|
711 |
+
else:
|
712 |
+
break
|
713 |
+
|
714 |
+
try:
|
715 |
+
for transform in self.cleanup_transforms:
|
716 |
+
if not getattr(transform, 'has_side_effects', False):
|
717 |
+
lines = transform(lines)
|
718 |
+
except SyntaxError:
|
719 |
+
return 'invalid', None
|
720 |
+
|
721 |
+
if lines[0].startswith('%%'):
|
722 |
+
# Special case for cell magics - completion marked by blank line
|
723 |
+
if lines[-1].strip():
|
724 |
+
return 'incomplete', find_last_indent(lines)
|
725 |
+
else:
|
726 |
+
return 'complete', None
|
727 |
+
|
728 |
+
try:
|
729 |
+
for transform in self.line_transforms:
|
730 |
+
if not getattr(transform, 'has_side_effects', False):
|
731 |
+
lines = transform(lines)
|
732 |
+
lines = self.do_token_transforms(lines)
|
733 |
+
except SyntaxError:
|
734 |
+
return 'invalid', None
|
735 |
+
|
736 |
+
tokens_by_line = make_tokens_by_line(lines)
|
737 |
+
|
738 |
+
# Bail if we got one line and there are more closing parentheses than
|
739 |
+
# the opening ones
|
740 |
+
if (
|
741 |
+
len(lines) == 1
|
742 |
+
and tokens_by_line
|
743 |
+
and has_sunken_brackets(tokens_by_line[0])
|
744 |
+
):
|
745 |
+
return "invalid", None
|
746 |
+
|
747 |
+
if not tokens_by_line:
|
748 |
+
return 'incomplete', find_last_indent(lines)
|
749 |
+
|
750 |
+
if (
|
751 |
+
tokens_by_line[-1][-1].type != tokenize.ENDMARKER
|
752 |
+
and tokens_by_line[-1][-1].type != tokenize.ERRORTOKEN
|
753 |
+
):
|
754 |
+
# We're in a multiline string or expression
|
755 |
+
return 'incomplete', find_last_indent(lines)
|
756 |
+
|
757 |
+
newline_types = {tokenize.NEWLINE, tokenize.COMMENT, tokenize.ENDMARKER} # type: ignore
|
758 |
+
|
759 |
+
# Pop the last line which only contains DEDENTs and ENDMARKER
|
760 |
+
last_token_line = None
|
761 |
+
if {t.type for t in tokens_by_line[-1]} in [
|
762 |
+
{tokenize.DEDENT, tokenize.ENDMARKER},
|
763 |
+
{tokenize.ENDMARKER}
|
764 |
+
] and len(tokens_by_line) > 1:
|
765 |
+
last_token_line = tokens_by_line.pop()
|
766 |
+
|
767 |
+
while tokens_by_line[-1] and tokens_by_line[-1][-1].type in newline_types:
|
768 |
+
tokens_by_line[-1].pop()
|
769 |
+
|
770 |
+
if not tokens_by_line[-1]:
|
771 |
+
return 'incomplete', find_last_indent(lines)
|
772 |
+
|
773 |
+
if tokens_by_line[-1][-1].string == ':':
|
774 |
+
# The last line starts a block (e.g. 'if foo:')
|
775 |
+
ix = 0
|
776 |
+
while tokens_by_line[-1][ix].type in {tokenize.INDENT, tokenize.DEDENT}:
|
777 |
+
ix += 1
|
778 |
+
|
779 |
+
indent = tokens_by_line[-1][ix].start[1]
|
780 |
+
return 'incomplete', indent + 4
|
781 |
+
|
782 |
+
if tokens_by_line[-1][0].line.endswith('\\'):
|
783 |
+
return 'incomplete', None
|
784 |
+
|
785 |
+
# At this point, our checks think the code is complete (or invalid).
|
786 |
+
# We'll use codeop.compile_command to check this with the real parser
|
787 |
+
try:
|
788 |
+
with warnings.catch_warnings():
|
789 |
+
warnings.simplefilter('error', SyntaxWarning)
|
790 |
+
res = compile_command(''.join(lines), symbol='exec')
|
791 |
+
except (SyntaxError, OverflowError, ValueError, TypeError,
|
792 |
+
MemoryError, SyntaxWarning):
|
793 |
+
return 'invalid', None
|
794 |
+
else:
|
795 |
+
if res is None:
|
796 |
+
return 'incomplete', find_last_indent(lines)
|
797 |
+
|
798 |
+
if last_token_line and last_token_line[0].type == tokenize.DEDENT:
|
799 |
+
if ends_with_newline:
|
800 |
+
return 'complete', None
|
801 |
+
return 'incomplete', find_last_indent(lines)
|
802 |
+
|
803 |
+
# If there's a blank line at the end, assume we're ready to execute
|
804 |
+
if not lines[-1].strip():
|
805 |
+
return 'complete', None
|
806 |
+
|
807 |
+
return 'complete', None
|
808 |
+
|
809 |
+
|
810 |
+
def find_last_indent(lines):
|
811 |
+
m = _indent_re.match(lines[-1])
|
812 |
+
if not m:
|
813 |
+
return 0
|
814 |
+
return len(m.group(0).replace('\t', ' '*4))
|
815 |
+
|
816 |
+
|
817 |
+
class MaybeAsyncCompile(Compile):
|
818 |
+
def __init__(self, extra_flags=0):
|
819 |
+
super().__init__()
|
820 |
+
self.flags |= extra_flags
|
821 |
+
|
822 |
+
|
823 |
+
class MaybeAsyncCommandCompiler(CommandCompiler):
|
824 |
+
def __init__(self, extra_flags=0):
|
825 |
+
self.compiler = MaybeAsyncCompile(extra_flags=extra_flags)
|
826 |
+
|
827 |
+
|
828 |
+
_extra_flags = ast.PyCF_ALLOW_TOP_LEVEL_AWAIT
|
829 |
+
|
830 |
+
compile_command = MaybeAsyncCommandCompiler(extra_flags=_extra_flags)
|
openalex_env_map/lib/python3.10/site-packages/IPython/core/interactiveshell.py
ADDED
The diff for this file is too large to render.
See raw diff
|
|
openalex_env_map/lib/python3.10/site-packages/IPython/core/latex_symbols.py
ADDED
@@ -0,0 +1,1301 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# encoding: utf-8
|
2 |
+
|
3 |
+
# DO NOT EDIT THIS FILE BY HAND.
|
4 |
+
|
5 |
+
# To update this file, run the script /tools/gen_latex_symbols.py using Python 3
|
6 |
+
|
7 |
+
# This file is autogenerated from the file:
|
8 |
+
# https://raw.githubusercontent.com/JuliaLang/julia/master/base/latex_symbols.jl
|
9 |
+
# This original list is filtered to remove any unicode characters that are not valid
|
10 |
+
# Python identifiers.
|
11 |
+
|
12 |
+
latex_symbols = {
|
13 |
+
|
14 |
+
"\\euler" : "ℯ",
|
15 |
+
"\\^a" : "ᵃ",
|
16 |
+
"\\^b" : "ᵇ",
|
17 |
+
"\\^c" : "ᶜ",
|
18 |
+
"\\^d" : "ᵈ",
|
19 |
+
"\\^e" : "ᵉ",
|
20 |
+
"\\^f" : "ᶠ",
|
21 |
+
"\\^g" : "ᵍ",
|
22 |
+
"\\^h" : "ʰ",
|
23 |
+
"\\^i" : "ⁱ",
|
24 |
+
"\\^j" : "ʲ",
|
25 |
+
"\\^k" : "ᵏ",
|
26 |
+
"\\^l" : "ˡ",
|
27 |
+
"\\^m" : "ᵐ",
|
28 |
+
"\\^n" : "ⁿ",
|
29 |
+
"\\^o" : "ᵒ",
|
30 |
+
"\\^p" : "ᵖ",
|
31 |
+
"\\^r" : "ʳ",
|
32 |
+
"\\^s" : "ˢ",
|
33 |
+
"\\^t" : "ᵗ",
|
34 |
+
"\\^u" : "ᵘ",
|
35 |
+
"\\^v" : "ᵛ",
|
36 |
+
"\\^w" : "ʷ",
|
37 |
+
"\\^x" : "ˣ",
|
38 |
+
"\\^y" : "ʸ",
|
39 |
+
"\\^z" : "ᶻ",
|
40 |
+
"\\^A" : "ᴬ",
|
41 |
+
"\\^B" : "ᴮ",
|
42 |
+
"\\^D" : "ᴰ",
|
43 |
+
"\\^E" : "ᴱ",
|
44 |
+
"\\^G" : "ᴳ",
|
45 |
+
"\\^H" : "ᴴ",
|
46 |
+
"\\^I" : "ᴵ",
|
47 |
+
"\\^J" : "ᴶ",
|
48 |
+
"\\^K" : "ᴷ",
|
49 |
+
"\\^L" : "ᴸ",
|
50 |
+
"\\^M" : "ᴹ",
|
51 |
+
"\\^N" : "ᴺ",
|
52 |
+
"\\^O" : "ᴼ",
|
53 |
+
"\\^P" : "ᴾ",
|
54 |
+
"\\^R" : "ᴿ",
|
55 |
+
"\\^T" : "ᵀ",
|
56 |
+
"\\^U" : "ᵁ",
|
57 |
+
"\\^V" : "ⱽ",
|
58 |
+
"\\^W" : "ᵂ",
|
59 |
+
"\\^alpha" : "ᵅ",
|
60 |
+
"\\^beta" : "ᵝ",
|
61 |
+
"\\^gamma" : "ᵞ",
|
62 |
+
"\\^delta" : "ᵟ",
|
63 |
+
"\\^epsilon" : "ᵋ",
|
64 |
+
"\\^theta" : "ᶿ",
|
65 |
+
"\\^iota" : "ᶥ",
|
66 |
+
"\\^phi" : "ᵠ",
|
67 |
+
"\\^chi" : "ᵡ",
|
68 |
+
"\\^Phi" : "ᶲ",
|
69 |
+
"\\_a" : "ₐ",
|
70 |
+
"\\_e" : "ₑ",
|
71 |
+
"\\_h" : "ₕ",
|
72 |
+
"\\_i" : "ᵢ",
|
73 |
+
"\\_j" : "ⱼ",
|
74 |
+
"\\_k" : "ₖ",
|
75 |
+
"\\_l" : "ₗ",
|
76 |
+
"\\_m" : "ₘ",
|
77 |
+
"\\_n" : "ₙ",
|
78 |
+
"\\_o" : "ₒ",
|
79 |
+
"\\_p" : "ₚ",
|
80 |
+
"\\_r" : "ᵣ",
|
81 |
+
"\\_s" : "ₛ",
|
82 |
+
"\\_t" : "ₜ",
|
83 |
+
"\\_u" : "ᵤ",
|
84 |
+
"\\_v" : "ᵥ",
|
85 |
+
"\\_x" : "ₓ",
|
86 |
+
"\\_schwa" : "ₔ",
|
87 |
+
"\\_beta" : "ᵦ",
|
88 |
+
"\\_gamma" : "ᵧ",
|
89 |
+
"\\_rho" : "ᵨ",
|
90 |
+
"\\_phi" : "ᵩ",
|
91 |
+
"\\_chi" : "ᵪ",
|
92 |
+
"\\hbar" : "ħ",
|
93 |
+
"\\sout" : "̶",
|
94 |
+
"\\ordfeminine" : "ª",
|
95 |
+
"\\cdotp" : "·",
|
96 |
+
"\\ordmasculine" : "º",
|
97 |
+
"\\AA" : "Å",
|
98 |
+
"\\AE" : "Æ",
|
99 |
+
"\\DH" : "Ð",
|
100 |
+
"\\O" : "Ø",
|
101 |
+
"\\TH" : "Þ",
|
102 |
+
"\\ss" : "ß",
|
103 |
+
"\\aa" : "å",
|
104 |
+
"\\ae" : "æ",
|
105 |
+
"\\eth" : "ð",
|
106 |
+
"\\dh" : "ð",
|
107 |
+
"\\o" : "ø",
|
108 |
+
"\\th" : "þ",
|
109 |
+
"\\DJ" : "Đ",
|
110 |
+
"\\dj" : "đ",
|
111 |
+
"\\imath" : "ı",
|
112 |
+
"\\jmath" : "ȷ",
|
113 |
+
"\\L" : "Ł",
|
114 |
+
"\\l" : "ł",
|
115 |
+
"\\NG" : "Ŋ",
|
116 |
+
"\\ng" : "ŋ",
|
117 |
+
"\\OE" : "Œ",
|
118 |
+
"\\oe" : "œ",
|
119 |
+
"\\hvlig" : "ƕ",
|
120 |
+
"\\nrleg" : "ƞ",
|
121 |
+
"\\doublepipe" : "ǂ",
|
122 |
+
"\\trna" : "ɐ",
|
123 |
+
"\\trnsa" : "ɒ",
|
124 |
+
"\\openo" : "ɔ",
|
125 |
+
"\\rtld" : "ɖ",
|
126 |
+
"\\schwa" : "ə",
|
127 |
+
"\\varepsilon" : "ε",
|
128 |
+
"\\pgamma" : "ɣ",
|
129 |
+
"\\pbgam" : "ɤ",
|
130 |
+
"\\trnh" : "ɥ",
|
131 |
+
"\\btdl" : "ɬ",
|
132 |
+
"\\rtll" : "ɭ",
|
133 |
+
"\\trnm" : "ɯ",
|
134 |
+
"\\trnmlr" : "ɰ",
|
135 |
+
"\\ltlmr" : "ɱ",
|
136 |
+
"\\ltln" : "ɲ",
|
137 |
+
"\\rtln" : "ɳ",
|
138 |
+
"\\clomeg" : "ɷ",
|
139 |
+
"\\ltphi" : "ɸ",
|
140 |
+
"\\trnr" : "ɹ",
|
141 |
+
"\\trnrl" : "ɺ",
|
142 |
+
"\\rttrnr" : "ɻ",
|
143 |
+
"\\rl" : "ɼ",
|
144 |
+
"\\rtlr" : "ɽ",
|
145 |
+
"\\fhr" : "ɾ",
|
146 |
+
"\\rtls" : "ʂ",
|
147 |
+
"\\esh" : "ʃ",
|
148 |
+
"\\trnt" : "ʇ",
|
149 |
+
"\\rtlt" : "ʈ",
|
150 |
+
"\\pupsil" : "ʊ",
|
151 |
+
"\\pscrv" : "ʋ",
|
152 |
+
"\\invv" : "ʌ",
|
153 |
+
"\\invw" : "ʍ",
|
154 |
+
"\\trny" : "ʎ",
|
155 |
+
"\\rtlz" : "ʐ",
|
156 |
+
"\\yogh" : "ʒ",
|
157 |
+
"\\glst" : "ʔ",
|
158 |
+
"\\reglst" : "ʕ",
|
159 |
+
"\\inglst" : "ʖ",
|
160 |
+
"\\turnk" : "ʞ",
|
161 |
+
"\\dyogh" : "ʤ",
|
162 |
+
"\\tesh" : "ʧ",
|
163 |
+
"\\rasp" : "ʼ",
|
164 |
+
"\\verts" : "ˈ",
|
165 |
+
"\\verti" : "ˌ",
|
166 |
+
"\\lmrk" : "ː",
|
167 |
+
"\\hlmrk" : "ˑ",
|
168 |
+
"\\grave" : "̀",
|
169 |
+
"\\acute" : "́",
|
170 |
+
"\\hat" : "̂",
|
171 |
+
"\\tilde" : "̃",
|
172 |
+
"\\bar" : "̄",
|
173 |
+
"\\breve" : "̆",
|
174 |
+
"\\dot" : "̇",
|
175 |
+
"\\ddot" : "̈",
|
176 |
+
"\\ocirc" : "̊",
|
177 |
+
"\\H" : "̋",
|
178 |
+
"\\check" : "̌",
|
179 |
+
"\\palh" : "̡",
|
180 |
+
"\\rh" : "̢",
|
181 |
+
"\\c" : "̧",
|
182 |
+
"\\k" : "̨",
|
183 |
+
"\\sbbrg" : "̪",
|
184 |
+
"\\strike" : "̶",
|
185 |
+
"\\Alpha" : "Α",
|
186 |
+
"\\Beta" : "Β",
|
187 |
+
"\\Gamma" : "Γ",
|
188 |
+
"\\Delta" : "Δ",
|
189 |
+
"\\Epsilon" : "Ε",
|
190 |
+
"\\Zeta" : "Ζ",
|
191 |
+
"\\Eta" : "Η",
|
192 |
+
"\\Theta" : "Θ",
|
193 |
+
"\\Iota" : "Ι",
|
194 |
+
"\\Kappa" : "Κ",
|
195 |
+
"\\Lambda" : "Λ",
|
196 |
+
"\\Xi" : "Ξ",
|
197 |
+
"\\Pi" : "Π",
|
198 |
+
"\\Rho" : "Ρ",
|
199 |
+
"\\Sigma" : "Σ",
|
200 |
+
"\\Tau" : "Τ",
|
201 |
+
"\\Upsilon" : "Υ",
|
202 |
+
"\\Phi" : "Φ",
|
203 |
+
"\\Chi" : "Χ",
|
204 |
+
"\\Psi" : "Ψ",
|
205 |
+
"\\Omega" : "Ω",
|
206 |
+
"\\alpha" : "α",
|
207 |
+
"\\beta" : "β",
|
208 |
+
"\\gamma" : "γ",
|
209 |
+
"\\delta" : "δ",
|
210 |
+
"\\zeta" : "ζ",
|
211 |
+
"\\eta" : "η",
|
212 |
+
"\\theta" : "θ",
|
213 |
+
"\\iota" : "ι",
|
214 |
+
"\\kappa" : "κ",
|
215 |
+
"\\lambda" : "λ",
|
216 |
+
"\\mu" : "μ",
|
217 |
+
"\\nu" : "ν",
|
218 |
+
"\\xi" : "ξ",
|
219 |
+
"\\pi" : "π",
|
220 |
+
"\\rho" : "ρ",
|
221 |
+
"\\varsigma" : "ς",
|
222 |
+
"\\sigma" : "σ",
|
223 |
+
"\\tau" : "τ",
|
224 |
+
"\\upsilon" : "υ",
|
225 |
+
"\\varphi" : "φ",
|
226 |
+
"\\chi" : "χ",
|
227 |
+
"\\psi" : "ψ",
|
228 |
+
"\\omega" : "ω",
|
229 |
+
"\\vartheta" : "ϑ",
|
230 |
+
"\\phi" : "ϕ",
|
231 |
+
"\\varpi" : "ϖ",
|
232 |
+
"\\Stigma" : "Ϛ",
|
233 |
+
"\\Digamma" : "Ϝ",
|
234 |
+
"\\digamma" : "ϝ",
|
235 |
+
"\\Koppa" : "Ϟ",
|
236 |
+
"\\Sampi" : "Ϡ",
|
237 |
+
"\\varkappa" : "ϰ",
|
238 |
+
"\\varrho" : "ϱ",
|
239 |
+
"\\varTheta" : "ϴ",
|
240 |
+
"\\epsilon" : "ϵ",
|
241 |
+
"\\dddot" : "⃛",
|
242 |
+
"\\ddddot" : "⃜",
|
243 |
+
"\\hslash" : "ℏ",
|
244 |
+
"\\Im" : "ℑ",
|
245 |
+
"\\ell" : "ℓ",
|
246 |
+
"\\wp" : "℘",
|
247 |
+
"\\Re" : "ℜ",
|
248 |
+
"\\aleph" : "ℵ",
|
249 |
+
"\\beth" : "ℶ",
|
250 |
+
"\\gimel" : "ℷ",
|
251 |
+
"\\daleth" : "ℸ",
|
252 |
+
"\\bbPi" : "ℿ",
|
253 |
+
"\\Zbar" : "Ƶ",
|
254 |
+
"\\overbar" : "̅",
|
255 |
+
"\\ovhook" : "̉",
|
256 |
+
"\\candra" : "̐",
|
257 |
+
"\\oturnedcomma" : "̒",
|
258 |
+
"\\ocommatopright" : "̕",
|
259 |
+
"\\droang" : "̚",
|
260 |
+
"\\wideutilde" : "̰",
|
261 |
+
"\\not" : "̸",
|
262 |
+
"\\upMu" : "Μ",
|
263 |
+
"\\upNu" : "Ν",
|
264 |
+
"\\upOmicron" : "Ο",
|
265 |
+
"\\upepsilon" : "ε",
|
266 |
+
"\\upomicron" : "ο",
|
267 |
+
"\\upvarbeta" : "ϐ",
|
268 |
+
"\\upoldKoppa" : "Ϙ",
|
269 |
+
"\\upoldkoppa" : "ϙ",
|
270 |
+
"\\upstigma" : "ϛ",
|
271 |
+
"\\upkoppa" : "ϟ",
|
272 |
+
"\\upsampi" : "ϡ",
|
273 |
+
"\\tieconcat" : "⁀",
|
274 |
+
"\\leftharpoonaccent" : "⃐",
|
275 |
+
"\\rightharpoonaccent" : "⃑",
|
276 |
+
"\\vertoverlay" : "⃒",
|
277 |
+
"\\overleftarrow" : "⃖",
|
278 |
+
"\\vec" : "⃗",
|
279 |
+
"\\overleftrightarrow" : "⃡",
|
280 |
+
"\\annuity" : "⃧",
|
281 |
+
"\\threeunderdot" : "⃨",
|
282 |
+
"\\widebridgeabove" : "⃩",
|
283 |
+
"\\bbC" : "ℂ",
|
284 |
+
"\\eulermascheroni" : "ℇ",
|
285 |
+
"\\scrg" : "ℊ",
|
286 |
+
"\\scrH" : "ℋ",
|
287 |
+
"\\frakH" : "ℌ",
|
288 |
+
"\\bbH" : "ℍ",
|
289 |
+
"\\planck" : "ℎ",
|
290 |
+
"\\scrI" : "ℐ",
|
291 |
+
"\\scrL" : "ℒ",
|
292 |
+
"\\bbN" : "ℕ",
|
293 |
+
"\\bbP" : "ℙ",
|
294 |
+
"\\bbQ" : "ℚ",
|
295 |
+
"\\scrR" : "ℛ",
|
296 |
+
"\\bbR" : "ℝ",
|
297 |
+
"\\bbZ" : "ℤ",
|
298 |
+
"\\frakZ" : "ℨ",
|
299 |
+
"\\Angstrom" : "Å",
|
300 |
+
"\\scrB" : "ℬ",
|
301 |
+
"\\frakC" : "ℭ",
|
302 |
+
"\\scre" : "ℯ",
|
303 |
+
"\\scrE" : "ℰ",
|
304 |
+
"\\scrF" : "ℱ",
|
305 |
+
"\\Finv" : "Ⅎ",
|
306 |
+
"\\scrM" : "ℳ",
|
307 |
+
"\\scro" : "ℴ",
|
308 |
+
"\\bbgamma" : "ℽ",
|
309 |
+
"\\bbGamma" : "ℾ",
|
310 |
+
"\\bbiD" : "ⅅ",
|
311 |
+
"\\bbid" : "ⅆ",
|
312 |
+
"\\bbie" : "ⅇ",
|
313 |
+
"\\bbii" : "ⅈ",
|
314 |
+
"\\bbij" : "ⅉ",
|
315 |
+
"\\bfA" : "𝐀",
|
316 |
+
"\\bfB" : "𝐁",
|
317 |
+
"\\bfC" : "𝐂",
|
318 |
+
"\\bfD" : "𝐃",
|
319 |
+
"\\bfE" : "𝐄",
|
320 |
+
"\\bfF" : "𝐅",
|
321 |
+
"\\bfG" : "𝐆",
|
322 |
+
"\\bfH" : "𝐇",
|
323 |
+
"\\bfI" : "𝐈",
|
324 |
+
"\\bfJ" : "𝐉",
|
325 |
+
"\\bfK" : "𝐊",
|
326 |
+
"\\bfL" : "𝐋",
|
327 |
+
"\\bfM" : "𝐌",
|
328 |
+
"\\bfN" : "𝐍",
|
329 |
+
"\\bfO" : "𝐎",
|
330 |
+
"\\bfP" : "𝐏",
|
331 |
+
"\\bfQ" : "𝐐",
|
332 |
+
"\\bfR" : "𝐑",
|
333 |
+
"\\bfS" : "𝐒",
|
334 |
+
"\\bfT" : "𝐓",
|
335 |
+
"\\bfU" : "𝐔",
|
336 |
+
"\\bfV" : "𝐕",
|
337 |
+
"\\bfW" : "𝐖",
|
338 |
+
"\\bfX" : "𝐗",
|
339 |
+
"\\bfY" : "𝐘",
|
340 |
+
"\\bfZ" : "𝐙",
|
341 |
+
"\\bfa" : "𝐚",
|
342 |
+
"\\bfb" : "𝐛",
|
343 |
+
"\\bfc" : "𝐜",
|
344 |
+
"\\bfd" : "𝐝",
|
345 |
+
"\\bfe" : "𝐞",
|
346 |
+
"\\bff" : "𝐟",
|
347 |
+
"\\bfg" : "𝐠",
|
348 |
+
"\\bfh" : "𝐡",
|
349 |
+
"\\bfi" : "𝐢",
|
350 |
+
"\\bfj" : "𝐣",
|
351 |
+
"\\bfk" : "𝐤",
|
352 |
+
"\\bfl" : "𝐥",
|
353 |
+
"\\bfm" : "𝐦",
|
354 |
+
"\\bfn" : "𝐧",
|
355 |
+
"\\bfo" : "𝐨",
|
356 |
+
"\\bfp" : "𝐩",
|
357 |
+
"\\bfq" : "𝐪",
|
358 |
+
"\\bfr" : "𝐫",
|
359 |
+
"\\bfs" : "𝐬",
|
360 |
+
"\\bft" : "𝐭",
|
361 |
+
"\\bfu" : "𝐮",
|
362 |
+
"\\bfv" : "𝐯",
|
363 |
+
"\\bfw" : "𝐰",
|
364 |
+
"\\bfx" : "𝐱",
|
365 |
+
"\\bfy" : "𝐲",
|
366 |
+
"\\bfz" : "𝐳",
|
367 |
+
"\\itA" : "𝐴",
|
368 |
+
"\\itB" : "𝐵",
|
369 |
+
"\\itC" : "𝐶",
|
370 |
+
"\\itD" : "𝐷",
|
371 |
+
"\\itE" : "𝐸",
|
372 |
+
"\\itF" : "𝐹",
|
373 |
+
"\\itG" : "𝐺",
|
374 |
+
"\\itH" : "𝐻",
|
375 |
+
"\\itI" : "𝐼",
|
376 |
+
"\\itJ" : "𝐽",
|
377 |
+
"\\itK" : "𝐾",
|
378 |
+
"\\itL" : "𝐿",
|
379 |
+
"\\itM" : "𝑀",
|
380 |
+
"\\itN" : "𝑁",
|
381 |
+
"\\itO" : "𝑂",
|
382 |
+
"\\itP" : "𝑃",
|
383 |
+
"\\itQ" : "𝑄",
|
384 |
+
"\\itR" : "𝑅",
|
385 |
+
"\\itS" : "𝑆",
|
386 |
+
"\\itT" : "𝑇",
|
387 |
+
"\\itU" : "𝑈",
|
388 |
+
"\\itV" : "𝑉",
|
389 |
+
"\\itW" : "𝑊",
|
390 |
+
"\\itX" : "𝑋",
|
391 |
+
"\\itY" : "𝑌",
|
392 |
+
"\\itZ" : "𝑍",
|
393 |
+
"\\ita" : "𝑎",
|
394 |
+
"\\itb" : "𝑏",
|
395 |
+
"\\itc" : "𝑐",
|
396 |
+
"\\itd" : "𝑑",
|
397 |
+
"\\ite" : "𝑒",
|
398 |
+
"\\itf" : "𝑓",
|
399 |
+
"\\itg" : "𝑔",
|
400 |
+
"\\iti" : "𝑖",
|
401 |
+
"\\itj" : "𝑗",
|
402 |
+
"\\itk" : "𝑘",
|
403 |
+
"\\itl" : "𝑙",
|
404 |
+
"\\itm" : "𝑚",
|
405 |
+
"\\itn" : "𝑛",
|
406 |
+
"\\ito" : "𝑜",
|
407 |
+
"\\itp" : "𝑝",
|
408 |
+
"\\itq" : "𝑞",
|
409 |
+
"\\itr" : "𝑟",
|
410 |
+
"\\its" : "𝑠",
|
411 |
+
"\\itt" : "𝑡",
|
412 |
+
"\\itu" : "𝑢",
|
413 |
+
"\\itv" : "𝑣",
|
414 |
+
"\\itw" : "𝑤",
|
415 |
+
"\\itx" : "𝑥",
|
416 |
+
"\\ity" : "𝑦",
|
417 |
+
"\\itz" : "𝑧",
|
418 |
+
"\\biA" : "𝑨",
|
419 |
+
"\\biB" : "𝑩",
|
420 |
+
"\\biC" : "𝑪",
|
421 |
+
"\\biD" : "𝑫",
|
422 |
+
"\\biE" : "𝑬",
|
423 |
+
"\\biF" : "𝑭",
|
424 |
+
"\\biG" : "𝑮",
|
425 |
+
"\\biH" : "𝑯",
|
426 |
+
"\\biI" : "𝑰",
|
427 |
+
"\\biJ" : "𝑱",
|
428 |
+
"\\biK" : "𝑲",
|
429 |
+
"\\biL" : "𝑳",
|
430 |
+
"\\biM" : "𝑴",
|
431 |
+
"\\biN" : "𝑵",
|
432 |
+
"\\biO" : "𝑶",
|
433 |
+
"\\biP" : "𝑷",
|
434 |
+
"\\biQ" : "𝑸",
|
435 |
+
"\\biR" : "𝑹",
|
436 |
+
"\\biS" : "𝑺",
|
437 |
+
"\\biT" : "𝑻",
|
438 |
+
"\\biU" : "𝑼",
|
439 |
+
"\\biV" : "𝑽",
|
440 |
+
"\\biW" : "𝑾",
|
441 |
+
"\\biX" : "𝑿",
|
442 |
+
"\\biY" : "𝒀",
|
443 |
+
"\\biZ" : "𝒁",
|
444 |
+
"\\bia" : "𝒂",
|
445 |
+
"\\bib" : "𝒃",
|
446 |
+
"\\bic" : "𝒄",
|
447 |
+
"\\bid" : "𝒅",
|
448 |
+
"\\bie" : "𝒆",
|
449 |
+
"\\bif" : "𝒇",
|
450 |
+
"\\big" : "𝒈",
|
451 |
+
"\\bih" : "𝒉",
|
452 |
+
"\\bii" : "𝒊",
|
453 |
+
"\\bij" : "𝒋",
|
454 |
+
"\\bik" : "𝒌",
|
455 |
+
"\\bil" : "𝒍",
|
456 |
+
"\\bim" : "𝒎",
|
457 |
+
"\\bin" : "𝒏",
|
458 |
+
"\\bio" : "𝒐",
|
459 |
+
"\\bip" : "𝒑",
|
460 |
+
"\\biq" : "𝒒",
|
461 |
+
"\\bir" : "𝒓",
|
462 |
+
"\\bis" : "𝒔",
|
463 |
+
"\\bit" : "𝒕",
|
464 |
+
"\\biu" : "𝒖",
|
465 |
+
"\\biv" : "𝒗",
|
466 |
+
"\\biw" : "𝒘",
|
467 |
+
"\\bix" : "𝒙",
|
468 |
+
"\\biy" : "𝒚",
|
469 |
+
"\\biz" : "𝒛",
|
470 |
+
"\\scrA" : "𝒜",
|
471 |
+
"\\scrC" : "𝒞",
|
472 |
+
"\\scrD" : "𝒟",
|
473 |
+
"\\scrG" : "𝒢",
|
474 |
+
"\\scrJ" : "𝒥",
|
475 |
+
"\\scrK" : "𝒦",
|
476 |
+
"\\scrN" : "𝒩",
|
477 |
+
"\\scrO" : "𝒪",
|
478 |
+
"\\scrP" : "𝒫",
|
479 |
+
"\\scrQ" : "𝒬",
|
480 |
+
"\\scrS" : "𝒮",
|
481 |
+
"\\scrT" : "𝒯",
|
482 |
+
"\\scrU" : "𝒰",
|
483 |
+
"\\scrV" : "𝒱",
|
484 |
+
"\\scrW" : "𝒲",
|
485 |
+
"\\scrX" : "𝒳",
|
486 |
+
"\\scrY" : "𝒴",
|
487 |
+
"\\scrZ" : "𝒵",
|
488 |
+
"\\scra" : "𝒶",
|
489 |
+
"\\scrb" : "𝒷",
|
490 |
+
"\\scrc" : "𝒸",
|
491 |
+
"\\scrd" : "𝒹",
|
492 |
+
"\\scrf" : "𝒻",
|
493 |
+
"\\scrh" : "𝒽",
|
494 |
+
"\\scri" : "𝒾",
|
495 |
+
"\\scrj" : "𝒿",
|
496 |
+
"\\scrk" : "𝓀",
|
497 |
+
"\\scrm" : "𝓂",
|
498 |
+
"\\scrn" : "𝓃",
|
499 |
+
"\\scrp" : "𝓅",
|
500 |
+
"\\scrq" : "𝓆",
|
501 |
+
"\\scrr" : "𝓇",
|
502 |
+
"\\scrs" : "𝓈",
|
503 |
+
"\\scrt" : "𝓉",
|
504 |
+
"\\scru" : "𝓊",
|
505 |
+
"\\scrv" : "𝓋",
|
506 |
+
"\\scrw" : "𝓌",
|
507 |
+
"\\scrx" : "𝓍",
|
508 |
+
"\\scry" : "𝓎",
|
509 |
+
"\\scrz" : "𝓏",
|
510 |
+
"\\bscrA" : "𝓐",
|
511 |
+
"\\bscrB" : "𝓑",
|
512 |
+
"\\bscrC" : "𝓒",
|
513 |
+
"\\bscrD" : "𝓓",
|
514 |
+
"\\bscrE" : "𝓔",
|
515 |
+
"\\bscrF" : "𝓕",
|
516 |
+
"\\bscrG" : "𝓖",
|
517 |
+
"\\bscrH" : "𝓗",
|
518 |
+
"\\bscrI" : "𝓘",
|
519 |
+
"\\bscrJ" : "𝓙",
|
520 |
+
"\\bscrK" : "𝓚",
|
521 |
+
"\\bscrL" : "𝓛",
|
522 |
+
"\\bscrM" : "𝓜",
|
523 |
+
"\\bscrN" : "𝓝",
|
524 |
+
"\\bscrO" : "𝓞",
|
525 |
+
"\\bscrP" : "𝓟",
|
526 |
+
"\\bscrQ" : "𝓠",
|
527 |
+
"\\bscrR" : "𝓡",
|
528 |
+
"\\bscrS" : "𝓢",
|
529 |
+
"\\bscrT" : "𝓣",
|
530 |
+
"\\bscrU" : "𝓤",
|
531 |
+
"\\bscrV" : "𝓥",
|
532 |
+
"\\bscrW" : "𝓦",
|
533 |
+
"\\bscrX" : "𝓧",
|
534 |
+
"\\bscrY" : "𝓨",
|
535 |
+
"\\bscrZ" : "𝓩",
|
536 |
+
"\\bscra" : "𝓪",
|
537 |
+
"\\bscrb" : "𝓫",
|
538 |
+
"\\bscrc" : "𝓬",
|
539 |
+
"\\bscrd" : "𝓭",
|
540 |
+
"\\bscre" : "𝓮",
|
541 |
+
"\\bscrf" : "𝓯",
|
542 |
+
"\\bscrg" : "𝓰",
|
543 |
+
"\\bscrh" : "𝓱",
|
544 |
+
"\\bscri" : "𝓲",
|
545 |
+
"\\bscrj" : "𝓳",
|
546 |
+
"\\bscrk" : "𝓴",
|
547 |
+
"\\bscrl" : "𝓵",
|
548 |
+
"\\bscrm" : "𝓶",
|
549 |
+
"\\bscrn" : "𝓷",
|
550 |
+
"\\bscro" : "𝓸",
|
551 |
+
"\\bscrp" : "𝓹",
|
552 |
+
"\\bscrq" : "𝓺",
|
553 |
+
"\\bscrr" : "𝓻",
|
554 |
+
"\\bscrs" : "𝓼",
|
555 |
+
"\\bscrt" : "𝓽",
|
556 |
+
"\\bscru" : "𝓾",
|
557 |
+
"\\bscrv" : "𝓿",
|
558 |
+
"\\bscrw" : "𝔀",
|
559 |
+
"\\bscrx" : "𝔁",
|
560 |
+
"\\bscry" : "𝔂",
|
561 |
+
"\\bscrz" : "𝔃",
|
562 |
+
"\\frakA" : "𝔄",
|
563 |
+
"\\frakB" : "𝔅",
|
564 |
+
"\\frakD" : "𝔇",
|
565 |
+
"\\frakE" : "𝔈",
|
566 |
+
"\\frakF" : "𝔉",
|
567 |
+
"\\frakG" : "𝔊",
|
568 |
+
"\\frakJ" : "𝔍",
|
569 |
+
"\\frakK" : "𝔎",
|
570 |
+
"\\frakL" : "𝔏",
|
571 |
+
"\\frakM" : "𝔐",
|
572 |
+
"\\frakN" : "𝔑",
|
573 |
+
"\\frakO" : "𝔒",
|
574 |
+
"\\frakP" : "𝔓",
|
575 |
+
"\\frakQ" : "𝔔",
|
576 |
+
"\\frakS" : "𝔖",
|
577 |
+
"\\frakT" : "𝔗",
|
578 |
+
"\\frakU" : "𝔘",
|
579 |
+
"\\frakV" : "𝔙",
|
580 |
+
"\\frakW" : "𝔚",
|
581 |
+
"\\frakX" : "𝔛",
|
582 |
+
"\\frakY" : "𝔜",
|
583 |
+
"\\fraka" : "𝔞",
|
584 |
+
"\\frakb" : "𝔟",
|
585 |
+
"\\frakc" : "𝔠",
|
586 |
+
"\\frakd" : "𝔡",
|
587 |
+
"\\frake" : "𝔢",
|
588 |
+
"\\frakf" : "𝔣",
|
589 |
+
"\\frakg" : "𝔤",
|
590 |
+
"\\frakh" : "𝔥",
|
591 |
+
"\\fraki" : "𝔦",
|
592 |
+
"\\frakj" : "𝔧",
|
593 |
+
"\\frakk" : "𝔨",
|
594 |
+
"\\frakl" : "𝔩",
|
595 |
+
"\\frakm" : "𝔪",
|
596 |
+
"\\frakn" : "𝔫",
|
597 |
+
"\\frako" : "𝔬",
|
598 |
+
"\\frakp" : "𝔭",
|
599 |
+
"\\frakq" : "𝔮",
|
600 |
+
"\\frakr" : "𝔯",
|
601 |
+
"\\fraks" : "𝔰",
|
602 |
+
"\\frakt" : "𝔱",
|
603 |
+
"\\fraku" : "𝔲",
|
604 |
+
"\\frakv" : "𝔳",
|
605 |
+
"\\frakw" : "𝔴",
|
606 |
+
"\\frakx" : "𝔵",
|
607 |
+
"\\fraky" : "𝔶",
|
608 |
+
"\\frakz" : "𝔷",
|
609 |
+
"\\bbA" : "𝔸",
|
610 |
+
"\\bbB" : "𝔹",
|
611 |
+
"\\bbD" : "𝔻",
|
612 |
+
"\\bbE" : "𝔼",
|
613 |
+
"\\bbF" : "𝔽",
|
614 |
+
"\\bbG" : "𝔾",
|
615 |
+
"\\bbI" : "𝕀",
|
616 |
+
"\\bbJ" : "𝕁",
|
617 |
+
"\\bbK" : "𝕂",
|
618 |
+
"\\bbL" : "𝕃",
|
619 |
+
"\\bbM" : "𝕄",
|
620 |
+
"\\bbO" : "𝕆",
|
621 |
+
"\\bbS" : "𝕊",
|
622 |
+
"\\bbT" : "𝕋",
|
623 |
+
"\\bbU" : "𝕌",
|
624 |
+
"\\bbV" : "𝕍",
|
625 |
+
"\\bbW" : "𝕎",
|
626 |
+
"\\bbX" : "𝕏",
|
627 |
+
"\\bbY" : "𝕐",
|
628 |
+
"\\bba" : "𝕒",
|
629 |
+
"\\bbb" : "𝕓",
|
630 |
+
"\\bbc" : "𝕔",
|
631 |
+
"\\bbd" : "𝕕",
|
632 |
+
"\\bbe" : "𝕖",
|
633 |
+
"\\bbf" : "𝕗",
|
634 |
+
"\\bbg" : "𝕘",
|
635 |
+
"\\bbh" : "𝕙",
|
636 |
+
"\\bbi" : "𝕚",
|
637 |
+
"\\bbj" : "𝕛",
|
638 |
+
"\\bbk" : "𝕜",
|
639 |
+
"\\bbl" : "𝕝",
|
640 |
+
"\\bbm" : "𝕞",
|
641 |
+
"\\bbn" : "𝕟",
|
642 |
+
"\\bbo" : "𝕠",
|
643 |
+
"\\bbp" : "𝕡",
|
644 |
+
"\\bbq" : "𝕢",
|
645 |
+
"\\bbr" : "𝕣",
|
646 |
+
"\\bbs" : "𝕤",
|
647 |
+
"\\bbt" : "𝕥",
|
648 |
+
"\\bbu" : "𝕦",
|
649 |
+
"\\bbv" : "𝕧",
|
650 |
+
"\\bbw" : "𝕨",
|
651 |
+
"\\bbx" : "𝕩",
|
652 |
+
"\\bby" : "𝕪",
|
653 |
+
"\\bbz" : "𝕫",
|
654 |
+
"\\bfrakA" : "𝕬",
|
655 |
+
"\\bfrakB" : "𝕭",
|
656 |
+
"\\bfrakC" : "𝕮",
|
657 |
+
"\\bfrakD" : "𝕯",
|
658 |
+
"\\bfrakE" : "𝕰",
|
659 |
+
"\\bfrakF" : "𝕱",
|
660 |
+
"\\bfrakG" : "𝕲",
|
661 |
+
"\\bfrakH" : "𝕳",
|
662 |
+
"\\bfrakI" : "𝕴",
|
663 |
+
"\\bfrakJ" : "𝕵",
|
664 |
+
"\\bfrakK" : "𝕶",
|
665 |
+
"\\bfrakL" : "𝕷",
|
666 |
+
"\\bfrakM" : "𝕸",
|
667 |
+
"\\bfrakN" : "𝕹",
|
668 |
+
"\\bfrakO" : "𝕺",
|
669 |
+
"\\bfrakP" : "𝕻",
|
670 |
+
"\\bfrakQ" : "𝕼",
|
671 |
+
"\\bfrakR" : "𝕽",
|
672 |
+
"\\bfrakS" : "𝕾",
|
673 |
+
"\\bfrakT" : "𝕿",
|
674 |
+
"\\bfrakU" : "𝖀",
|
675 |
+
"\\bfrakV" : "𝖁",
|
676 |
+
"\\bfrakW" : "𝖂",
|
677 |
+
"\\bfrakX" : "𝖃",
|
678 |
+
"\\bfrakY" : "𝖄",
|
679 |
+
"\\bfrakZ" : "𝖅",
|
680 |
+
"\\bfraka" : "𝖆",
|
681 |
+
"\\bfrakb" : "𝖇",
|
682 |
+
"\\bfrakc" : "𝖈",
|
683 |
+
"\\bfrakd" : "𝖉",
|
684 |
+
"\\bfrake" : "𝖊",
|
685 |
+
"\\bfrakf" : "𝖋",
|
686 |
+
"\\bfrakg" : "𝖌",
|
687 |
+
"\\bfrakh" : "𝖍",
|
688 |
+
"\\bfraki" : "𝖎",
|
689 |
+
"\\bfrakj" : "𝖏",
|
690 |
+
"\\bfrakk" : "𝖐",
|
691 |
+
"\\bfrakl" : "𝖑",
|
692 |
+
"\\bfrakm" : "𝖒",
|
693 |
+
"\\bfrakn" : "𝖓",
|
694 |
+
"\\bfrako" : "𝖔",
|
695 |
+
"\\bfrakp" : "𝖕",
|
696 |
+
"\\bfrakq" : "𝖖",
|
697 |
+
"\\bfrakr" : "𝖗",
|
698 |
+
"\\bfraks" : "𝖘",
|
699 |
+
"\\bfrakt" : "𝖙",
|
700 |
+
"\\bfraku" : "𝖚",
|
701 |
+
"\\bfrakv" : "𝖛",
|
702 |
+
"\\bfrakw" : "𝖜",
|
703 |
+
"\\bfrakx" : "𝖝",
|
704 |
+
"\\bfraky" : "𝖞",
|
705 |
+
"\\bfrakz" : "𝖟",
|
706 |
+
"\\sansA" : "𝖠",
|
707 |
+
"\\sansB" : "𝖡",
|
708 |
+
"\\sansC" : "𝖢",
|
709 |
+
"\\sansD" : "𝖣",
|
710 |
+
"\\sansE" : "𝖤",
|
711 |
+
"\\sansF" : "𝖥",
|
712 |
+
"\\sansG" : "𝖦",
|
713 |
+
"\\sansH" : "𝖧",
|
714 |
+
"\\sansI" : "𝖨",
|
715 |
+
"\\sansJ" : "𝖩",
|
716 |
+
"\\sansK" : "𝖪",
|
717 |
+
"\\sansL" : "𝖫",
|
718 |
+
"\\sansM" : "𝖬",
|
719 |
+
"\\sansN" : "𝖭",
|
720 |
+
"\\sansO" : "𝖮",
|
721 |
+
"\\sansP" : "𝖯",
|
722 |
+
"\\sansQ" : "𝖰",
|
723 |
+
"\\sansR" : "𝖱",
|
724 |
+
"\\sansS" : "𝖲",
|
725 |
+
"\\sansT" : "𝖳",
|
726 |
+
"\\sansU" : "𝖴",
|
727 |
+
"\\sansV" : "𝖵",
|
728 |
+
"\\sansW" : "𝖶",
|
729 |
+
"\\sansX" : "𝖷",
|
730 |
+
"\\sansY" : "𝖸",
|
731 |
+
"\\sansZ" : "𝖹",
|
732 |
+
"\\sansa" : "𝖺",
|
733 |
+
"\\sansb" : "𝖻",
|
734 |
+
"\\sansc" : "𝖼",
|
735 |
+
"\\sansd" : "𝖽",
|
736 |
+
"\\sanse" : "𝖾",
|
737 |
+
"\\sansf" : "𝖿",
|
738 |
+
"\\sansg" : "𝗀",
|
739 |
+
"\\sansh" : "𝗁",
|
740 |
+
"\\sansi" : "𝗂",
|
741 |
+
"\\sansj" : "𝗃",
|
742 |
+
"\\sansk" : "𝗄",
|
743 |
+
"\\sansl" : "𝗅",
|
744 |
+
"\\sansm" : "𝗆",
|
745 |
+
"\\sansn" : "𝗇",
|
746 |
+
"\\sanso" : "𝗈",
|
747 |
+
"\\sansp" : "𝗉",
|
748 |
+
"\\sansq" : "𝗊",
|
749 |
+
"\\sansr" : "𝗋",
|
750 |
+
"\\sanss" : "𝗌",
|
751 |
+
"\\sanst" : "𝗍",
|
752 |
+
"\\sansu" : "𝗎",
|
753 |
+
"\\sansv" : "𝗏",
|
754 |
+
"\\sansw" : "𝗐",
|
755 |
+
"\\sansx" : "𝗑",
|
756 |
+
"\\sansy" : "𝗒",
|
757 |
+
"\\sansz" : "𝗓",
|
758 |
+
"\\bsansA" : "𝗔",
|
759 |
+
"\\bsansB" : "𝗕",
|
760 |
+
"\\bsansC" : "𝗖",
|
761 |
+
"\\bsansD" : "𝗗",
|
762 |
+
"\\bsansE" : "𝗘",
|
763 |
+
"\\bsansF" : "𝗙",
|
764 |
+
"\\bsansG" : "𝗚",
|
765 |
+
"\\bsansH" : "𝗛",
|
766 |
+
"\\bsansI" : "𝗜",
|
767 |
+
"\\bsansJ" : "𝗝",
|
768 |
+
"\\bsansK" : "𝗞",
|
769 |
+
"\\bsansL" : "𝗟",
|
770 |
+
"\\bsansM" : "𝗠",
|
771 |
+
"\\bsansN" : "𝗡",
|
772 |
+
"\\bsansO" : "𝗢",
|
773 |
+
"\\bsansP" : "𝗣",
|
774 |
+
"\\bsansQ" : "𝗤",
|
775 |
+
"\\bsansR" : "𝗥",
|
776 |
+
"\\bsansS" : "𝗦",
|
777 |
+
"\\bsansT" : "𝗧",
|
778 |
+
"\\bsansU" : "𝗨",
|
779 |
+
"\\bsansV" : "𝗩",
|
780 |
+
"\\bsansW" : "𝗪",
|
781 |
+
"\\bsansX" : "𝗫",
|
782 |
+
"\\bsansY" : "𝗬",
|
783 |
+
"\\bsansZ" : "𝗭",
|
784 |
+
"\\bsansa" : "𝗮",
|
785 |
+
"\\bsansb" : "𝗯",
|
786 |
+
"\\bsansc" : "𝗰",
|
787 |
+
"\\bsansd" : "𝗱",
|
788 |
+
"\\bsanse" : "𝗲",
|
789 |
+
"\\bsansf" : "𝗳",
|
790 |
+
"\\bsansg" : "𝗴",
|
791 |
+
"\\bsansh" : "𝗵",
|
792 |
+
"\\bsansi" : "𝗶",
|
793 |
+
"\\bsansj" : "𝗷",
|
794 |
+
"\\bsansk" : "𝗸",
|
795 |
+
"\\bsansl" : "𝗹",
|
796 |
+
"\\bsansm" : "𝗺",
|
797 |
+
"\\bsansn" : "𝗻",
|
798 |
+
"\\bsanso" : "𝗼",
|
799 |
+
"\\bsansp" : "𝗽",
|
800 |
+
"\\bsansq" : "𝗾",
|
801 |
+
"\\bsansr" : "𝗿",
|
802 |
+
"\\bsanss" : "𝘀",
|
803 |
+
"\\bsanst" : "𝘁",
|
804 |
+
"\\bsansu" : "𝘂",
|
805 |
+
"\\bsansv" : "𝘃",
|
806 |
+
"\\bsansw" : "𝘄",
|
807 |
+
"\\bsansx" : "𝘅",
|
808 |
+
"\\bsansy" : "𝘆",
|
809 |
+
"\\bsansz" : "𝘇",
|
810 |
+
"\\isansA" : "𝘈",
|
811 |
+
"\\isansB" : "𝘉",
|
812 |
+
"\\isansC" : "𝘊",
|
813 |
+
"\\isansD" : "𝘋",
|
814 |
+
"\\isansE" : "𝘌",
|
815 |
+
"\\isansF" : "𝘍",
|
816 |
+
"\\isansG" : "𝘎",
|
817 |
+
"\\isansH" : "𝘏",
|
818 |
+
"\\isansI" : "𝘐",
|
819 |
+
"\\isansJ" : "𝘑",
|
820 |
+
"\\isansK" : "𝘒",
|
821 |
+
"\\isansL" : "𝘓",
|
822 |
+
"\\isansM" : "𝘔",
|
823 |
+
"\\isansN" : "𝘕",
|
824 |
+
"\\isansO" : "𝘖",
|
825 |
+
"\\isansP" : "𝘗",
|
826 |
+
"\\isansQ" : "𝘘",
|
827 |
+
"\\isansR" : "𝘙",
|
828 |
+
"\\isansS" : "𝘚",
|
829 |
+
"\\isansT" : "𝘛",
|
830 |
+
"\\isansU" : "𝘜",
|
831 |
+
"\\isansV" : "𝘝",
|
832 |
+
"\\isansW" : "𝘞",
|
833 |
+
"\\isansX" : "𝘟",
|
834 |
+
"\\isansY" : "𝘠",
|
835 |
+
"\\isansZ" : "𝘡",
|
836 |
+
"\\isansa" : "𝘢",
|
837 |
+
"\\isansb" : "𝘣",
|
838 |
+
"\\isansc" : "𝘤",
|
839 |
+
"\\isansd" : "𝘥",
|
840 |
+
"\\isanse" : "𝘦",
|
841 |
+
"\\isansf" : "𝘧",
|
842 |
+
"\\isansg" : "𝘨",
|
843 |
+
"\\isansh" : "𝘩",
|
844 |
+
"\\isansi" : "𝘪",
|
845 |
+
"\\isansj" : "𝘫",
|
846 |
+
"\\isansk" : "𝘬",
|
847 |
+
"\\isansl" : "𝘭",
|
848 |
+
"\\isansm" : "𝘮",
|
849 |
+
"\\isansn" : "𝘯",
|
850 |
+
"\\isanso" : "𝘰",
|
851 |
+
"\\isansp" : "𝘱",
|
852 |
+
"\\isansq" : "𝘲",
|
853 |
+
"\\isansr" : "𝘳",
|
854 |
+
"\\isanss" : "𝘴",
|
855 |
+
"\\isanst" : "𝘵",
|
856 |
+
"\\isansu" : "𝘶",
|
857 |
+
"\\isansv" : "𝘷",
|
858 |
+
"\\isansw" : "𝘸",
|
859 |
+
"\\isansx" : "𝘹",
|
860 |
+
"\\isansy" : "𝘺",
|
861 |
+
"\\isansz" : "𝘻",
|
862 |
+
"\\bisansA" : "𝘼",
|
863 |
+
"\\bisansB" : "𝘽",
|
864 |
+
"\\bisansC" : "𝘾",
|
865 |
+
"\\bisansD" : "𝘿",
|
866 |
+
"\\bisansE" : "𝙀",
|
867 |
+
"\\bisansF" : "𝙁",
|
868 |
+
"\\bisansG" : "𝙂",
|
869 |
+
"\\bisansH" : "𝙃",
|
870 |
+
"\\bisansI" : "𝙄",
|
871 |
+
"\\bisansJ" : "𝙅",
|
872 |
+
"\\bisansK" : "𝙆",
|
873 |
+
"\\bisansL" : "𝙇",
|
874 |
+
"\\bisansM" : "𝙈",
|
875 |
+
"\\bisansN" : "𝙉",
|
876 |
+
"\\bisansO" : "𝙊",
|
877 |
+
"\\bisansP" : "𝙋",
|
878 |
+
"\\bisansQ" : "𝙌",
|
879 |
+
"\\bisansR" : "𝙍",
|
880 |
+
"\\bisansS" : "𝙎",
|
881 |
+
"\\bisansT" : "𝙏",
|
882 |
+
"\\bisansU" : "𝙐",
|
883 |
+
"\\bisansV" : "𝙑",
|
884 |
+
"\\bisansW" : "𝙒",
|
885 |
+
"\\bisansX" : "𝙓",
|
886 |
+
"\\bisansY" : "𝙔",
|
887 |
+
"\\bisansZ" : "𝙕",
|
888 |
+
"\\bisansa" : "𝙖",
|
889 |
+
"\\bisansb" : "𝙗",
|
890 |
+
"\\bisansc" : "𝙘",
|
891 |
+
"\\bisansd" : "𝙙",
|
892 |
+
"\\bisanse" : "𝙚",
|
893 |
+
"\\bisansf" : "𝙛",
|
894 |
+
"\\bisansg" : "𝙜",
|
895 |
+
"\\bisansh" : "𝙝",
|
896 |
+
"\\bisansi" : "𝙞",
|
897 |
+
"\\bisansj" : "𝙟",
|
898 |
+
"\\bisansk" : "𝙠",
|
899 |
+
"\\bisansl" : "𝙡",
|
900 |
+
"\\bisansm" : "𝙢",
|
901 |
+
"\\bisansn" : "𝙣",
|
902 |
+
"\\bisanso" : "𝙤",
|
903 |
+
"\\bisansp" : "𝙥",
|
904 |
+
"\\bisansq" : "𝙦",
|
905 |
+
"\\bisansr" : "𝙧",
|
906 |
+
"\\bisanss" : "𝙨",
|
907 |
+
"\\bisanst" : "𝙩",
|
908 |
+
"\\bisansu" : "𝙪",
|
909 |
+
"\\bisansv" : "𝙫",
|
910 |
+
"\\bisansw" : "𝙬",
|
911 |
+
"\\bisansx" : "𝙭",
|
912 |
+
"\\bisansy" : "𝙮",
|
913 |
+
"\\bisansz" : "𝙯",
|
914 |
+
"\\ttA" : "𝙰",
|
915 |
+
"\\ttB" : "𝙱",
|
916 |
+
"\\ttC" : "𝙲",
|
917 |
+
"\\ttD" : "𝙳",
|
918 |
+
"\\ttE" : "𝙴",
|
919 |
+
"\\ttF" : "𝙵",
|
920 |
+
"\\ttG" : "𝙶",
|
921 |
+
"\\ttH" : "𝙷",
|
922 |
+
"\\ttI" : "𝙸",
|
923 |
+
"\\ttJ" : "𝙹",
|
924 |
+
"\\ttK" : "𝙺",
|
925 |
+
"\\ttL" : "𝙻",
|
926 |
+
"\\ttM" : "𝙼",
|
927 |
+
"\\ttN" : "𝙽",
|
928 |
+
"\\ttO" : "𝙾",
|
929 |
+
"\\ttP" : "𝙿",
|
930 |
+
"\\ttQ" : "𝚀",
|
931 |
+
"\\ttR" : "𝚁",
|
932 |
+
"\\ttS" : "𝚂",
|
933 |
+
"\\ttT" : "𝚃",
|
934 |
+
"\\ttU" : "𝚄",
|
935 |
+
"\\ttV" : "𝚅",
|
936 |
+
"\\ttW" : "𝚆",
|
937 |
+
"\\ttX" : "𝚇",
|
938 |
+
"\\ttY" : "𝚈",
|
939 |
+
"\\ttZ" : "𝚉",
|
940 |
+
"\\tta" : "𝚊",
|
941 |
+
"\\ttb" : "𝚋",
|
942 |
+
"\\ttc" : "𝚌",
|
943 |
+
"\\ttd" : "𝚍",
|
944 |
+
"\\tte" : "𝚎",
|
945 |
+
"\\ttf" : "𝚏",
|
946 |
+
"\\ttg" : "𝚐",
|
947 |
+
"\\tth" : "𝚑",
|
948 |
+
"\\tti" : "𝚒",
|
949 |
+
"\\ttj" : "𝚓",
|
950 |
+
"\\ttk" : "𝚔",
|
951 |
+
"\\ttl" : "𝚕",
|
952 |
+
"\\ttm" : "𝚖",
|
953 |
+
"\\ttn" : "𝚗",
|
954 |
+
"\\tto" : "𝚘",
|
955 |
+
"\\ttp" : "𝚙",
|
956 |
+
"\\ttq" : "𝚚",
|
957 |
+
"\\ttr" : "𝚛",
|
958 |
+
"\\tts" : "𝚜",
|
959 |
+
"\\ttt" : "𝚝",
|
960 |
+
"\\ttu" : "𝚞",
|
961 |
+
"\\ttv" : "𝚟",
|
962 |
+
"\\ttw" : "𝚠",
|
963 |
+
"\\ttx" : "𝚡",
|
964 |
+
"\\tty" : "𝚢",
|
965 |
+
"\\ttz" : "𝚣",
|
966 |
+
"\\bfAlpha" : "𝚨",
|
967 |
+
"\\bfBeta" : "𝚩",
|
968 |
+
"\\bfGamma" : "𝚪",
|
969 |
+
"\\bfDelta" : "𝚫",
|
970 |
+
"\\bfEpsilon" : "𝚬",
|
971 |
+
"\\bfZeta" : "𝚭",
|
972 |
+
"\\bfEta" : "𝚮",
|
973 |
+
"\\bfTheta" : "𝚯",
|
974 |
+
"\\bfIota" : "𝚰",
|
975 |
+
"\\bfKappa" : "𝚱",
|
976 |
+
"\\bfLambda" : "𝚲",
|
977 |
+
"\\bfMu" : "𝚳",
|
978 |
+
"\\bfNu" : "𝚴",
|
979 |
+
"\\bfXi" : "𝚵",
|
980 |
+
"\\bfOmicron" : "𝚶",
|
981 |
+
"\\bfPi" : "𝚷",
|
982 |
+
"\\bfRho" : "𝚸",
|
983 |
+
"\\bfvarTheta" : "𝚹",
|
984 |
+
"\\bfSigma" : "𝚺",
|
985 |
+
"\\bfTau" : "𝚻",
|
986 |
+
"\\bfUpsilon" : "𝚼",
|
987 |
+
"\\bfPhi" : "𝚽",
|
988 |
+
"\\bfChi" : "𝚾",
|
989 |
+
"\\bfPsi" : "𝚿",
|
990 |
+
"\\bfOmega" : "𝛀",
|
991 |
+
"\\bfalpha" : "𝛂",
|
992 |
+
"\\bfbeta" : "𝛃",
|
993 |
+
"\\bfgamma" : "𝛄",
|
994 |
+
"\\bfdelta" : "𝛅",
|
995 |
+
"\\bfepsilon" : "𝛆",
|
996 |
+
"\\bfzeta" : "𝛇",
|
997 |
+
"\\bfeta" : "𝛈",
|
998 |
+
"\\bftheta" : "𝛉",
|
999 |
+
"\\bfiota" : "𝛊",
|
1000 |
+
"\\bfkappa" : "𝛋",
|
1001 |
+
"\\bflambda" : "𝛌",
|
1002 |
+
"\\bfmu" : "𝛍",
|
1003 |
+
"\\bfnu" : "𝛎",
|
1004 |
+
"\\bfxi" : "𝛏",
|
1005 |
+
"\\bfomicron" : "𝛐",
|
1006 |
+
"\\bfpi" : "𝛑",
|
1007 |
+
"\\bfrho" : "𝛒",
|
1008 |
+
"\\bfvarsigma" : "𝛓",
|
1009 |
+
"\\bfsigma" : "𝛔",
|
1010 |
+
"\\bftau" : "𝛕",
|
1011 |
+
"\\bfupsilon" : "𝛖",
|
1012 |
+
"\\bfvarphi" : "𝛗",
|
1013 |
+
"\\bfchi" : "𝛘",
|
1014 |
+
"\\bfpsi" : "𝛙",
|
1015 |
+
"\\bfomega" : "𝛚",
|
1016 |
+
"\\bfvarepsilon" : "𝛜",
|
1017 |
+
"\\bfvartheta" : "𝛝",
|
1018 |
+
"\\bfvarkappa" : "𝛞",
|
1019 |
+
"\\bfphi" : "𝛟",
|
1020 |
+
"\\bfvarrho" : "𝛠",
|
1021 |
+
"\\bfvarpi" : "𝛡",
|
1022 |
+
"\\itAlpha" : "𝛢",
|
1023 |
+
"\\itBeta" : "𝛣",
|
1024 |
+
"\\itGamma" : "𝛤",
|
1025 |
+
"\\itDelta" : "𝛥",
|
1026 |
+
"\\itEpsilon" : "𝛦",
|
1027 |
+
"\\itZeta" : "𝛧",
|
1028 |
+
"\\itEta" : "𝛨",
|
1029 |
+
"\\itTheta" : "𝛩",
|
1030 |
+
"\\itIota" : "𝛪",
|
1031 |
+
"\\itKappa" : "𝛫",
|
1032 |
+
"\\itLambda" : "𝛬",
|
1033 |
+
"\\itMu" : "𝛭",
|
1034 |
+
"\\itNu" : "𝛮",
|
1035 |
+
"\\itXi" : "𝛯",
|
1036 |
+
"\\itOmicron" : "𝛰",
|
1037 |
+
"\\itPi" : "𝛱",
|
1038 |
+
"\\itRho" : "𝛲",
|
1039 |
+
"\\itvarTheta" : "𝛳",
|
1040 |
+
"\\itSigma" : "𝛴",
|
1041 |
+
"\\itTau" : "𝛵",
|
1042 |
+
"\\itUpsilon" : "𝛶",
|
1043 |
+
"\\itPhi" : "𝛷",
|
1044 |
+
"\\itChi" : "𝛸",
|
1045 |
+
"\\itPsi" : "𝛹",
|
1046 |
+
"\\itOmega" : "𝛺",
|
1047 |
+
"\\italpha" : "𝛼",
|
1048 |
+
"\\itbeta" : "𝛽",
|
1049 |
+
"\\itgamma" : "𝛾",
|
1050 |
+
"\\itdelta" : "𝛿",
|
1051 |
+
"\\itepsilon" : "𝜀",
|
1052 |
+
"\\itzeta" : "𝜁",
|
1053 |
+
"\\iteta" : "𝜂",
|
1054 |
+
"\\ittheta" : "𝜃",
|
1055 |
+
"\\itiota" : "𝜄",
|
1056 |
+
"\\itkappa" : "𝜅",
|
1057 |
+
"\\itlambda" : "𝜆",
|
1058 |
+
"\\itmu" : "𝜇",
|
1059 |
+
"\\itnu" : "𝜈",
|
1060 |
+
"\\itxi" : "𝜉",
|
1061 |
+
"\\itomicron" : "𝜊",
|
1062 |
+
"\\itpi" : "𝜋",
|
1063 |
+
"\\itrho" : "𝜌",
|
1064 |
+
"\\itvarsigma" : "𝜍",
|
1065 |
+
"\\itsigma" : "𝜎",
|
1066 |
+
"\\ittau" : "𝜏",
|
1067 |
+
"\\itupsilon" : "𝜐",
|
1068 |
+
"\\itphi" : "𝜑",
|
1069 |
+
"\\itchi" : "𝜒",
|
1070 |
+
"\\itpsi" : "𝜓",
|
1071 |
+
"\\itomega" : "𝜔",
|
1072 |
+
"\\itvarepsilon" : "𝜖",
|
1073 |
+
"\\itvartheta" : "𝜗",
|
1074 |
+
"\\itvarkappa" : "𝜘",
|
1075 |
+
"\\itvarphi" : "𝜙",
|
1076 |
+
"\\itvarrho" : "𝜚",
|
1077 |
+
"\\itvarpi" : "𝜛",
|
1078 |
+
"\\biAlpha" : "𝜜",
|
1079 |
+
"\\biBeta" : "𝜝",
|
1080 |
+
"\\biGamma" : "𝜞",
|
1081 |
+
"\\biDelta" : "𝜟",
|
1082 |
+
"\\biEpsilon" : "𝜠",
|
1083 |
+
"\\biZeta" : "𝜡",
|
1084 |
+
"\\biEta" : "𝜢",
|
1085 |
+
"\\biTheta" : "𝜣",
|
1086 |
+
"\\biIota" : "𝜤",
|
1087 |
+
"\\biKappa" : "𝜥",
|
1088 |
+
"\\biLambda" : "𝜦",
|
1089 |
+
"\\biMu" : "𝜧",
|
1090 |
+
"\\biNu" : "𝜨",
|
1091 |
+
"\\biXi" : "𝜩",
|
1092 |
+
"\\biOmicron" : "𝜪",
|
1093 |
+
"\\biPi" : "𝜫",
|
1094 |
+
"\\biRho" : "𝜬",
|
1095 |
+
"\\bivarTheta" : "𝜭",
|
1096 |
+
"\\biSigma" : "𝜮",
|
1097 |
+
"\\biTau" : "𝜯",
|
1098 |
+
"\\biUpsilon" : "𝜰",
|
1099 |
+
"\\biPhi" : "𝜱",
|
1100 |
+
"\\biChi" : "𝜲",
|
1101 |
+
"\\biPsi" : "𝜳",
|
1102 |
+
"\\biOmega" : "𝜴",
|
1103 |
+
"\\bialpha" : "𝜶",
|
1104 |
+
"\\bibeta" : "𝜷",
|
1105 |
+
"\\bigamma" : "𝜸",
|
1106 |
+
"\\bidelta" : "𝜹",
|
1107 |
+
"\\biepsilon" : "𝜺",
|
1108 |
+
"\\bizeta" : "𝜻",
|
1109 |
+
"\\bieta" : "𝜼",
|
1110 |
+
"\\bitheta" : "𝜽",
|
1111 |
+
"\\biiota" : "𝜾",
|
1112 |
+
"\\bikappa" : "𝜿",
|
1113 |
+
"\\bilambda" : "𝝀",
|
1114 |
+
"\\bimu" : "𝝁",
|
1115 |
+
"\\binu" : "𝝂",
|
1116 |
+
"\\bixi" : "𝝃",
|
1117 |
+
"\\biomicron" : "𝝄",
|
1118 |
+
"\\bipi" : "𝝅",
|
1119 |
+
"\\birho" : "𝝆",
|
1120 |
+
"\\bivarsigma" : "𝝇",
|
1121 |
+
"\\bisigma" : "𝝈",
|
1122 |
+
"\\bitau" : "𝝉",
|
1123 |
+
"\\biupsilon" : "𝝊",
|
1124 |
+
"\\biphi" : "𝝋",
|
1125 |
+
"\\bichi" : "𝝌",
|
1126 |
+
"\\bipsi" : "𝝍",
|
1127 |
+
"\\biomega" : "𝝎",
|
1128 |
+
"\\bivarepsilon" : "𝝐",
|
1129 |
+
"\\bivartheta" : "𝝑",
|
1130 |
+
"\\bivarkappa" : "𝝒",
|
1131 |
+
"\\bivarphi" : "𝝓",
|
1132 |
+
"\\bivarrho" : "𝝔",
|
1133 |
+
"\\bivarpi" : "𝝕",
|
1134 |
+
"\\bsansAlpha" : "𝝖",
|
1135 |
+
"\\bsansBeta" : "𝝗",
|
1136 |
+
"\\bsansGamma" : "𝝘",
|
1137 |
+
"\\bsansDelta" : "𝝙",
|
1138 |
+
"\\bsansEpsilon" : "𝝚",
|
1139 |
+
"\\bsansZeta" : "𝝛",
|
1140 |
+
"\\bsansEta" : "𝝜",
|
1141 |
+
"\\bsansTheta" : "𝝝",
|
1142 |
+
"\\bsansIota" : "𝝞",
|
1143 |
+
"\\bsansKappa" : "𝝟",
|
1144 |
+
"\\bsansLambda" : "𝝠",
|
1145 |
+
"\\bsansMu" : "𝝡",
|
1146 |
+
"\\bsansNu" : "𝝢",
|
1147 |
+
"\\bsansXi" : "𝝣",
|
1148 |
+
"\\bsansOmicron" : "𝝤",
|
1149 |
+
"\\bsansPi" : "𝝥",
|
1150 |
+
"\\bsansRho" : "𝝦",
|
1151 |
+
"\\bsansvarTheta" : "𝝧",
|
1152 |
+
"\\bsansSigma" : "𝝨",
|
1153 |
+
"\\bsansTau" : "𝝩",
|
1154 |
+
"\\bsansUpsilon" : "𝝪",
|
1155 |
+
"\\bsansPhi" : "𝝫",
|
1156 |
+
"\\bsansChi" : "𝝬",
|
1157 |
+
"\\bsansPsi" : "𝝭",
|
1158 |
+
"\\bsansOmega" : "𝝮",
|
1159 |
+
"\\bsansalpha" : "𝝰",
|
1160 |
+
"\\bsansbeta" : "𝝱",
|
1161 |
+
"\\bsansgamma" : "𝝲",
|
1162 |
+
"\\bsansdelta" : "𝝳",
|
1163 |
+
"\\bsansepsilon" : "𝝴",
|
1164 |
+
"\\bsanszeta" : "𝝵",
|
1165 |
+
"\\bsanseta" : "𝝶",
|
1166 |
+
"\\bsanstheta" : "𝝷",
|
1167 |
+
"\\bsansiota" : "𝝸",
|
1168 |
+
"\\bsanskappa" : "𝝹",
|
1169 |
+
"\\bsanslambda" : "𝝺",
|
1170 |
+
"\\bsansmu" : "𝝻",
|
1171 |
+
"\\bsansnu" : "𝝼",
|
1172 |
+
"\\bsansxi" : "𝝽",
|
1173 |
+
"\\bsansomicron" : "𝝾",
|
1174 |
+
"\\bsanspi" : "𝝿",
|
1175 |
+
"\\bsansrho" : "𝞀",
|
1176 |
+
"\\bsansvarsigma" : "𝞁",
|
1177 |
+
"\\bsanssigma" : "𝞂",
|
1178 |
+
"\\bsanstau" : "𝞃",
|
1179 |
+
"\\bsansupsilon" : "𝞄",
|
1180 |
+
"\\bsansphi" : "𝞅",
|
1181 |
+
"\\bsanschi" : "𝞆",
|
1182 |
+
"\\bsanspsi" : "𝞇",
|
1183 |
+
"\\bsansomega" : "𝞈",
|
1184 |
+
"\\bsansvarepsilon" : "𝞊",
|
1185 |
+
"\\bsansvartheta" : "𝞋",
|
1186 |
+
"\\bsansvarkappa" : "𝞌",
|
1187 |
+
"\\bsansvarphi" : "𝞍",
|
1188 |
+
"\\bsansvarrho" : "𝞎",
|
1189 |
+
"\\bsansvarpi" : "𝞏",
|
1190 |
+
"\\bisansAlpha" : "𝞐",
|
1191 |
+
"\\bisansBeta" : "𝞑",
|
1192 |
+
"\\bisansGamma" : "𝞒",
|
1193 |
+
"\\bisansDelta" : "𝞓",
|
1194 |
+
"\\bisansEpsilon" : "𝞔",
|
1195 |
+
"\\bisansZeta" : "𝞕",
|
1196 |
+
"\\bisansEta" : "𝞖",
|
1197 |
+
"\\bisansTheta" : "𝞗",
|
1198 |
+
"\\bisansIota" : "𝞘",
|
1199 |
+
"\\bisansKappa" : "𝞙",
|
1200 |
+
"\\bisansLambda" : "𝞚",
|
1201 |
+
"\\bisansMu" : "𝞛",
|
1202 |
+
"\\bisansNu" : "𝞜",
|
1203 |
+
"\\bisansXi" : "𝞝",
|
1204 |
+
"\\bisansOmicron" : "𝞞",
|
1205 |
+
"\\bisansPi" : "𝞟",
|
1206 |
+
"\\bisansRho" : "𝞠",
|
1207 |
+
"\\bisansvarTheta" : "𝞡",
|
1208 |
+
"\\bisansSigma" : "𝞢",
|
1209 |
+
"\\bisansTau" : "𝞣",
|
1210 |
+
"\\bisansUpsilon" : "𝞤",
|
1211 |
+
"\\bisansPhi" : "𝞥",
|
1212 |
+
"\\bisansChi" : "𝞦",
|
1213 |
+
"\\bisansPsi" : "𝞧",
|
1214 |
+
"\\bisansOmega" : "𝞨",
|
1215 |
+
"\\bisansalpha" : "𝞪",
|
1216 |
+
"\\bisansbeta" : "𝞫",
|
1217 |
+
"\\bisansgamma" : "𝞬",
|
1218 |
+
"\\bisansdelta" : "𝞭",
|
1219 |
+
"\\bisansepsilon" : "𝞮",
|
1220 |
+
"\\bisanszeta" : "𝞯",
|
1221 |
+
"\\bisanseta" : "𝞰",
|
1222 |
+
"\\bisanstheta" : "𝞱",
|
1223 |
+
"\\bisansiota" : "𝞲",
|
1224 |
+
"\\bisanskappa" : "𝞳",
|
1225 |
+
"\\bisanslambda" : "𝞴",
|
1226 |
+
"\\bisansmu" : "𝞵",
|
1227 |
+
"\\bisansnu" : "𝞶",
|
1228 |
+
"\\bisansxi" : "𝞷",
|
1229 |
+
"\\bisansomicron" : "𝞸",
|
1230 |
+
"\\bisanspi" : "𝞹",
|
1231 |
+
"\\bisansrho" : "𝞺",
|
1232 |
+
"\\bisansvarsigma" : "𝞻",
|
1233 |
+
"\\bisanssigma" : "𝞼",
|
1234 |
+
"\\bisanstau" : "𝞽",
|
1235 |
+
"\\bisansupsilon" : "𝞾",
|
1236 |
+
"\\bisansphi" : "𝞿",
|
1237 |
+
"\\bisanschi" : "𝟀",
|
1238 |
+
"\\bisanspsi" : "𝟁",
|
1239 |
+
"\\bisansomega" : "𝟂",
|
1240 |
+
"\\bisansvarepsilon" : "𝟄",
|
1241 |
+
"\\bisansvartheta" : "𝟅",
|
1242 |
+
"\\bisansvarkappa" : "𝟆",
|
1243 |
+
"\\bisansvarphi" : "𝟇",
|
1244 |
+
"\\bisansvarrho" : "𝟈",
|
1245 |
+
"\\bisansvarpi" : "𝟉",
|
1246 |
+
"\\bfzero" : "𝟎",
|
1247 |
+
"\\bfone" : "𝟏",
|
1248 |
+
"\\bftwo" : "𝟐",
|
1249 |
+
"\\bfthree" : "𝟑",
|
1250 |
+
"\\bffour" : "𝟒",
|
1251 |
+
"\\bffive" : "𝟓",
|
1252 |
+
"\\bfsix" : "𝟔",
|
1253 |
+
"\\bfseven" : "𝟕",
|
1254 |
+
"\\bfeight" : "𝟖",
|
1255 |
+
"\\bfnine" : "𝟗",
|
1256 |
+
"\\bbzero" : "𝟘",
|
1257 |
+
"\\bbone" : "𝟙",
|
1258 |
+
"\\bbtwo" : "𝟚",
|
1259 |
+
"\\bbthree" : "𝟛",
|
1260 |
+
"\\bbfour" : "𝟜",
|
1261 |
+
"\\bbfive" : "𝟝",
|
1262 |
+
"\\bbsix" : "𝟞",
|
1263 |
+
"\\bbseven" : "𝟟",
|
1264 |
+
"\\bbeight" : "𝟠",
|
1265 |
+
"\\bbnine" : "𝟡",
|
1266 |
+
"\\sanszero" : "𝟢",
|
1267 |
+
"\\sansone" : "𝟣",
|
1268 |
+
"\\sanstwo" : "𝟤",
|
1269 |
+
"\\sansthree" : "𝟥",
|
1270 |
+
"\\sansfour" : "𝟦",
|
1271 |
+
"\\sansfive" : "𝟧",
|
1272 |
+
"\\sanssix" : "𝟨",
|
1273 |
+
"\\sansseven" : "𝟩",
|
1274 |
+
"\\sanseight" : "𝟪",
|
1275 |
+
"\\sansnine" : "𝟫",
|
1276 |
+
"\\bsanszero" : "𝟬",
|
1277 |
+
"\\bsansone" : "𝟭",
|
1278 |
+
"\\bsanstwo" : "𝟮",
|
1279 |
+
"\\bsansthree" : "𝟯",
|
1280 |
+
"\\bsansfour" : "𝟰",
|
1281 |
+
"\\bsansfive" : "𝟱",
|
1282 |
+
"\\bsanssix" : "𝟲",
|
1283 |
+
"\\bsansseven" : "𝟳",
|
1284 |
+
"\\bsanseight" : "𝟴",
|
1285 |
+
"\\bsansnine" : "𝟵",
|
1286 |
+
"\\ttzero" : "𝟶",
|
1287 |
+
"\\ttone" : "𝟷",
|
1288 |
+
"\\tttwo" : "𝟸",
|
1289 |
+
"\\ttthree" : "𝟹",
|
1290 |
+
"\\ttfour" : "𝟺",
|
1291 |
+
"\\ttfive" : "𝟻",
|
1292 |
+
"\\ttsix" : "𝟼",
|
1293 |
+
"\\ttseven" : "𝟽",
|
1294 |
+
"\\tteight" : "𝟾",
|
1295 |
+
"\\ttnine" : "𝟿",
|
1296 |
+
"\\underbar" : "̲",
|
1297 |
+
"\\underleftrightarrow" : "͍",
|
1298 |
+
}
|
1299 |
+
|
1300 |
+
|
1301 |
+
reverse_latex_symbol = { v:k for k,v in latex_symbols.items()}
|
openalex_env_map/lib/python3.10/site-packages/IPython/core/logger.py
ADDED
@@ -0,0 +1,231 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"""Logger class for IPython's logging facilities.
|
2 |
+
"""
|
3 |
+
|
4 |
+
#*****************************************************************************
|
5 |
+
# Copyright (C) 2001 Janko Hauser <[email protected]> and
|
6 |
+
# Copyright (C) 2001-2006 Fernando Perez <[email protected]>
|
7 |
+
#
|
8 |
+
# Distributed under the terms of the BSD License. The full license is in
|
9 |
+
# the file COPYING, distributed as part of this software.
|
10 |
+
#*****************************************************************************
|
11 |
+
|
12 |
+
#****************************************************************************
|
13 |
+
# Modules and globals
|
14 |
+
|
15 |
+
# Python standard modules
|
16 |
+
import glob
|
17 |
+
import io
|
18 |
+
import logging
|
19 |
+
import os
|
20 |
+
import time
|
21 |
+
|
22 |
+
|
23 |
+
# prevent jedi/parso's debug messages pipe into interactiveshell
|
24 |
+
logging.getLogger("parso").setLevel(logging.WARNING)
|
25 |
+
|
26 |
+
#****************************************************************************
|
27 |
+
# FIXME: This class isn't a mixin anymore, but it still needs attributes from
|
28 |
+
# ipython and does input cache management. Finish cleanup later...
|
29 |
+
|
30 |
+
class Logger(object):
|
31 |
+
"""A Logfile class with different policies for file creation"""
|
32 |
+
|
33 |
+
def __init__(self, home_dir, logfname='Logger.log', loghead=u'',
|
34 |
+
logmode='over'):
|
35 |
+
|
36 |
+
# this is the full ipython instance, we need some attributes from it
|
37 |
+
# which won't exist until later. What a mess, clean up later...
|
38 |
+
self.home_dir = home_dir
|
39 |
+
|
40 |
+
self.logfname = logfname
|
41 |
+
self.loghead = loghead
|
42 |
+
self.logmode = logmode
|
43 |
+
self.logfile = None
|
44 |
+
|
45 |
+
# Whether to log raw or processed input
|
46 |
+
self.log_raw_input = False
|
47 |
+
|
48 |
+
# whether to also log output
|
49 |
+
self.log_output = False
|
50 |
+
|
51 |
+
# whether to put timestamps before each log entry
|
52 |
+
self.timestamp = False
|
53 |
+
|
54 |
+
# activity control flags
|
55 |
+
self.log_active = False
|
56 |
+
|
57 |
+
# logmode is a validated property
|
58 |
+
def _set_mode(self,mode):
|
59 |
+
if mode not in ['append','backup','global','over','rotate']:
|
60 |
+
raise ValueError('invalid log mode %s given' % mode)
|
61 |
+
self._logmode = mode
|
62 |
+
|
63 |
+
def _get_mode(self):
|
64 |
+
return self._logmode
|
65 |
+
|
66 |
+
logmode = property(_get_mode,_set_mode)
|
67 |
+
|
68 |
+
def logstart(self, logfname=None, loghead=None, logmode=None,
|
69 |
+
log_output=False, timestamp=False, log_raw_input=False):
|
70 |
+
"""Generate a new log-file with a default header.
|
71 |
+
|
72 |
+
Raises RuntimeError if the log has already been started"""
|
73 |
+
|
74 |
+
if self.logfile is not None:
|
75 |
+
raise RuntimeError('Log file is already active: %s' %
|
76 |
+
self.logfname)
|
77 |
+
|
78 |
+
# The parameters can override constructor defaults
|
79 |
+
if logfname is not None: self.logfname = logfname
|
80 |
+
if loghead is not None: self.loghead = loghead
|
81 |
+
if logmode is not None: self.logmode = logmode
|
82 |
+
|
83 |
+
# Parameters not part of the constructor
|
84 |
+
self.timestamp = timestamp
|
85 |
+
self.log_output = log_output
|
86 |
+
self.log_raw_input = log_raw_input
|
87 |
+
|
88 |
+
# init depending on the log mode requested
|
89 |
+
isfile = os.path.isfile
|
90 |
+
logmode = self.logmode
|
91 |
+
|
92 |
+
if logmode == 'append':
|
93 |
+
self.logfile = io.open(self.logfname, 'a', encoding='utf-8')
|
94 |
+
|
95 |
+
elif logmode == 'backup':
|
96 |
+
if isfile(self.logfname):
|
97 |
+
backup_logname = self.logfname+'~'
|
98 |
+
# Manually remove any old backup, since os.rename may fail
|
99 |
+
# under Windows.
|
100 |
+
if isfile(backup_logname):
|
101 |
+
os.remove(backup_logname)
|
102 |
+
os.rename(self.logfname,backup_logname)
|
103 |
+
self.logfile = io.open(self.logfname, 'w', encoding='utf-8')
|
104 |
+
|
105 |
+
elif logmode == 'global':
|
106 |
+
self.logfname = os.path.join(self.home_dir,self.logfname)
|
107 |
+
self.logfile = io.open(self.logfname, 'a', encoding='utf-8')
|
108 |
+
|
109 |
+
elif logmode == 'over':
|
110 |
+
if isfile(self.logfname):
|
111 |
+
os.remove(self.logfname)
|
112 |
+
self.logfile = io.open(self.logfname,'w', encoding='utf-8')
|
113 |
+
|
114 |
+
elif logmode == 'rotate':
|
115 |
+
if isfile(self.logfname):
|
116 |
+
if isfile(self.logfname+'.001~'):
|
117 |
+
old = glob.glob(self.logfname+'.*~')
|
118 |
+
old.sort()
|
119 |
+
old.reverse()
|
120 |
+
for f in old:
|
121 |
+
root, ext = os.path.splitext(f)
|
122 |
+
num = int(ext[1:-1])+1
|
123 |
+
os.rename(f, root+'.'+repr(num).zfill(3)+'~')
|
124 |
+
os.rename(self.logfname, self.logfname+'.001~')
|
125 |
+
self.logfile = io.open(self.logfname, 'w', encoding='utf-8')
|
126 |
+
|
127 |
+
if logmode != 'append':
|
128 |
+
self.logfile.write(self.loghead)
|
129 |
+
|
130 |
+
self.logfile.flush()
|
131 |
+
self.log_active = True
|
132 |
+
|
133 |
+
def switch_log(self,val):
|
134 |
+
"""Switch logging on/off. val should be ONLY a boolean."""
|
135 |
+
|
136 |
+
if val not in [False,True,0,1]:
|
137 |
+
raise ValueError('Call switch_log ONLY with a boolean argument, '
|
138 |
+
'not with: %s' % val)
|
139 |
+
|
140 |
+
label = {0:'OFF',1:'ON',False:'OFF',True:'ON'}
|
141 |
+
|
142 |
+
if self.logfile is None:
|
143 |
+
print("""
|
144 |
+
Logging hasn't been started yet (use logstart for that).
|
145 |
+
|
146 |
+
%logon/%logoff are for temporarily starting and stopping logging for a logfile
|
147 |
+
which already exists. But you must first start the logging process with
|
148 |
+
%logstart (optionally giving a logfile name).""")
|
149 |
+
|
150 |
+
else:
|
151 |
+
if self.log_active == val:
|
152 |
+
print('Logging is already',label[val])
|
153 |
+
else:
|
154 |
+
print('Switching logging',label[val])
|
155 |
+
self.log_active = not self.log_active
|
156 |
+
self.log_active_out = self.log_active
|
157 |
+
|
158 |
+
def logstate(self):
|
159 |
+
"""Print a status message about the logger."""
|
160 |
+
if self.logfile is None:
|
161 |
+
print('Logging has not been activated.')
|
162 |
+
else:
|
163 |
+
state = self.log_active and 'active' or 'temporarily suspended'
|
164 |
+
print('Filename :', self.logfname)
|
165 |
+
print('Mode :', self.logmode)
|
166 |
+
print('Output logging :', self.log_output)
|
167 |
+
print('Raw input log :', self.log_raw_input)
|
168 |
+
print('Timestamping :', self.timestamp)
|
169 |
+
print('State :', state)
|
170 |
+
|
171 |
+
def log(self, line_mod, line_ori):
|
172 |
+
"""Write the sources to a log.
|
173 |
+
|
174 |
+
Inputs:
|
175 |
+
|
176 |
+
- line_mod: possibly modified input, such as the transformations made
|
177 |
+
by input prefilters or input handlers of various kinds. This should
|
178 |
+
always be valid Python.
|
179 |
+
|
180 |
+
- line_ori: unmodified input line from the user. This is not
|
181 |
+
necessarily valid Python.
|
182 |
+
"""
|
183 |
+
|
184 |
+
# Write the log line, but decide which one according to the
|
185 |
+
# log_raw_input flag, set when the log is started.
|
186 |
+
if self.log_raw_input:
|
187 |
+
self.log_write(line_ori)
|
188 |
+
else:
|
189 |
+
self.log_write(line_mod)
|
190 |
+
|
191 |
+
def log_write(self, data, kind='input'):
|
192 |
+
"""Write data to the log file, if active"""
|
193 |
+
|
194 |
+
# print('data: %r' % data) # dbg
|
195 |
+
if self.log_active and data:
|
196 |
+
write = self.logfile.write
|
197 |
+
if kind=='input':
|
198 |
+
if self.timestamp:
|
199 |
+
write(time.strftime('# %a, %d %b %Y %H:%M:%S\n', time.localtime()))
|
200 |
+
write(data)
|
201 |
+
elif kind=='output' and self.log_output:
|
202 |
+
odata = u'\n'.join([u'#[Out]# %s' % s
|
203 |
+
for s in data.splitlines()])
|
204 |
+
write(u'%s\n' % odata)
|
205 |
+
try:
|
206 |
+
self.logfile.flush()
|
207 |
+
except OSError:
|
208 |
+
print("Failed to flush the log file.")
|
209 |
+
print(
|
210 |
+
f"Please check that {self.logfname} exists and have the right permissions."
|
211 |
+
)
|
212 |
+
print(
|
213 |
+
"Also consider turning off the log with `%logstop` to avoid this warning."
|
214 |
+
)
|
215 |
+
|
216 |
+
def logstop(self):
|
217 |
+
"""Fully stop logging and close log file.
|
218 |
+
|
219 |
+
In order to start logging again, a new logstart() call needs to be
|
220 |
+
made, possibly (though not necessarily) with a new filename, mode and
|
221 |
+
other options."""
|
222 |
+
|
223 |
+
if self.logfile is not None:
|
224 |
+
self.logfile.close()
|
225 |
+
self.logfile = None
|
226 |
+
else:
|
227 |
+
print("Logging hadn't been started.")
|
228 |
+
self.log_active = False
|
229 |
+
|
230 |
+
# For backwards compatibility, in case anyone was using this.
|
231 |
+
close_log = logstop
|
openalex_env_map/lib/python3.10/site-packages/IPython/core/macro.py
ADDED
@@ -0,0 +1,53 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"""Support for interactive macros in IPython"""
|
2 |
+
|
3 |
+
#*****************************************************************************
|
4 |
+
# Copyright (C) 2001-2005 Fernando Perez <[email protected]>
|
5 |
+
#
|
6 |
+
# Distributed under the terms of the BSD License. The full license is in
|
7 |
+
# the file COPYING, distributed as part of this software.
|
8 |
+
#*****************************************************************************
|
9 |
+
|
10 |
+
import re
|
11 |
+
|
12 |
+
from IPython.utils.encoding import DEFAULT_ENCODING
|
13 |
+
|
14 |
+
coding_declaration = re.compile(r"#\s*coding[:=]\s*([-\w.]+)")
|
15 |
+
|
16 |
+
class Macro(object):
|
17 |
+
"""Simple class to store the value of macros as strings.
|
18 |
+
|
19 |
+
Macro is just a callable that executes a string of IPython
|
20 |
+
input when called.
|
21 |
+
"""
|
22 |
+
|
23 |
+
def __init__(self,code):
|
24 |
+
"""store the macro value, as a single string which can be executed"""
|
25 |
+
lines = []
|
26 |
+
enc = None
|
27 |
+
for line in code.splitlines():
|
28 |
+
coding_match = coding_declaration.match(line)
|
29 |
+
if coding_match:
|
30 |
+
enc = coding_match.group(1)
|
31 |
+
else:
|
32 |
+
lines.append(line)
|
33 |
+
code = "\n".join(lines)
|
34 |
+
if isinstance(code, bytes):
|
35 |
+
code = code.decode(enc or DEFAULT_ENCODING)
|
36 |
+
self.value = code + '\n'
|
37 |
+
|
38 |
+
def __str__(self):
|
39 |
+
return self.value
|
40 |
+
|
41 |
+
def __repr__(self):
|
42 |
+
return 'IPython.macro.Macro(%s)' % repr(self.value)
|
43 |
+
|
44 |
+
def __getstate__(self):
|
45 |
+
""" needed for safe pickling via %store """
|
46 |
+
return {'value': self.value}
|
47 |
+
|
48 |
+
def __add__(self, other):
|
49 |
+
if isinstance(other, Macro):
|
50 |
+
return Macro(self.value + other.value)
|
51 |
+
elif isinstance(other, str):
|
52 |
+
return Macro(self.value + other)
|
53 |
+
raise TypeError
|
openalex_env_map/lib/python3.10/site-packages/IPython/core/magic.py
ADDED
@@ -0,0 +1,759 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# encoding: utf-8
|
2 |
+
"""Magic functions for InteractiveShell.
|
3 |
+
"""
|
4 |
+
|
5 |
+
#-----------------------------------------------------------------------------
|
6 |
+
# Copyright (C) 2001 Janko Hauser <[email protected]> and
|
7 |
+
# Copyright (C) 2001 Fernando Perez <[email protected]>
|
8 |
+
# Copyright (C) 2008 The IPython Development Team
|
9 |
+
|
10 |
+
# Distributed under the terms of the BSD License. The full license is in
|
11 |
+
# the file COPYING, distributed as part of this software.
|
12 |
+
#-----------------------------------------------------------------------------
|
13 |
+
|
14 |
+
import os
|
15 |
+
import re
|
16 |
+
import sys
|
17 |
+
from getopt import getopt, GetoptError
|
18 |
+
|
19 |
+
from traitlets.config.configurable import Configurable
|
20 |
+
from . import oinspect
|
21 |
+
from .error import UsageError
|
22 |
+
from .inputtransformer2 import ESC_MAGIC, ESC_MAGIC2
|
23 |
+
from ..utils.ipstruct import Struct
|
24 |
+
from ..utils.process import arg_split
|
25 |
+
from ..utils.text import dedent
|
26 |
+
from traitlets import Bool, Dict, Instance, observe
|
27 |
+
from logging import error
|
28 |
+
|
29 |
+
import typing as t
|
30 |
+
|
31 |
+
#-----------------------------------------------------------------------------
|
32 |
+
# Globals
|
33 |
+
#-----------------------------------------------------------------------------
|
34 |
+
|
35 |
+
# A dict we'll use for each class that has magics, used as temporary storage to
|
36 |
+
# pass information between the @line/cell_magic method decorators and the
|
37 |
+
# @magics_class class decorator, because the method decorators have no
|
38 |
+
# access to the class when they run. See for more details:
|
39 |
+
# http://stackoverflow.com/questions/2366713/can-a-python-decorator-of-an-instance-method-access-the-class
|
40 |
+
|
41 |
+
magics: t.Dict = dict(line={}, cell={})
|
42 |
+
|
43 |
+
magic_kinds = ('line', 'cell')
|
44 |
+
magic_spec = ('line', 'cell', 'line_cell')
|
45 |
+
magic_escapes = dict(line=ESC_MAGIC, cell=ESC_MAGIC2)
|
46 |
+
|
47 |
+
#-----------------------------------------------------------------------------
|
48 |
+
# Utility classes and functions
|
49 |
+
#-----------------------------------------------------------------------------
|
50 |
+
|
51 |
+
class Bunch: pass
|
52 |
+
|
53 |
+
|
54 |
+
def on_off(tag):
|
55 |
+
"""Return an ON/OFF string for a 1/0 input. Simple utility function."""
|
56 |
+
return ['OFF','ON'][tag]
|
57 |
+
|
58 |
+
|
59 |
+
def compress_dhist(dh):
|
60 |
+
"""Compress a directory history into a new one with at most 20 entries.
|
61 |
+
|
62 |
+
Return a new list made from the first and last 10 elements of dhist after
|
63 |
+
removal of duplicates.
|
64 |
+
"""
|
65 |
+
head, tail = dh[:-10], dh[-10:]
|
66 |
+
|
67 |
+
newhead = []
|
68 |
+
done = set()
|
69 |
+
for h in head:
|
70 |
+
if h in done:
|
71 |
+
continue
|
72 |
+
newhead.append(h)
|
73 |
+
done.add(h)
|
74 |
+
|
75 |
+
return newhead + tail
|
76 |
+
|
77 |
+
|
78 |
+
def needs_local_scope(func):
|
79 |
+
"""Decorator to mark magic functions which need to local scope to run."""
|
80 |
+
func.needs_local_scope = True
|
81 |
+
return func
|
82 |
+
|
83 |
+
#-----------------------------------------------------------------------------
|
84 |
+
# Class and method decorators for registering magics
|
85 |
+
#-----------------------------------------------------------------------------
|
86 |
+
|
87 |
+
def magics_class(cls):
|
88 |
+
"""Class decorator for all subclasses of the main Magics class.
|
89 |
+
|
90 |
+
Any class that subclasses Magics *must* also apply this decorator, to
|
91 |
+
ensure that all the methods that have been decorated as line/cell magics
|
92 |
+
get correctly registered in the class instance. This is necessary because
|
93 |
+
when method decorators run, the class does not exist yet, so they
|
94 |
+
temporarily store their information into a module global. Application of
|
95 |
+
this class decorator copies that global data to the class instance and
|
96 |
+
clears the global.
|
97 |
+
|
98 |
+
Obviously, this mechanism is not thread-safe, which means that the
|
99 |
+
*creation* of subclasses of Magic should only be done in a single-thread
|
100 |
+
context. Instantiation of the classes has no restrictions. Given that
|
101 |
+
these classes are typically created at IPython startup time and before user
|
102 |
+
application code becomes active, in practice this should not pose any
|
103 |
+
problems.
|
104 |
+
"""
|
105 |
+
cls.registered = True
|
106 |
+
cls.magics = dict(line = magics['line'],
|
107 |
+
cell = magics['cell'])
|
108 |
+
magics['line'] = {}
|
109 |
+
magics['cell'] = {}
|
110 |
+
return cls
|
111 |
+
|
112 |
+
|
113 |
+
def record_magic(dct, magic_kind, magic_name, func):
|
114 |
+
"""Utility function to store a function as a magic of a specific kind.
|
115 |
+
|
116 |
+
Parameters
|
117 |
+
----------
|
118 |
+
dct : dict
|
119 |
+
A dictionary with 'line' and 'cell' subdicts.
|
120 |
+
magic_kind : str
|
121 |
+
Kind of magic to be stored.
|
122 |
+
magic_name : str
|
123 |
+
Key to store the magic as.
|
124 |
+
func : function
|
125 |
+
Callable object to store.
|
126 |
+
"""
|
127 |
+
if magic_kind == 'line_cell':
|
128 |
+
dct['line'][magic_name] = dct['cell'][magic_name] = func
|
129 |
+
else:
|
130 |
+
dct[magic_kind][magic_name] = func
|
131 |
+
|
132 |
+
|
133 |
+
def validate_type(magic_kind):
|
134 |
+
"""Ensure that the given magic_kind is valid.
|
135 |
+
|
136 |
+
Check that the given magic_kind is one of the accepted spec types (stored
|
137 |
+
in the global `magic_spec`), raise ValueError otherwise.
|
138 |
+
"""
|
139 |
+
if magic_kind not in magic_spec:
|
140 |
+
raise ValueError('magic_kind must be one of %s, %s given' %
|
141 |
+
magic_kinds, magic_kind)
|
142 |
+
|
143 |
+
|
144 |
+
# The docstrings for the decorator below will be fairly similar for the two
|
145 |
+
# types (method and function), so we generate them here once and reuse the
|
146 |
+
# templates below.
|
147 |
+
_docstring_template = \
|
148 |
+
"""Decorate the given {0} as {1} magic.
|
149 |
+
|
150 |
+
The decorator can be used with or without arguments, as follows.
|
151 |
+
|
152 |
+
i) without arguments: it will create a {1} magic named as the {0} being
|
153 |
+
decorated::
|
154 |
+
|
155 |
+
@deco
|
156 |
+
def foo(...)
|
157 |
+
|
158 |
+
will create a {1} magic named `foo`.
|
159 |
+
|
160 |
+
ii) with one string argument: which will be used as the actual name of the
|
161 |
+
resulting magic::
|
162 |
+
|
163 |
+
@deco('bar')
|
164 |
+
def foo(...)
|
165 |
+
|
166 |
+
will create a {1} magic named `bar`.
|
167 |
+
|
168 |
+
To register a class magic use ``Interactiveshell.register_magic(class or instance)``.
|
169 |
+
"""
|
170 |
+
|
171 |
+
# These two are decorator factories. While they are conceptually very similar,
|
172 |
+
# there are enough differences in the details that it's simpler to have them
|
173 |
+
# written as completely standalone functions rather than trying to share code
|
174 |
+
# and make a single one with convoluted logic.
|
175 |
+
|
176 |
+
def _method_magic_marker(magic_kind):
|
177 |
+
"""Decorator factory for methods in Magics subclasses.
|
178 |
+
"""
|
179 |
+
|
180 |
+
validate_type(magic_kind)
|
181 |
+
|
182 |
+
# This is a closure to capture the magic_kind. We could also use a class,
|
183 |
+
# but it's overkill for just that one bit of state.
|
184 |
+
def magic_deco(arg):
|
185 |
+
if callable(arg):
|
186 |
+
# "Naked" decorator call (just @foo, no args)
|
187 |
+
func = arg
|
188 |
+
name = func.__name__
|
189 |
+
retval = arg
|
190 |
+
record_magic(magics, magic_kind, name, name)
|
191 |
+
elif isinstance(arg, str):
|
192 |
+
# Decorator called with arguments (@foo('bar'))
|
193 |
+
name = arg
|
194 |
+
def mark(func, *a, **kw):
|
195 |
+
record_magic(magics, magic_kind, name, func.__name__)
|
196 |
+
return func
|
197 |
+
retval = mark
|
198 |
+
else:
|
199 |
+
raise TypeError("Decorator can only be called with "
|
200 |
+
"string or function")
|
201 |
+
return retval
|
202 |
+
|
203 |
+
# Ensure the resulting decorator has a usable docstring
|
204 |
+
magic_deco.__doc__ = _docstring_template.format('method', magic_kind)
|
205 |
+
return magic_deco
|
206 |
+
|
207 |
+
|
208 |
+
def _function_magic_marker(magic_kind):
|
209 |
+
"""Decorator factory for standalone functions.
|
210 |
+
"""
|
211 |
+
validate_type(magic_kind)
|
212 |
+
|
213 |
+
# This is a closure to capture the magic_kind. We could also use a class,
|
214 |
+
# but it's overkill for just that one bit of state.
|
215 |
+
def magic_deco(arg):
|
216 |
+
# Find get_ipython() in the caller's namespace
|
217 |
+
caller = sys._getframe(1)
|
218 |
+
for ns in ['f_locals', 'f_globals', 'f_builtins']:
|
219 |
+
get_ipython = getattr(caller, ns).get('get_ipython')
|
220 |
+
if get_ipython is not None:
|
221 |
+
break
|
222 |
+
else:
|
223 |
+
raise NameError('Decorator can only run in context where '
|
224 |
+
'`get_ipython` exists')
|
225 |
+
|
226 |
+
ip = get_ipython()
|
227 |
+
|
228 |
+
if callable(arg):
|
229 |
+
# "Naked" decorator call (just @foo, no args)
|
230 |
+
func = arg
|
231 |
+
name = func.__name__
|
232 |
+
ip.register_magic_function(func, magic_kind, name)
|
233 |
+
retval = arg
|
234 |
+
elif isinstance(arg, str):
|
235 |
+
# Decorator called with arguments (@foo('bar'))
|
236 |
+
name = arg
|
237 |
+
def mark(func, *a, **kw):
|
238 |
+
ip.register_magic_function(func, magic_kind, name)
|
239 |
+
return func
|
240 |
+
retval = mark
|
241 |
+
else:
|
242 |
+
raise TypeError("Decorator can only be called with "
|
243 |
+
"string or function")
|
244 |
+
return retval
|
245 |
+
|
246 |
+
# Ensure the resulting decorator has a usable docstring
|
247 |
+
ds = _docstring_template.format('function', magic_kind)
|
248 |
+
|
249 |
+
ds += dedent("""
|
250 |
+
Note: this decorator can only be used in a context where IPython is already
|
251 |
+
active, so that the `get_ipython()` call succeeds. You can therefore use
|
252 |
+
it in your startup files loaded after IPython initializes, but *not* in the
|
253 |
+
IPython configuration file itself, which is executed before IPython is
|
254 |
+
fully up and running. Any file located in the `startup` subdirectory of
|
255 |
+
your configuration profile will be OK in this sense.
|
256 |
+
""")
|
257 |
+
|
258 |
+
magic_deco.__doc__ = ds
|
259 |
+
return magic_deco
|
260 |
+
|
261 |
+
|
262 |
+
MAGIC_NO_VAR_EXPAND_ATTR = "_ipython_magic_no_var_expand"
|
263 |
+
MAGIC_OUTPUT_CAN_BE_SILENCED = "_ipython_magic_output_can_be_silenced"
|
264 |
+
|
265 |
+
|
266 |
+
def no_var_expand(magic_func):
|
267 |
+
"""Mark a magic function as not needing variable expansion
|
268 |
+
|
269 |
+
By default, IPython interprets `{a}` or `$a` in the line passed to magics
|
270 |
+
as variables that should be interpolated from the interactive namespace
|
271 |
+
before passing the line to the magic function.
|
272 |
+
This is not always desirable, e.g. when the magic executes Python code
|
273 |
+
(%timeit, %time, etc.).
|
274 |
+
Decorate magics with `@no_var_expand` to opt-out of variable expansion.
|
275 |
+
|
276 |
+
.. versionadded:: 7.3
|
277 |
+
"""
|
278 |
+
setattr(magic_func, MAGIC_NO_VAR_EXPAND_ATTR, True)
|
279 |
+
return magic_func
|
280 |
+
|
281 |
+
|
282 |
+
def output_can_be_silenced(magic_func):
|
283 |
+
"""Mark a magic function so its output may be silenced.
|
284 |
+
|
285 |
+
The output is silenced if the Python code used as a parameter of
|
286 |
+
the magic ends in a semicolon, not counting a Python comment that can
|
287 |
+
follow it.
|
288 |
+
"""
|
289 |
+
setattr(magic_func, MAGIC_OUTPUT_CAN_BE_SILENCED, True)
|
290 |
+
return magic_func
|
291 |
+
|
292 |
+
# Create the actual decorators for public use
|
293 |
+
|
294 |
+
# These three are used to decorate methods in class definitions
|
295 |
+
line_magic = _method_magic_marker('line')
|
296 |
+
cell_magic = _method_magic_marker('cell')
|
297 |
+
line_cell_magic = _method_magic_marker('line_cell')
|
298 |
+
|
299 |
+
# These three decorate standalone functions and perform the decoration
|
300 |
+
# immediately. They can only run where get_ipython() works
|
301 |
+
register_line_magic = _function_magic_marker('line')
|
302 |
+
register_cell_magic = _function_magic_marker('cell')
|
303 |
+
register_line_cell_magic = _function_magic_marker('line_cell')
|
304 |
+
|
305 |
+
#-----------------------------------------------------------------------------
|
306 |
+
# Core Magic classes
|
307 |
+
#-----------------------------------------------------------------------------
|
308 |
+
|
309 |
+
class MagicsManager(Configurable):
|
310 |
+
"""Object that handles all magic-related functionality for IPython.
|
311 |
+
"""
|
312 |
+
# Non-configurable class attributes
|
313 |
+
|
314 |
+
# A two-level dict, first keyed by magic type, then by magic function, and
|
315 |
+
# holding the actual callable object as value. This is the dict used for
|
316 |
+
# magic function dispatch
|
317 |
+
magics = Dict()
|
318 |
+
lazy_magics = Dict(
|
319 |
+
help="""
|
320 |
+
Mapping from magic names to modules to load.
|
321 |
+
|
322 |
+
This can be used in IPython/IPykernel configuration to declare lazy magics
|
323 |
+
that will only be imported/registered on first use.
|
324 |
+
|
325 |
+
For example::
|
326 |
+
|
327 |
+
c.MagicsManager.lazy_magics = {
|
328 |
+
"my_magic": "slow.to.import",
|
329 |
+
"my_other_magic": "also.slow",
|
330 |
+
}
|
331 |
+
|
332 |
+
On first invocation of `%my_magic`, `%%my_magic`, `%%my_other_magic` or
|
333 |
+
`%%my_other_magic`, the corresponding module will be loaded as an ipython
|
334 |
+
extensions as if you had previously done `%load_ext ipython`.
|
335 |
+
|
336 |
+
Magics names should be without percent(s) as magics can be both cell
|
337 |
+
and line magics.
|
338 |
+
|
339 |
+
Lazy loading happen relatively late in execution process, and
|
340 |
+
complex extensions that manipulate Python/IPython internal state or global state
|
341 |
+
might not support lazy loading.
|
342 |
+
"""
|
343 |
+
).tag(
|
344 |
+
config=True,
|
345 |
+
)
|
346 |
+
|
347 |
+
# A registry of the original objects that we've been given holding magics.
|
348 |
+
registry = Dict()
|
349 |
+
|
350 |
+
shell = Instance('IPython.core.interactiveshell.InteractiveShellABC', allow_none=True)
|
351 |
+
|
352 |
+
auto_magic = Bool(True, help=
|
353 |
+
"Automatically call line magics without requiring explicit % prefix"
|
354 |
+
).tag(config=True)
|
355 |
+
@observe('auto_magic')
|
356 |
+
def _auto_magic_changed(self, change):
|
357 |
+
self.shell.automagic = change['new']
|
358 |
+
|
359 |
+
_auto_status = [
|
360 |
+
'Automagic is OFF, % prefix IS needed for line magics.',
|
361 |
+
'Automagic is ON, % prefix IS NOT needed for line magics.']
|
362 |
+
|
363 |
+
user_magics = Instance('IPython.core.magics.UserMagics', allow_none=True)
|
364 |
+
|
365 |
+
def __init__(self, shell=None, config=None, user_magics=None, **traits):
|
366 |
+
|
367 |
+
super(MagicsManager, self).__init__(shell=shell, config=config,
|
368 |
+
user_magics=user_magics, **traits)
|
369 |
+
self.magics = dict(line={}, cell={})
|
370 |
+
# Let's add the user_magics to the registry for uniformity, so *all*
|
371 |
+
# registered magic containers can be found there.
|
372 |
+
self.registry[user_magics.__class__.__name__] = user_magics
|
373 |
+
|
374 |
+
def auto_status(self):
|
375 |
+
"""Return descriptive string with automagic status."""
|
376 |
+
return self._auto_status[self.auto_magic]
|
377 |
+
|
378 |
+
def lsmagic(self):
|
379 |
+
"""Return a dict of currently available magic functions.
|
380 |
+
|
381 |
+
The return dict has the keys 'line' and 'cell', corresponding to the
|
382 |
+
two types of magics we support. Each value is a list of names.
|
383 |
+
"""
|
384 |
+
return self.magics
|
385 |
+
|
386 |
+
def lsmagic_docs(self, brief=False, missing=''):
|
387 |
+
"""Return dict of documentation of magic functions.
|
388 |
+
|
389 |
+
The return dict has the keys 'line' and 'cell', corresponding to the
|
390 |
+
two types of magics we support. Each value is a dict keyed by magic
|
391 |
+
name whose value is the function docstring. If a docstring is
|
392 |
+
unavailable, the value of `missing` is used instead.
|
393 |
+
|
394 |
+
If brief is True, only the first line of each docstring will be returned.
|
395 |
+
"""
|
396 |
+
docs = {}
|
397 |
+
for m_type in self.magics:
|
398 |
+
m_docs = {}
|
399 |
+
for m_name, m_func in self.magics[m_type].items():
|
400 |
+
if m_func.__doc__:
|
401 |
+
if brief:
|
402 |
+
m_docs[m_name] = m_func.__doc__.split('\n', 1)[0]
|
403 |
+
else:
|
404 |
+
m_docs[m_name] = m_func.__doc__.rstrip()
|
405 |
+
else:
|
406 |
+
m_docs[m_name] = missing
|
407 |
+
docs[m_type] = m_docs
|
408 |
+
return docs
|
409 |
+
|
410 |
+
def register_lazy(self, name: str, fully_qualified_name: str):
|
411 |
+
"""
|
412 |
+
Lazily register a magic via an extension.
|
413 |
+
|
414 |
+
|
415 |
+
Parameters
|
416 |
+
----------
|
417 |
+
name : str
|
418 |
+
Name of the magic you wish to register.
|
419 |
+
fully_qualified_name :
|
420 |
+
Fully qualified name of the module/submodule that should be loaded
|
421 |
+
as an extensions when the magic is first called.
|
422 |
+
It is assumed that loading this extensions will register the given
|
423 |
+
magic.
|
424 |
+
"""
|
425 |
+
|
426 |
+
self.lazy_magics[name] = fully_qualified_name
|
427 |
+
|
428 |
+
def register(self, *magic_objects):
|
429 |
+
"""Register one or more instances of Magics.
|
430 |
+
|
431 |
+
Take one or more classes or instances of classes that subclass the main
|
432 |
+
`core.Magic` class, and register them with IPython to use the magic
|
433 |
+
functions they provide. The registration process will then ensure that
|
434 |
+
any methods that have decorated to provide line and/or cell magics will
|
435 |
+
be recognized with the `%x`/`%%x` syntax as a line/cell magic
|
436 |
+
respectively.
|
437 |
+
|
438 |
+
If classes are given, they will be instantiated with the default
|
439 |
+
constructor. If your classes need a custom constructor, you should
|
440 |
+
instanitate them first and pass the instance.
|
441 |
+
|
442 |
+
The provided arguments can be an arbitrary mix of classes and instances.
|
443 |
+
|
444 |
+
Parameters
|
445 |
+
----------
|
446 |
+
*magic_objects : one or more classes or instances
|
447 |
+
"""
|
448 |
+
# Start by validating them to ensure they have all had their magic
|
449 |
+
# methods registered at the instance level
|
450 |
+
for m in magic_objects:
|
451 |
+
if not m.registered:
|
452 |
+
raise ValueError("Class of magics %r was constructed without "
|
453 |
+
"the @register_magics class decorator")
|
454 |
+
if isinstance(m, type):
|
455 |
+
# If we're given an uninstantiated class
|
456 |
+
m = m(shell=self.shell)
|
457 |
+
|
458 |
+
# Now that we have an instance, we can register it and update the
|
459 |
+
# table of callables
|
460 |
+
self.registry[m.__class__.__name__] = m
|
461 |
+
for mtype in magic_kinds:
|
462 |
+
self.magics[mtype].update(m.magics[mtype])
|
463 |
+
|
464 |
+
def register_function(self, func, magic_kind='line', magic_name=None):
|
465 |
+
"""Expose a standalone function as magic function for IPython.
|
466 |
+
|
467 |
+
This will create an IPython magic (line, cell or both) from a
|
468 |
+
standalone function. The functions should have the following
|
469 |
+
signatures:
|
470 |
+
|
471 |
+
* For line magics: `def f(line)`
|
472 |
+
* For cell magics: `def f(line, cell)`
|
473 |
+
* For a function that does both: `def f(line, cell=None)`
|
474 |
+
|
475 |
+
In the latter case, the function will be called with `cell==None` when
|
476 |
+
invoked as `%f`, and with cell as a string when invoked as `%%f`.
|
477 |
+
|
478 |
+
Parameters
|
479 |
+
----------
|
480 |
+
func : callable
|
481 |
+
Function to be registered as a magic.
|
482 |
+
magic_kind : str
|
483 |
+
Kind of magic, one of 'line', 'cell' or 'line_cell'
|
484 |
+
magic_name : optional str
|
485 |
+
If given, the name the magic will have in the IPython namespace. By
|
486 |
+
default, the name of the function itself is used.
|
487 |
+
"""
|
488 |
+
|
489 |
+
# Create the new method in the user_magics and register it in the
|
490 |
+
# global table
|
491 |
+
validate_type(magic_kind)
|
492 |
+
magic_name = func.__name__ if magic_name is None else magic_name
|
493 |
+
setattr(self.user_magics, magic_name, func)
|
494 |
+
record_magic(self.magics, magic_kind, magic_name, func)
|
495 |
+
|
496 |
+
def register_alias(self, alias_name, magic_name, magic_kind='line', magic_params=None):
|
497 |
+
"""Register an alias to a magic function.
|
498 |
+
|
499 |
+
The alias is an instance of :class:`MagicAlias`, which holds the
|
500 |
+
name and kind of the magic it should call. Binding is done at
|
501 |
+
call time, so if the underlying magic function is changed the alias
|
502 |
+
will call the new function.
|
503 |
+
|
504 |
+
Parameters
|
505 |
+
----------
|
506 |
+
alias_name : str
|
507 |
+
The name of the magic to be registered.
|
508 |
+
magic_name : str
|
509 |
+
The name of an existing magic.
|
510 |
+
magic_kind : str
|
511 |
+
Kind of magic, one of 'line' or 'cell'
|
512 |
+
"""
|
513 |
+
|
514 |
+
# `validate_type` is too permissive, as it allows 'line_cell'
|
515 |
+
# which we do not handle.
|
516 |
+
if magic_kind not in magic_kinds:
|
517 |
+
raise ValueError('magic_kind must be one of %s, %s given' %
|
518 |
+
magic_kinds, magic_kind)
|
519 |
+
|
520 |
+
alias = MagicAlias(self.shell, magic_name, magic_kind, magic_params)
|
521 |
+
setattr(self.user_magics, alias_name, alias)
|
522 |
+
record_magic(self.magics, magic_kind, alias_name, alias)
|
523 |
+
|
524 |
+
# Key base class that provides the central functionality for magics.
|
525 |
+
|
526 |
+
|
527 |
+
class Magics(Configurable):
|
528 |
+
"""Base class for implementing magic functions.
|
529 |
+
|
530 |
+
Shell functions which can be reached as %function_name. All magic
|
531 |
+
functions should accept a string, which they can parse for their own
|
532 |
+
needs. This can make some functions easier to type, eg `%cd ../`
|
533 |
+
vs. `%cd("../")`
|
534 |
+
|
535 |
+
Classes providing magic functions need to subclass this class, and they
|
536 |
+
MUST:
|
537 |
+
|
538 |
+
- Use the method decorators `@line_magic` and `@cell_magic` to decorate
|
539 |
+
individual methods as magic functions, AND
|
540 |
+
|
541 |
+
- Use the class decorator `@magics_class` to ensure that the magic
|
542 |
+
methods are properly registered at the instance level upon instance
|
543 |
+
initialization.
|
544 |
+
|
545 |
+
See :mod:`magic_functions` for examples of actual implementation classes.
|
546 |
+
"""
|
547 |
+
# Dict holding all command-line options for each magic.
|
548 |
+
options_table = None
|
549 |
+
# Dict for the mapping of magic names to methods, set by class decorator
|
550 |
+
magics = None
|
551 |
+
# Flag to check that the class decorator was properly applied
|
552 |
+
registered = False
|
553 |
+
# Instance of IPython shell
|
554 |
+
shell = None
|
555 |
+
|
556 |
+
def __init__(self, shell=None, **kwargs):
|
557 |
+
if not(self.__class__.registered):
|
558 |
+
raise ValueError('Magics subclass without registration - '
|
559 |
+
'did you forget to apply @magics_class?')
|
560 |
+
if shell is not None:
|
561 |
+
if hasattr(shell, 'configurables'):
|
562 |
+
shell.configurables.append(self)
|
563 |
+
if hasattr(shell, 'config'):
|
564 |
+
kwargs.setdefault('parent', shell)
|
565 |
+
|
566 |
+
self.shell = shell
|
567 |
+
self.options_table = {}
|
568 |
+
# The method decorators are run when the instance doesn't exist yet, so
|
569 |
+
# they can only record the names of the methods they are supposed to
|
570 |
+
# grab. Only now, that the instance exists, can we create the proper
|
571 |
+
# mapping to bound methods. So we read the info off the original names
|
572 |
+
# table and replace each method name by the actual bound method.
|
573 |
+
# But we mustn't clobber the *class* mapping, in case of multiple instances.
|
574 |
+
class_magics = self.magics
|
575 |
+
self.magics = {}
|
576 |
+
for mtype in magic_kinds:
|
577 |
+
tab = self.magics[mtype] = {}
|
578 |
+
cls_tab = class_magics[mtype]
|
579 |
+
for magic_name, meth_name in cls_tab.items():
|
580 |
+
if isinstance(meth_name, str):
|
581 |
+
# it's a method name, grab it
|
582 |
+
tab[magic_name] = getattr(self, meth_name)
|
583 |
+
else:
|
584 |
+
# it's the real thing
|
585 |
+
tab[magic_name] = meth_name
|
586 |
+
# Configurable **needs** to be initiated at the end or the config
|
587 |
+
# magics get screwed up.
|
588 |
+
super(Magics, self).__init__(**kwargs)
|
589 |
+
|
590 |
+
def arg_err(self,func):
|
591 |
+
"""Print docstring if incorrect arguments were passed"""
|
592 |
+
print('Error in arguments:')
|
593 |
+
print(oinspect.getdoc(func))
|
594 |
+
|
595 |
+
def format_latex(self, strng):
|
596 |
+
"""Format a string for latex inclusion."""
|
597 |
+
|
598 |
+
# Characters that need to be escaped for latex:
|
599 |
+
escape_re = re.compile(r'(%|_|\$|#|&)',re.MULTILINE)
|
600 |
+
# Magic command names as headers:
|
601 |
+
cmd_name_re = re.compile(r'^(%s.*?):' % ESC_MAGIC,
|
602 |
+
re.MULTILINE)
|
603 |
+
# Magic commands
|
604 |
+
cmd_re = re.compile(r'(?P<cmd>%s.+?\b)(?!\}\}:)' % ESC_MAGIC,
|
605 |
+
re.MULTILINE)
|
606 |
+
# Paragraph continue
|
607 |
+
par_re = re.compile(r'\\$',re.MULTILINE)
|
608 |
+
|
609 |
+
# The "\n" symbol
|
610 |
+
newline_re = re.compile(r'\\n')
|
611 |
+
|
612 |
+
# Now build the string for output:
|
613 |
+
#strng = cmd_name_re.sub(r'\n\\texttt{\\textsl{\\large \1}}:',strng)
|
614 |
+
strng = cmd_name_re.sub(r'\n\\bigskip\n\\texttt{\\textbf{ \1}}:',
|
615 |
+
strng)
|
616 |
+
strng = cmd_re.sub(r'\\texttt{\g<cmd>}',strng)
|
617 |
+
strng = par_re.sub(r'\\\\',strng)
|
618 |
+
strng = escape_re.sub(r'\\\1',strng)
|
619 |
+
strng = newline_re.sub(r'\\textbackslash{}n',strng)
|
620 |
+
return strng
|
621 |
+
|
622 |
+
def parse_options(self, arg_str, opt_str, *long_opts, **kw):
|
623 |
+
"""Parse options passed to an argument string.
|
624 |
+
|
625 |
+
The interface is similar to that of :func:`getopt.getopt`, but it
|
626 |
+
returns a :class:`~IPython.utils.struct.Struct` with the options as keys
|
627 |
+
and the stripped argument string still as a string.
|
628 |
+
|
629 |
+
arg_str is quoted as a true sys.argv vector by using shlex.split.
|
630 |
+
This allows us to easily expand variables, glob files, quote
|
631 |
+
arguments, etc.
|
632 |
+
|
633 |
+
Parameters
|
634 |
+
----------
|
635 |
+
arg_str : str
|
636 |
+
The arguments to parse.
|
637 |
+
opt_str : str
|
638 |
+
The options specification.
|
639 |
+
mode : str, default 'string'
|
640 |
+
If given as 'list', the argument string is returned as a list (split
|
641 |
+
on whitespace) instead of a string.
|
642 |
+
list_all : bool, default False
|
643 |
+
Put all option values in lists. Normally only options
|
644 |
+
appearing more than once are put in a list.
|
645 |
+
posix : bool, default True
|
646 |
+
Whether to split the input line in POSIX mode or not, as per the
|
647 |
+
conventions outlined in the :mod:`shlex` module from the standard
|
648 |
+
library.
|
649 |
+
"""
|
650 |
+
|
651 |
+
# inject default options at the beginning of the input line
|
652 |
+
caller = sys._getframe(1).f_code.co_name
|
653 |
+
arg_str = '%s %s' % (self.options_table.get(caller,''),arg_str)
|
654 |
+
|
655 |
+
mode = kw.get('mode','string')
|
656 |
+
if mode not in ['string','list']:
|
657 |
+
raise ValueError('incorrect mode given: %s' % mode)
|
658 |
+
# Get options
|
659 |
+
list_all = kw.get('list_all',0)
|
660 |
+
posix = kw.get('posix', os.name == 'posix')
|
661 |
+
strict = kw.get('strict', True)
|
662 |
+
|
663 |
+
preserve_non_opts = kw.get("preserve_non_opts", False)
|
664 |
+
remainder_arg_str = arg_str
|
665 |
+
|
666 |
+
# Check if we have more than one argument to warrant extra processing:
|
667 |
+
odict = {} # Dictionary with options
|
668 |
+
args = arg_str.split()
|
669 |
+
if len(args) >= 1:
|
670 |
+
# If the list of inputs only has 0 or 1 thing in it, there's no
|
671 |
+
# need to look for options
|
672 |
+
argv = arg_split(arg_str, posix, strict)
|
673 |
+
# Do regular option processing
|
674 |
+
try:
|
675 |
+
opts,args = getopt(argv, opt_str, long_opts)
|
676 |
+
except GetoptError as e:
|
677 |
+
raise UsageError(
|
678 |
+
'%s ( allowed: "%s" %s)' % (e.msg, opt_str, " ".join(long_opts))
|
679 |
+
) from e
|
680 |
+
for o, a in opts:
|
681 |
+
if mode == "string" and preserve_non_opts:
|
682 |
+
# remove option-parts from the original args-string and preserve remaining-part.
|
683 |
+
# This relies on the arg_split(...) and getopt(...)'s impl spec, that the parsed options are
|
684 |
+
# returned in the original order.
|
685 |
+
remainder_arg_str = remainder_arg_str.replace(o, "", 1).replace(
|
686 |
+
a, "", 1
|
687 |
+
)
|
688 |
+
if o.startswith("--"):
|
689 |
+
o = o[2:]
|
690 |
+
else:
|
691 |
+
o = o[1:]
|
692 |
+
try:
|
693 |
+
odict[o].append(a)
|
694 |
+
except AttributeError:
|
695 |
+
odict[o] = [odict[o],a]
|
696 |
+
except KeyError:
|
697 |
+
if list_all:
|
698 |
+
odict[o] = [a]
|
699 |
+
else:
|
700 |
+
odict[o] = a
|
701 |
+
|
702 |
+
# Prepare opts,args for return
|
703 |
+
opts = Struct(odict)
|
704 |
+
if mode == 'string':
|
705 |
+
if preserve_non_opts:
|
706 |
+
args = remainder_arg_str.lstrip()
|
707 |
+
else:
|
708 |
+
args = " ".join(args)
|
709 |
+
|
710 |
+
return opts,args
|
711 |
+
|
712 |
+
def default_option(self, fn, optstr):
|
713 |
+
"""Make an entry in the options_table for fn, with value optstr"""
|
714 |
+
|
715 |
+
if fn not in self.lsmagic():
|
716 |
+
error("%s is not a magic function" % fn)
|
717 |
+
self.options_table[fn] = optstr
|
718 |
+
|
719 |
+
|
720 |
+
class MagicAlias(object):
|
721 |
+
"""An alias to another magic function.
|
722 |
+
|
723 |
+
An alias is determined by its magic name and magic kind. Lookup
|
724 |
+
is done at call time, so if the underlying magic changes the alias
|
725 |
+
will call the new function.
|
726 |
+
|
727 |
+
Use the :meth:`MagicsManager.register_alias` method or the
|
728 |
+
`%alias_magic` magic function to create and register a new alias.
|
729 |
+
"""
|
730 |
+
def __init__(self, shell, magic_name, magic_kind, magic_params=None):
|
731 |
+
self.shell = shell
|
732 |
+
self.magic_name = magic_name
|
733 |
+
self.magic_params = magic_params
|
734 |
+
self.magic_kind = magic_kind
|
735 |
+
|
736 |
+
self.pretty_target = '%s%s' % (magic_escapes[self.magic_kind], self.magic_name)
|
737 |
+
self.__doc__ = "Alias for `%s`." % self.pretty_target
|
738 |
+
|
739 |
+
self._in_call = False
|
740 |
+
|
741 |
+
def __call__(self, *args, **kwargs):
|
742 |
+
"""Call the magic alias."""
|
743 |
+
fn = self.shell.find_magic(self.magic_name, self.magic_kind)
|
744 |
+
if fn is None:
|
745 |
+
raise UsageError("Magic `%s` not found." % self.pretty_target)
|
746 |
+
|
747 |
+
# Protect against infinite recursion.
|
748 |
+
if self._in_call:
|
749 |
+
raise UsageError("Infinite recursion detected; "
|
750 |
+
"magic aliases cannot call themselves.")
|
751 |
+
self._in_call = True
|
752 |
+
try:
|
753 |
+
if self.magic_params:
|
754 |
+
args_list = list(args)
|
755 |
+
args_list[0] = self.magic_params + " " + args[0]
|
756 |
+
args = tuple(args_list)
|
757 |
+
return fn(*args, **kwargs)
|
758 |
+
finally:
|
759 |
+
self._in_call = False
|
openalex_env_map/lib/python3.10/site-packages/IPython/core/magic_arguments.py
ADDED
@@ -0,0 +1,310 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
''' A decorator-based method of constructing IPython magics with `argparse`
|
2 |
+
option handling.
|
3 |
+
|
4 |
+
New magic functions can be defined like so::
|
5 |
+
|
6 |
+
from IPython.core.magic_arguments import (argument, magic_arguments,
|
7 |
+
parse_argstring)
|
8 |
+
|
9 |
+
@magic_arguments()
|
10 |
+
@argument('-o', '--option', help='An optional argument.')
|
11 |
+
@argument('arg', type=int, help='An integer positional argument.')
|
12 |
+
def magic_cool(self, arg):
|
13 |
+
""" A really cool magic command.
|
14 |
+
|
15 |
+
"""
|
16 |
+
args = parse_argstring(magic_cool, arg)
|
17 |
+
...
|
18 |
+
|
19 |
+
The `@magic_arguments` decorator marks the function as having argparse arguments.
|
20 |
+
The `@argument` decorator adds an argument using the same syntax as argparse's
|
21 |
+
`add_argument()` method. More sophisticated uses may also require the
|
22 |
+
`@argument_group` or `@kwds` decorator to customize the formatting and the
|
23 |
+
parsing.
|
24 |
+
|
25 |
+
Help text for the magic is automatically generated from the docstring and the
|
26 |
+
arguments::
|
27 |
+
|
28 |
+
In[1]: %cool?
|
29 |
+
%cool [-o OPTION] arg
|
30 |
+
|
31 |
+
A really cool magic command.
|
32 |
+
|
33 |
+
positional arguments:
|
34 |
+
arg An integer positional argument.
|
35 |
+
|
36 |
+
optional arguments:
|
37 |
+
-o OPTION, --option OPTION
|
38 |
+
An optional argument.
|
39 |
+
|
40 |
+
Here is an elaborated example that uses default parameters in `argument` and calls the `args` in the cell magic::
|
41 |
+
|
42 |
+
from IPython.core.magic import register_cell_magic
|
43 |
+
from IPython.core.magic_arguments import (argument, magic_arguments,
|
44 |
+
parse_argstring)
|
45 |
+
|
46 |
+
|
47 |
+
@magic_arguments()
|
48 |
+
@argument(
|
49 |
+
"--option",
|
50 |
+
"-o",
|
51 |
+
help=("Add an option here"),
|
52 |
+
)
|
53 |
+
@argument(
|
54 |
+
"--style",
|
55 |
+
"-s",
|
56 |
+
default="foo",
|
57 |
+
help=("Add some style arguments"),
|
58 |
+
)
|
59 |
+
@register_cell_magic
|
60 |
+
def my_cell_magic(line, cell):
|
61 |
+
args = parse_argstring(my_cell_magic, line)
|
62 |
+
print(f"{args.option=}")
|
63 |
+
print(f"{args.style=}")
|
64 |
+
print(f"{cell=}")
|
65 |
+
|
66 |
+
In a jupyter notebook, this cell magic can be executed like this::
|
67 |
+
|
68 |
+
%%my_cell_magic -o Hello
|
69 |
+
print("bar")
|
70 |
+
i = 42
|
71 |
+
|
72 |
+
Inheritance diagram:
|
73 |
+
|
74 |
+
.. inheritance-diagram:: IPython.core.magic_arguments
|
75 |
+
:parts: 3
|
76 |
+
|
77 |
+
'''
|
78 |
+
#-----------------------------------------------------------------------------
|
79 |
+
# Copyright (C) 2010-2011, IPython Development Team.
|
80 |
+
#
|
81 |
+
# Distributed under the terms of the Modified BSD License.
|
82 |
+
#
|
83 |
+
# The full license is in the file COPYING.txt, distributed with this software.
|
84 |
+
#-----------------------------------------------------------------------------
|
85 |
+
import argparse
|
86 |
+
import re
|
87 |
+
|
88 |
+
# Our own imports
|
89 |
+
from IPython.core.error import UsageError
|
90 |
+
from IPython.utils.decorators import undoc
|
91 |
+
from IPython.utils.process import arg_split
|
92 |
+
from IPython.utils.text import dedent
|
93 |
+
|
94 |
+
NAME_RE = re.compile(r"[a-zA-Z][a-zA-Z0-9_-]*$")
|
95 |
+
|
96 |
+
@undoc
|
97 |
+
class MagicHelpFormatter(argparse.RawDescriptionHelpFormatter):
|
98 |
+
"""A HelpFormatter with a couple of changes to meet our needs.
|
99 |
+
"""
|
100 |
+
# Modified to dedent text.
|
101 |
+
def _fill_text(self, text, width, indent):
|
102 |
+
return argparse.RawDescriptionHelpFormatter._fill_text(self, dedent(text), width, indent)
|
103 |
+
|
104 |
+
# Modified to wrap argument placeholders in <> where necessary.
|
105 |
+
def _format_action_invocation(self, action):
|
106 |
+
if not action.option_strings:
|
107 |
+
metavar, = self._metavar_formatter(action, action.dest)(1)
|
108 |
+
return metavar
|
109 |
+
|
110 |
+
else:
|
111 |
+
parts = []
|
112 |
+
|
113 |
+
# if the Optional doesn't take a value, format is:
|
114 |
+
# -s, --long
|
115 |
+
if action.nargs == 0:
|
116 |
+
parts.extend(action.option_strings)
|
117 |
+
|
118 |
+
# if the Optional takes a value, format is:
|
119 |
+
# -s ARGS, --long ARGS
|
120 |
+
else:
|
121 |
+
default = action.dest.upper()
|
122 |
+
args_string = self._format_args(action, default)
|
123 |
+
# IPYTHON MODIFICATION: If args_string is not a plain name, wrap
|
124 |
+
# it in <> so it's valid RST.
|
125 |
+
if not NAME_RE.match(args_string):
|
126 |
+
args_string = "<%s>" % args_string
|
127 |
+
for option_string in action.option_strings:
|
128 |
+
parts.append('%s %s' % (option_string, args_string))
|
129 |
+
|
130 |
+
return ', '.join(parts)
|
131 |
+
|
132 |
+
# Override the default prefix ('usage') to our % magic escape,
|
133 |
+
# in a code block.
|
134 |
+
def add_usage(self, usage, actions, groups, prefix="::\n\n %"):
|
135 |
+
super(MagicHelpFormatter, self).add_usage(usage, actions, groups, prefix)
|
136 |
+
|
137 |
+
class MagicArgumentParser(argparse.ArgumentParser):
|
138 |
+
""" An ArgumentParser tweaked for use by IPython magics.
|
139 |
+
"""
|
140 |
+
def __init__(self,
|
141 |
+
prog=None,
|
142 |
+
usage=None,
|
143 |
+
description=None,
|
144 |
+
epilog=None,
|
145 |
+
parents=None,
|
146 |
+
formatter_class=MagicHelpFormatter,
|
147 |
+
prefix_chars='-',
|
148 |
+
argument_default=None,
|
149 |
+
conflict_handler='error',
|
150 |
+
add_help=False):
|
151 |
+
if parents is None:
|
152 |
+
parents = []
|
153 |
+
super(MagicArgumentParser, self).__init__(prog=prog, usage=usage,
|
154 |
+
description=description, epilog=epilog,
|
155 |
+
parents=parents, formatter_class=formatter_class,
|
156 |
+
prefix_chars=prefix_chars, argument_default=argument_default,
|
157 |
+
conflict_handler=conflict_handler, add_help=add_help)
|
158 |
+
|
159 |
+
def error(self, message):
|
160 |
+
""" Raise a catchable error instead of exiting.
|
161 |
+
"""
|
162 |
+
raise UsageError(message)
|
163 |
+
|
164 |
+
def parse_argstring(self, argstring):
|
165 |
+
""" Split a string into an argument list and parse that argument list.
|
166 |
+
"""
|
167 |
+
argv = arg_split(argstring)
|
168 |
+
return self.parse_args(argv)
|
169 |
+
|
170 |
+
|
171 |
+
def construct_parser(magic_func):
|
172 |
+
""" Construct an argument parser using the function decorations.
|
173 |
+
"""
|
174 |
+
kwds = getattr(magic_func, 'argcmd_kwds', {})
|
175 |
+
if 'description' not in kwds:
|
176 |
+
kwds['description'] = getattr(magic_func, '__doc__', None)
|
177 |
+
arg_name = real_name(magic_func)
|
178 |
+
parser = MagicArgumentParser(arg_name, **kwds)
|
179 |
+
# Reverse the list of decorators in order to apply them in the
|
180 |
+
# order in which they appear in the source.
|
181 |
+
group = None
|
182 |
+
for deco in magic_func.decorators[::-1]:
|
183 |
+
result = deco.add_to_parser(parser, group)
|
184 |
+
if result is not None:
|
185 |
+
group = result
|
186 |
+
|
187 |
+
# Replace the magic function's docstring with the full help text.
|
188 |
+
magic_func.__doc__ = parser.format_help()
|
189 |
+
|
190 |
+
return parser
|
191 |
+
|
192 |
+
|
193 |
+
def parse_argstring(magic_func, argstring):
|
194 |
+
""" Parse the string of arguments for the given magic function.
|
195 |
+
"""
|
196 |
+
return magic_func.parser.parse_argstring(argstring)
|
197 |
+
|
198 |
+
|
199 |
+
def real_name(magic_func):
|
200 |
+
""" Find the real name of the magic.
|
201 |
+
"""
|
202 |
+
magic_name = magic_func.__name__
|
203 |
+
if magic_name.startswith('magic_'):
|
204 |
+
magic_name = magic_name[len('magic_'):]
|
205 |
+
return getattr(magic_func, 'argcmd_name', magic_name)
|
206 |
+
|
207 |
+
|
208 |
+
class ArgDecorator(object):
|
209 |
+
""" Base class for decorators to add ArgumentParser information to a method.
|
210 |
+
"""
|
211 |
+
|
212 |
+
def __call__(self, func):
|
213 |
+
if not getattr(func, 'has_arguments', False):
|
214 |
+
func.has_arguments = True
|
215 |
+
func.decorators = []
|
216 |
+
func.decorators.append(self)
|
217 |
+
return func
|
218 |
+
|
219 |
+
def add_to_parser(self, parser, group):
|
220 |
+
""" Add this object's information to the parser, if necessary.
|
221 |
+
"""
|
222 |
+
pass
|
223 |
+
|
224 |
+
|
225 |
+
class magic_arguments(ArgDecorator):
|
226 |
+
""" Mark the magic as having argparse arguments and possibly adjust the
|
227 |
+
name.
|
228 |
+
"""
|
229 |
+
|
230 |
+
def __init__(self, name=None):
|
231 |
+
self.name = name
|
232 |
+
|
233 |
+
def __call__(self, func):
|
234 |
+
if not getattr(func, 'has_arguments', False):
|
235 |
+
func.has_arguments = True
|
236 |
+
func.decorators = []
|
237 |
+
if self.name is not None:
|
238 |
+
func.argcmd_name = self.name
|
239 |
+
# This should be the first decorator in the list of decorators, thus the
|
240 |
+
# last to execute. Build the parser.
|
241 |
+
func.parser = construct_parser(func)
|
242 |
+
return func
|
243 |
+
|
244 |
+
|
245 |
+
class ArgMethodWrapper(ArgDecorator):
|
246 |
+
|
247 |
+
"""
|
248 |
+
Base class to define a wrapper for ArgumentParser method.
|
249 |
+
|
250 |
+
Child class must define either `_method_name` or `add_to_parser`.
|
251 |
+
|
252 |
+
"""
|
253 |
+
|
254 |
+
_method_name: str
|
255 |
+
|
256 |
+
def __init__(self, *args, **kwds):
|
257 |
+
self.args = args
|
258 |
+
self.kwds = kwds
|
259 |
+
|
260 |
+
def add_to_parser(self, parser, group):
|
261 |
+
""" Add this object's information to the parser.
|
262 |
+
"""
|
263 |
+
if group is not None:
|
264 |
+
parser = group
|
265 |
+
getattr(parser, self._method_name)(*self.args, **self.kwds)
|
266 |
+
return None
|
267 |
+
|
268 |
+
|
269 |
+
class argument(ArgMethodWrapper):
|
270 |
+
""" Store arguments and keywords to pass to add_argument().
|
271 |
+
|
272 |
+
Instances also serve to decorate command methods.
|
273 |
+
"""
|
274 |
+
_method_name = 'add_argument'
|
275 |
+
|
276 |
+
|
277 |
+
class defaults(ArgMethodWrapper):
|
278 |
+
""" Store arguments and keywords to pass to set_defaults().
|
279 |
+
|
280 |
+
Instances also serve to decorate command methods.
|
281 |
+
"""
|
282 |
+
_method_name = 'set_defaults'
|
283 |
+
|
284 |
+
|
285 |
+
class argument_group(ArgMethodWrapper):
|
286 |
+
""" Store arguments and keywords to pass to add_argument_group().
|
287 |
+
|
288 |
+
Instances also serve to decorate command methods.
|
289 |
+
"""
|
290 |
+
|
291 |
+
def add_to_parser(self, parser, group):
|
292 |
+
""" Add this object's information to the parser.
|
293 |
+
"""
|
294 |
+
return parser.add_argument_group(*self.args, **self.kwds)
|
295 |
+
|
296 |
+
|
297 |
+
class kwds(ArgDecorator):
|
298 |
+
""" Provide other keywords to the sub-parser constructor.
|
299 |
+
"""
|
300 |
+
def __init__(self, **kwds):
|
301 |
+
self.kwds = kwds
|
302 |
+
|
303 |
+
def __call__(self, func):
|
304 |
+
func = super(kwds, self).__call__(func)
|
305 |
+
func.argcmd_kwds = self.kwds
|
306 |
+
return func
|
307 |
+
|
308 |
+
|
309 |
+
__all__ = ['magic_arguments', 'argument', 'argument_group', 'kwds',
|
310 |
+
'parse_argstring']
|
openalex_env_map/lib/python3.10/site-packages/IPython/core/magics/__init__.py
ADDED
@@ -0,0 +1,42 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"""Implementation of all the magic functions built into IPython.
|
2 |
+
"""
|
3 |
+
#-----------------------------------------------------------------------------
|
4 |
+
# Copyright (c) 2012 The IPython Development Team.
|
5 |
+
#
|
6 |
+
# Distributed under the terms of the Modified BSD License.
|
7 |
+
#
|
8 |
+
# The full license is in the file COPYING.txt, distributed with this software.
|
9 |
+
#-----------------------------------------------------------------------------
|
10 |
+
|
11 |
+
#-----------------------------------------------------------------------------
|
12 |
+
# Imports
|
13 |
+
#-----------------------------------------------------------------------------
|
14 |
+
|
15 |
+
from ..magic import Magics, magics_class
|
16 |
+
from .auto import AutoMagics
|
17 |
+
from .basic import BasicMagics, AsyncMagics
|
18 |
+
from .code import CodeMagics, MacroToEdit
|
19 |
+
from .config import ConfigMagics
|
20 |
+
from .display import DisplayMagics
|
21 |
+
from .execution import ExecutionMagics
|
22 |
+
from .extension import ExtensionMagics
|
23 |
+
from .history import HistoryMagics
|
24 |
+
from .logging import LoggingMagics
|
25 |
+
from .namespace import NamespaceMagics
|
26 |
+
from .osm import OSMagics
|
27 |
+
from .packaging import PackagingMagics
|
28 |
+
from .pylab import PylabMagics
|
29 |
+
from .script import ScriptMagics
|
30 |
+
|
31 |
+
#-----------------------------------------------------------------------------
|
32 |
+
# Magic implementation classes
|
33 |
+
#-----------------------------------------------------------------------------
|
34 |
+
|
35 |
+
@magics_class
|
36 |
+
class UserMagics(Magics):
|
37 |
+
"""Placeholder for user-defined magics to be added at runtime.
|
38 |
+
|
39 |
+
All magics are eventually merged into a single namespace at runtime, but we
|
40 |
+
use this class to isolate the magics defined dynamically by the user into
|
41 |
+
their own class.
|
42 |
+
"""
|
openalex_env_map/lib/python3.10/site-packages/IPython/core/magics/ast_mod.py
ADDED
@@ -0,0 +1,330 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"""
|
2 |
+
This module contains utility function and classes to inject simple ast
|
3 |
+
transformations based on code strings into IPython. While it is already possible
|
4 |
+
with ast-transformers it is not easy to directly manipulate ast.
|
5 |
+
|
6 |
+
|
7 |
+
IPython has pre-code and post-code hooks, but are ran from within the IPython
|
8 |
+
machinery so may be inappropriate, for example for performance measurement.
|
9 |
+
|
10 |
+
This module give you tools to simplify this, and expose 2 classes:
|
11 |
+
|
12 |
+
- `ReplaceCodeTransformer` which is a simple ast transformer based on code
|
13 |
+
template,
|
14 |
+
|
15 |
+
and for advance case:
|
16 |
+
|
17 |
+
- `Mangler` which is a simple ast transformer that mangle names in the ast.
|
18 |
+
|
19 |
+
|
20 |
+
Example, let's try to make a simple version of the ``timeit`` magic, that run a
|
21 |
+
code snippet 10 times and print the average time taken.
|
22 |
+
|
23 |
+
Basically we want to run :
|
24 |
+
|
25 |
+
.. code-block:: python
|
26 |
+
|
27 |
+
from time import perf_counter
|
28 |
+
now = perf_counter()
|
29 |
+
for i in range(10):
|
30 |
+
__code__ # our code
|
31 |
+
print(f"Time taken: {(perf_counter() - now)/10}")
|
32 |
+
__ret__ # the result of the last statement
|
33 |
+
|
34 |
+
Where ``__code__`` is the code snippet we want to run, and ``__ret__`` is the
|
35 |
+
result, so that if we for example run `dataframe.head()` IPython still display
|
36 |
+
the head of dataframe instead of nothing.
|
37 |
+
|
38 |
+
Here is a complete example of a file `timit2.py` that define such a magic:
|
39 |
+
|
40 |
+
.. code-block:: python
|
41 |
+
|
42 |
+
from IPython.core.magic import (
|
43 |
+
Magics,
|
44 |
+
magics_class,
|
45 |
+
line_cell_magic,
|
46 |
+
)
|
47 |
+
from IPython.core.magics.ast_mod import ReplaceCodeTransformer
|
48 |
+
from textwrap import dedent
|
49 |
+
import ast
|
50 |
+
|
51 |
+
template = template = dedent('''
|
52 |
+
from time import perf_counter
|
53 |
+
now = perf_counter()
|
54 |
+
for i in range(10):
|
55 |
+
__code__
|
56 |
+
print(f"Time taken: {(perf_counter() - now)/10}")
|
57 |
+
__ret__
|
58 |
+
'''
|
59 |
+
)
|
60 |
+
|
61 |
+
|
62 |
+
@magics_class
|
63 |
+
class AstM(Magics):
|
64 |
+
@line_cell_magic
|
65 |
+
def t2(self, line, cell):
|
66 |
+
transformer = ReplaceCodeTransformer.from_string(template)
|
67 |
+
transformer.debug = True
|
68 |
+
transformer.mangler.debug = True
|
69 |
+
new_code = transformer.visit(ast.parse(cell))
|
70 |
+
return exec(compile(new_code, "<ast>", "exec"))
|
71 |
+
|
72 |
+
|
73 |
+
def load_ipython_extension(ip):
|
74 |
+
ip.register_magics(AstM)
|
75 |
+
|
76 |
+
|
77 |
+
|
78 |
+
.. code-block:: python
|
79 |
+
|
80 |
+
In [1]: %load_ext timit2
|
81 |
+
|
82 |
+
In [2]: %%t2
|
83 |
+
...: import time
|
84 |
+
...: time.sleep(0.05)
|
85 |
+
...:
|
86 |
+
...:
|
87 |
+
Time taken: 0.05435649999999441
|
88 |
+
|
89 |
+
|
90 |
+
If you wish to ran all the code enter in IPython in an ast transformer, you can
|
91 |
+
do so as well:
|
92 |
+
|
93 |
+
.. code-block:: python
|
94 |
+
|
95 |
+
In [1]: from IPython.core.magics.ast_mod import ReplaceCodeTransformer
|
96 |
+
...:
|
97 |
+
...: template = '''
|
98 |
+
...: from time import perf_counter
|
99 |
+
...: now = perf_counter()
|
100 |
+
...: __code__
|
101 |
+
...: print(f"Code ran in {perf_counter()-now}")
|
102 |
+
...: __ret__'''
|
103 |
+
...:
|
104 |
+
...: get_ipython().ast_transformers.append(ReplaceCodeTransformer.from_string(template))
|
105 |
+
|
106 |
+
In [2]: 1+1
|
107 |
+
Code ran in 3.40410006174352e-05
|
108 |
+
Out[2]: 2
|
109 |
+
|
110 |
+
|
111 |
+
|
112 |
+
Hygiene and Mangling
|
113 |
+
--------------------
|
114 |
+
|
115 |
+
The ast transformer above is not hygienic, it may not work if the user code use
|
116 |
+
the same variable names as the ones used in the template. For example.
|
117 |
+
|
118 |
+
To help with this by default the `ReplaceCodeTransformer` will mangle all names
|
119 |
+
staring with 3 underscores. This is a simple heuristic that should work in most
|
120 |
+
case, but can be cumbersome in some case. We provide a `Mangler` class that can
|
121 |
+
be overridden to change the mangling heuristic, or simply use the `mangle_all`
|
122 |
+
utility function. It will _try_ to mangle all names (except `__ret__` and
|
123 |
+
`__code__`), but this include builtins (``print``, ``range``, ``type``) and
|
124 |
+
replace those by invalid identifiers py prepending ``mangle-``:
|
125 |
+
``mangle-print``, ``mangle-range``, ``mangle-type`` etc. This is not a problem
|
126 |
+
as currently Python AST support invalid identifiers, but it may not be the case
|
127 |
+
in the future.
|
128 |
+
|
129 |
+
You can set `ReplaceCodeTransformer.debug=True` and
|
130 |
+
`ReplaceCodeTransformer.mangler.debug=True` to see the code after mangling and
|
131 |
+
transforming:
|
132 |
+
|
133 |
+
.. code-block:: python
|
134 |
+
|
135 |
+
|
136 |
+
In [1]: from IPython.core.magics.ast_mod import ReplaceCodeTransformer, mangle_all
|
137 |
+
...:
|
138 |
+
...: template = '''
|
139 |
+
...: from builtins import type, print
|
140 |
+
...: from time import perf_counter
|
141 |
+
...: now = perf_counter()
|
142 |
+
...: __code__
|
143 |
+
...: print(f"Code ran in {perf_counter()-now}")
|
144 |
+
...: __ret__'''
|
145 |
+
...:
|
146 |
+
...: transformer = ReplaceCodeTransformer.from_string(template, mangling_predicate=mangle_all)
|
147 |
+
|
148 |
+
|
149 |
+
In [2]: transformer.debug = True
|
150 |
+
...: transformer.mangler.debug = True
|
151 |
+
...: get_ipython().ast_transformers.append(transformer)
|
152 |
+
|
153 |
+
In [3]: 1+1
|
154 |
+
Mangling Alias mangle-type
|
155 |
+
Mangling Alias mangle-print
|
156 |
+
Mangling Alias mangle-perf_counter
|
157 |
+
Mangling now
|
158 |
+
Mangling perf_counter
|
159 |
+
Not mangling __code__
|
160 |
+
Mangling print
|
161 |
+
Mangling perf_counter
|
162 |
+
Mangling now
|
163 |
+
Not mangling __ret__
|
164 |
+
---- Transformed code ----
|
165 |
+
from builtins import type as mangle-type, print as mangle-print
|
166 |
+
from time import perf_counter as mangle-perf_counter
|
167 |
+
mangle-now = mangle-perf_counter()
|
168 |
+
ret-tmp = 1 + 1
|
169 |
+
mangle-print(f'Code ran in {mangle-perf_counter() - mangle-now}')
|
170 |
+
ret-tmp
|
171 |
+
---- ---------------- ----
|
172 |
+
Code ran in 0.00013654199938173406
|
173 |
+
Out[3]: 2
|
174 |
+
|
175 |
+
|
176 |
+
"""
|
177 |
+
|
178 |
+
__skip_doctest__ = True
|
179 |
+
|
180 |
+
|
181 |
+
from ast import (
|
182 |
+
NodeTransformer,
|
183 |
+
Store,
|
184 |
+
Load,
|
185 |
+
Name,
|
186 |
+
Expr,
|
187 |
+
Assign,
|
188 |
+
Module,
|
189 |
+
Import,
|
190 |
+
ImportFrom,
|
191 |
+
)
|
192 |
+
import ast
|
193 |
+
import copy
|
194 |
+
|
195 |
+
from typing import Dict, Optional, Union
|
196 |
+
|
197 |
+
|
198 |
+
mangle_all = lambda name: False if name in ("__ret__", "__code__") else True
|
199 |
+
|
200 |
+
|
201 |
+
class Mangler(NodeTransformer):
|
202 |
+
"""
|
203 |
+
Mangle given names in and ast tree to make sure they do not conflict with
|
204 |
+
user code.
|
205 |
+
"""
|
206 |
+
|
207 |
+
enabled: bool = True
|
208 |
+
debug: bool = False
|
209 |
+
|
210 |
+
def log(self, *args, **kwargs):
|
211 |
+
if self.debug:
|
212 |
+
print(*args, **kwargs)
|
213 |
+
|
214 |
+
def __init__(self, predicate=None):
|
215 |
+
if predicate is None:
|
216 |
+
predicate = lambda name: name.startswith("___")
|
217 |
+
self.predicate = predicate
|
218 |
+
|
219 |
+
def visit_Name(self, node):
|
220 |
+
if self.predicate(node.id):
|
221 |
+
self.log("Mangling", node.id)
|
222 |
+
# Once in the ast we do not need
|
223 |
+
# names to be valid identifiers.
|
224 |
+
node.id = "mangle-" + node.id
|
225 |
+
else:
|
226 |
+
self.log("Not mangling", node.id)
|
227 |
+
return node
|
228 |
+
|
229 |
+
def visit_FunctionDef(self, node):
|
230 |
+
if self.predicate(node.name):
|
231 |
+
self.log("Mangling", node.name)
|
232 |
+
node.name = "mangle-" + node.name
|
233 |
+
else:
|
234 |
+
self.log("Not mangling", node.name)
|
235 |
+
|
236 |
+
for arg in node.args.args:
|
237 |
+
if self.predicate(arg.arg):
|
238 |
+
self.log("Mangling function arg", arg.arg)
|
239 |
+
arg.arg = "mangle-" + arg.arg
|
240 |
+
else:
|
241 |
+
self.log("Not mangling function arg", arg.arg)
|
242 |
+
return self.generic_visit(node)
|
243 |
+
|
244 |
+
def visit_ImportFrom(self, node: ImportFrom):
|
245 |
+
return self._visit_Import_and_ImportFrom(node)
|
246 |
+
|
247 |
+
def visit_Import(self, node: Import):
|
248 |
+
return self._visit_Import_and_ImportFrom(node)
|
249 |
+
|
250 |
+
def _visit_Import_and_ImportFrom(self, node: Union[Import, ImportFrom]):
|
251 |
+
for alias in node.names:
|
252 |
+
asname = alias.name if alias.asname is None else alias.asname
|
253 |
+
if self.predicate(asname):
|
254 |
+
new_name: str = "mangle-" + asname
|
255 |
+
self.log("Mangling Alias", new_name)
|
256 |
+
alias.asname = new_name
|
257 |
+
else:
|
258 |
+
self.log("Not mangling Alias", alias.asname)
|
259 |
+
return node
|
260 |
+
|
261 |
+
|
262 |
+
class ReplaceCodeTransformer(NodeTransformer):
|
263 |
+
enabled: bool = True
|
264 |
+
debug: bool = False
|
265 |
+
mangler: Mangler
|
266 |
+
|
267 |
+
def __init__(
|
268 |
+
self, template: Module, mapping: Optional[Dict] = None, mangling_predicate=None
|
269 |
+
):
|
270 |
+
assert isinstance(mapping, (dict, type(None)))
|
271 |
+
assert isinstance(mangling_predicate, (type(None), type(lambda: None)))
|
272 |
+
assert isinstance(template, ast.Module)
|
273 |
+
self.template = template
|
274 |
+
self.mangler = Mangler(predicate=mangling_predicate)
|
275 |
+
if mapping is None:
|
276 |
+
mapping = {}
|
277 |
+
self.mapping = mapping
|
278 |
+
|
279 |
+
@classmethod
|
280 |
+
def from_string(
|
281 |
+
cls, template: str, mapping: Optional[Dict] = None, mangling_predicate=None
|
282 |
+
):
|
283 |
+
return cls(
|
284 |
+
ast.parse(template), mapping=mapping, mangling_predicate=mangling_predicate
|
285 |
+
)
|
286 |
+
|
287 |
+
def visit_Module(self, code):
|
288 |
+
if not self.enabled:
|
289 |
+
return code
|
290 |
+
# if not isinstance(code, ast.Module):
|
291 |
+
# recursively called...
|
292 |
+
# return generic_visit(self, code)
|
293 |
+
last = code.body[-1]
|
294 |
+
if isinstance(last, Expr):
|
295 |
+
code.body.pop()
|
296 |
+
code.body.append(Assign([Name("ret-tmp", ctx=Store())], value=last.value))
|
297 |
+
ast.fix_missing_locations(code)
|
298 |
+
ret = Expr(value=Name("ret-tmp", ctx=Load()))
|
299 |
+
ret = ast.fix_missing_locations(ret)
|
300 |
+
self.mapping["__ret__"] = ret
|
301 |
+
else:
|
302 |
+
self.mapping["__ret__"] = ast.parse("None").body[0]
|
303 |
+
self.mapping["__code__"] = code.body
|
304 |
+
tpl = ast.fix_missing_locations(self.template)
|
305 |
+
|
306 |
+
tx = copy.deepcopy(tpl)
|
307 |
+
tx = self.mangler.visit(tx)
|
308 |
+
node = self.generic_visit(tx)
|
309 |
+
node_2 = ast.fix_missing_locations(node)
|
310 |
+
if self.debug:
|
311 |
+
print("---- Transformed code ----")
|
312 |
+
print(ast.unparse(node_2))
|
313 |
+
print("---- ---------------- ----")
|
314 |
+
return node_2
|
315 |
+
|
316 |
+
# this does not work as the name might be in a list and one might want to extend the list.
|
317 |
+
# def visit_Name(self, name):
|
318 |
+
# if name.id in self.mapping and name.id == "__ret__":
|
319 |
+
# print(name, "in mapping")
|
320 |
+
# if isinstance(name.ctx, ast.Store):
|
321 |
+
# return Name("tmp", ctx=Store())
|
322 |
+
# else:
|
323 |
+
# return copy.deepcopy(self.mapping[name.id])
|
324 |
+
# return name
|
325 |
+
|
326 |
+
def visit_Expr(self, expr):
|
327 |
+
if isinstance(expr.value, Name) and expr.value.id in self.mapping:
|
328 |
+
if self.mapping[expr.value.id] is not None:
|
329 |
+
return copy.deepcopy(self.mapping[expr.value.id])
|
330 |
+
return self.generic_visit(expr)
|
openalex_env_map/lib/python3.10/site-packages/IPython/core/magics/auto.py
ADDED
@@ -0,0 +1,144 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"""Implementation of magic functions that control various automatic behaviors.
|
2 |
+
"""
|
3 |
+
#-----------------------------------------------------------------------------
|
4 |
+
# Copyright (c) 2012 The IPython Development Team.
|
5 |
+
#
|
6 |
+
# Distributed under the terms of the Modified BSD License.
|
7 |
+
#
|
8 |
+
# The full license is in the file COPYING.txt, distributed with this software.
|
9 |
+
#-----------------------------------------------------------------------------
|
10 |
+
|
11 |
+
#-----------------------------------------------------------------------------
|
12 |
+
# Imports
|
13 |
+
#-----------------------------------------------------------------------------
|
14 |
+
|
15 |
+
# Our own packages
|
16 |
+
from IPython.core.magic import Bunch, Magics, magics_class, line_magic
|
17 |
+
from IPython.testing.skipdoctest import skip_doctest
|
18 |
+
from logging import error
|
19 |
+
|
20 |
+
#-----------------------------------------------------------------------------
|
21 |
+
# Magic implementation classes
|
22 |
+
#-----------------------------------------------------------------------------
|
23 |
+
|
24 |
+
@magics_class
|
25 |
+
class AutoMagics(Magics):
|
26 |
+
"""Magics that control various autoX behaviors."""
|
27 |
+
|
28 |
+
def __init__(self, shell):
|
29 |
+
super(AutoMagics, self).__init__(shell)
|
30 |
+
# namespace for holding state we may need
|
31 |
+
self._magic_state = Bunch()
|
32 |
+
|
33 |
+
@line_magic
|
34 |
+
def automagic(self, parameter_s=''):
|
35 |
+
"""Make magic functions callable without having to type the initial %.
|
36 |
+
|
37 |
+
Without arguments toggles on/off (when off, you must call it as
|
38 |
+
%automagic, of course). With arguments it sets the value, and you can
|
39 |
+
use any of (case insensitive):
|
40 |
+
|
41 |
+
- on, 1, True: to activate
|
42 |
+
|
43 |
+
- off, 0, False: to deactivate.
|
44 |
+
|
45 |
+
Note that magic functions have lowest priority, so if there's a
|
46 |
+
variable whose name collides with that of a magic fn, automagic won't
|
47 |
+
work for that function (you get the variable instead). However, if you
|
48 |
+
delete the variable (del var), the previously shadowed magic function
|
49 |
+
becomes visible to automagic again."""
|
50 |
+
|
51 |
+
arg = parameter_s.lower()
|
52 |
+
mman = self.shell.magics_manager
|
53 |
+
if arg in ('on', '1', 'true'):
|
54 |
+
val = True
|
55 |
+
elif arg in ('off', '0', 'false'):
|
56 |
+
val = False
|
57 |
+
else:
|
58 |
+
val = not mman.auto_magic
|
59 |
+
mman.auto_magic = val
|
60 |
+
print('\n' + self.shell.magics_manager.auto_status())
|
61 |
+
|
62 |
+
@skip_doctest
|
63 |
+
@line_magic
|
64 |
+
def autocall(self, parameter_s=''):
|
65 |
+
"""Make functions callable without having to type parentheses.
|
66 |
+
|
67 |
+
Usage:
|
68 |
+
|
69 |
+
%autocall [mode]
|
70 |
+
|
71 |
+
The mode can be one of: 0->Off, 1->Smart, 2->Full. If not given, the
|
72 |
+
value is toggled on and off (remembering the previous state).
|
73 |
+
|
74 |
+
In more detail, these values mean:
|
75 |
+
|
76 |
+
0 -> fully disabled
|
77 |
+
|
78 |
+
1 -> active, but do not apply if there are no arguments on the line.
|
79 |
+
|
80 |
+
In this mode, you get::
|
81 |
+
|
82 |
+
In [1]: callable
|
83 |
+
Out[1]: <built-in function callable>
|
84 |
+
|
85 |
+
In [2]: callable 'hello'
|
86 |
+
------> callable('hello')
|
87 |
+
Out[2]: False
|
88 |
+
|
89 |
+
2 -> Active always. Even if no arguments are present, the callable
|
90 |
+
object is called::
|
91 |
+
|
92 |
+
In [2]: float
|
93 |
+
------> float()
|
94 |
+
Out[2]: 0.0
|
95 |
+
|
96 |
+
Note that even with autocall off, you can still use '/' at the start of
|
97 |
+
a line to treat the first argument on the command line as a function
|
98 |
+
and add parentheses to it::
|
99 |
+
|
100 |
+
In [8]: /str 43
|
101 |
+
------> str(43)
|
102 |
+
Out[8]: '43'
|
103 |
+
|
104 |
+
# all-random (note for auto-testing)
|
105 |
+
"""
|
106 |
+
|
107 |
+
valid_modes = {
|
108 |
+
0: "Off",
|
109 |
+
1: "Smart",
|
110 |
+
2: "Full",
|
111 |
+
}
|
112 |
+
|
113 |
+
def errorMessage() -> str:
|
114 |
+
error = "Valid modes: "
|
115 |
+
for k, v in valid_modes.items():
|
116 |
+
error += str(k) + "->" + v + ", "
|
117 |
+
error = error[:-2] # remove tailing `, ` after last element
|
118 |
+
return error
|
119 |
+
|
120 |
+
if parameter_s:
|
121 |
+
if not parameter_s in map(str, valid_modes.keys()):
|
122 |
+
error(errorMessage())
|
123 |
+
return
|
124 |
+
arg = int(parameter_s)
|
125 |
+
else:
|
126 |
+
arg = 'toggle'
|
127 |
+
|
128 |
+
if not arg in (*list(valid_modes.keys()), "toggle"):
|
129 |
+
error(errorMessage())
|
130 |
+
return
|
131 |
+
|
132 |
+
if arg in (valid_modes.keys()):
|
133 |
+
self.shell.autocall = arg
|
134 |
+
else: # toggle
|
135 |
+
if self.shell.autocall:
|
136 |
+
self._magic_state.autocall_save = self.shell.autocall
|
137 |
+
self.shell.autocall = 0
|
138 |
+
else:
|
139 |
+
try:
|
140 |
+
self.shell.autocall = self._magic_state.autocall_save
|
141 |
+
except AttributeError:
|
142 |
+
self.shell.autocall = self._magic_state.autocall_save = 1
|
143 |
+
|
144 |
+
print("Automatic calling is:", list(valid_modes.values())[self.shell.autocall])
|
openalex_env_map/lib/python3.10/site-packages/IPython/core/magics/basic.py
ADDED
@@ -0,0 +1,666 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"""Implementation of basic magic functions."""
|
2 |
+
|
3 |
+
|
4 |
+
from logging import error
|
5 |
+
import io
|
6 |
+
import os
|
7 |
+
from pprint import pformat
|
8 |
+
import sys
|
9 |
+
from warnings import warn
|
10 |
+
|
11 |
+
from traitlets.utils.importstring import import_item
|
12 |
+
from IPython.core import magic_arguments, page
|
13 |
+
from IPython.core.error import UsageError
|
14 |
+
from IPython.core.magic import Magics, magics_class, line_magic, magic_escapes
|
15 |
+
from IPython.utils.text import format_screen, dedent, indent
|
16 |
+
from IPython.testing.skipdoctest import skip_doctest
|
17 |
+
from IPython.utils.ipstruct import Struct
|
18 |
+
|
19 |
+
|
20 |
+
class MagicsDisplay(object):
|
21 |
+
def __init__(self, magics_manager, ignore=None):
|
22 |
+
self.ignore = ignore if ignore else []
|
23 |
+
self.magics_manager = magics_manager
|
24 |
+
|
25 |
+
def _lsmagic(self):
|
26 |
+
"""The main implementation of the %lsmagic"""
|
27 |
+
mesc = magic_escapes['line']
|
28 |
+
cesc = magic_escapes['cell']
|
29 |
+
mman = self.magics_manager
|
30 |
+
magics = mman.lsmagic()
|
31 |
+
out = ['Available line magics:',
|
32 |
+
mesc + (' '+mesc).join(sorted([m for m,v in magics['line'].items() if (v not in self.ignore)])),
|
33 |
+
'',
|
34 |
+
'Available cell magics:',
|
35 |
+
cesc + (' '+cesc).join(sorted([m for m,v in magics['cell'].items() if (v not in self.ignore)])),
|
36 |
+
'',
|
37 |
+
mman.auto_status()]
|
38 |
+
return '\n'.join(out)
|
39 |
+
|
40 |
+
def _repr_pretty_(self, p, cycle):
|
41 |
+
p.text(self._lsmagic())
|
42 |
+
|
43 |
+
def __repr__(self):
|
44 |
+
return self.__str__()
|
45 |
+
|
46 |
+
def __str__(self):
|
47 |
+
return self._lsmagic()
|
48 |
+
|
49 |
+
def _jsonable(self):
|
50 |
+
"""turn magics dict into jsonable dict of the same structure
|
51 |
+
|
52 |
+
replaces object instances with their class names as strings
|
53 |
+
"""
|
54 |
+
magic_dict = {}
|
55 |
+
mman = self.magics_manager
|
56 |
+
magics = mman.lsmagic()
|
57 |
+
for key, subdict in magics.items():
|
58 |
+
d = {}
|
59 |
+
magic_dict[key] = d
|
60 |
+
for name, obj in subdict.items():
|
61 |
+
try:
|
62 |
+
classname = obj.__self__.__class__.__name__
|
63 |
+
except AttributeError:
|
64 |
+
classname = 'Other'
|
65 |
+
|
66 |
+
d[name] = classname
|
67 |
+
return magic_dict
|
68 |
+
|
69 |
+
def _repr_json_(self):
|
70 |
+
return self._jsonable()
|
71 |
+
|
72 |
+
|
73 |
+
@magics_class
|
74 |
+
class BasicMagics(Magics):
|
75 |
+
"""Magics that provide central IPython functionality.
|
76 |
+
|
77 |
+
These are various magics that don't fit into specific categories but that
|
78 |
+
are all part of the base 'IPython experience'."""
|
79 |
+
|
80 |
+
@skip_doctest
|
81 |
+
@magic_arguments.magic_arguments()
|
82 |
+
@magic_arguments.argument(
|
83 |
+
'-l', '--line', action='store_true',
|
84 |
+
help="""Create a line magic alias."""
|
85 |
+
)
|
86 |
+
@magic_arguments.argument(
|
87 |
+
'-c', '--cell', action='store_true',
|
88 |
+
help="""Create a cell magic alias."""
|
89 |
+
)
|
90 |
+
@magic_arguments.argument(
|
91 |
+
'name',
|
92 |
+
help="""Name of the magic to be created."""
|
93 |
+
)
|
94 |
+
@magic_arguments.argument(
|
95 |
+
'target',
|
96 |
+
help="""Name of the existing line or cell magic."""
|
97 |
+
)
|
98 |
+
@magic_arguments.argument(
|
99 |
+
'-p', '--params', default=None,
|
100 |
+
help="""Parameters passed to the magic function."""
|
101 |
+
)
|
102 |
+
@line_magic
|
103 |
+
def alias_magic(self, line=''):
|
104 |
+
"""Create an alias for an existing line or cell magic.
|
105 |
+
|
106 |
+
Examples
|
107 |
+
--------
|
108 |
+
::
|
109 |
+
|
110 |
+
In [1]: %alias_magic t timeit
|
111 |
+
Created `%t` as an alias for `%timeit`.
|
112 |
+
Created `%%t` as an alias for `%%timeit`.
|
113 |
+
|
114 |
+
In [2]: %t -n1 pass
|
115 |
+
107 ns ± 43.6 ns per loop (mean ± std. dev. of 7 runs, 1 loop each)
|
116 |
+
|
117 |
+
In [3]: %%t -n1
|
118 |
+
...: pass
|
119 |
+
...:
|
120 |
+
107 ns ± 58.3 ns per loop (mean ± std. dev. of 7 runs, 1 loop each)
|
121 |
+
|
122 |
+
In [4]: %alias_magic --cell whereami pwd
|
123 |
+
UsageError: Cell magic function `%%pwd` not found.
|
124 |
+
In [5]: %alias_magic --line whereami pwd
|
125 |
+
Created `%whereami` as an alias for `%pwd`.
|
126 |
+
|
127 |
+
In [6]: %whereami
|
128 |
+
Out[6]: '/home/testuser'
|
129 |
+
|
130 |
+
In [7]: %alias_magic h history -p "-l 30" --line
|
131 |
+
Created `%h` as an alias for `%history -l 30`.
|
132 |
+
"""
|
133 |
+
|
134 |
+
args = magic_arguments.parse_argstring(self.alias_magic, line)
|
135 |
+
shell = self.shell
|
136 |
+
mman = self.shell.magics_manager
|
137 |
+
escs = ''.join(magic_escapes.values())
|
138 |
+
|
139 |
+
target = args.target.lstrip(escs)
|
140 |
+
name = args.name.lstrip(escs)
|
141 |
+
|
142 |
+
params = args.params
|
143 |
+
if (params and
|
144 |
+
((params.startswith('"') and params.endswith('"'))
|
145 |
+
or (params.startswith("'") and params.endswith("'")))):
|
146 |
+
params = params[1:-1]
|
147 |
+
|
148 |
+
# Find the requested magics.
|
149 |
+
m_line = shell.find_magic(target, 'line')
|
150 |
+
m_cell = shell.find_magic(target, 'cell')
|
151 |
+
if args.line and m_line is None:
|
152 |
+
raise UsageError('Line magic function `%s%s` not found.' %
|
153 |
+
(magic_escapes['line'], target))
|
154 |
+
if args.cell and m_cell is None:
|
155 |
+
raise UsageError('Cell magic function `%s%s` not found.' %
|
156 |
+
(magic_escapes['cell'], target))
|
157 |
+
|
158 |
+
# If --line and --cell are not specified, default to the ones
|
159 |
+
# that are available.
|
160 |
+
if not args.line and not args.cell:
|
161 |
+
if not m_line and not m_cell:
|
162 |
+
raise UsageError(
|
163 |
+
'No line or cell magic with name `%s` found.' % target
|
164 |
+
)
|
165 |
+
args.line = bool(m_line)
|
166 |
+
args.cell = bool(m_cell)
|
167 |
+
|
168 |
+
params_str = "" if params is None else " " + params
|
169 |
+
|
170 |
+
if args.line:
|
171 |
+
mman.register_alias(name, target, 'line', params)
|
172 |
+
print('Created `%s%s` as an alias for `%s%s%s`.' % (
|
173 |
+
magic_escapes['line'], name,
|
174 |
+
magic_escapes['line'], target, params_str))
|
175 |
+
|
176 |
+
if args.cell:
|
177 |
+
mman.register_alias(name, target, 'cell', params)
|
178 |
+
print('Created `%s%s` as an alias for `%s%s%s`.' % (
|
179 |
+
magic_escapes['cell'], name,
|
180 |
+
magic_escapes['cell'], target, params_str))
|
181 |
+
|
182 |
+
@line_magic
|
183 |
+
def lsmagic(self, parameter_s=''):
|
184 |
+
"""List currently available magic functions."""
|
185 |
+
return MagicsDisplay(self.shell.magics_manager, ignore=[])
|
186 |
+
|
187 |
+
def _magic_docs(self, brief=False, rest=False):
|
188 |
+
"""Return docstrings from magic functions."""
|
189 |
+
mman = self.shell.magics_manager
|
190 |
+
docs = mman.lsmagic_docs(brief, missing='No documentation')
|
191 |
+
|
192 |
+
if rest:
|
193 |
+
format_string = '**%s%s**::\n\n%s\n\n'
|
194 |
+
else:
|
195 |
+
format_string = '%s%s:\n%s\n'
|
196 |
+
|
197 |
+
return ''.join(
|
198 |
+
[format_string % (magic_escapes['line'], fname,
|
199 |
+
indent(dedent(fndoc)))
|
200 |
+
for fname, fndoc in sorted(docs['line'].items())]
|
201 |
+
+
|
202 |
+
[format_string % (magic_escapes['cell'], fname,
|
203 |
+
indent(dedent(fndoc)))
|
204 |
+
for fname, fndoc in sorted(docs['cell'].items())]
|
205 |
+
)
|
206 |
+
|
207 |
+
@line_magic
|
208 |
+
def magic(self, parameter_s=''):
|
209 |
+
"""Print information about the magic function system.
|
210 |
+
|
211 |
+
Supported formats: -latex, -brief, -rest
|
212 |
+
"""
|
213 |
+
|
214 |
+
mode = ''
|
215 |
+
try:
|
216 |
+
mode = parameter_s.split()[0][1:]
|
217 |
+
except IndexError:
|
218 |
+
pass
|
219 |
+
|
220 |
+
brief = (mode == 'brief')
|
221 |
+
rest = (mode == 'rest')
|
222 |
+
magic_docs = self._magic_docs(brief, rest)
|
223 |
+
|
224 |
+
if mode == 'latex':
|
225 |
+
print(self.format_latex(magic_docs))
|
226 |
+
return
|
227 |
+
else:
|
228 |
+
magic_docs = format_screen(magic_docs)
|
229 |
+
|
230 |
+
out = ["""
|
231 |
+
IPython's 'magic' functions
|
232 |
+
===========================
|
233 |
+
|
234 |
+
The magic function system provides a series of functions which allow you to
|
235 |
+
control the behavior of IPython itself, plus a lot of system-type
|
236 |
+
features. There are two kinds of magics, line-oriented and cell-oriented.
|
237 |
+
|
238 |
+
Line magics are prefixed with the % character and work much like OS
|
239 |
+
command-line calls: they get as an argument the rest of the line, where
|
240 |
+
arguments are passed without parentheses or quotes. For example, this will
|
241 |
+
time the given statement::
|
242 |
+
|
243 |
+
%timeit range(1000)
|
244 |
+
|
245 |
+
Cell magics are prefixed with a double %%, and they are functions that get as
|
246 |
+
an argument not only the rest of the line, but also the lines below it in a
|
247 |
+
separate argument. These magics are called with two arguments: the rest of the
|
248 |
+
call line and the body of the cell, consisting of the lines below the first.
|
249 |
+
For example::
|
250 |
+
|
251 |
+
%%timeit x = numpy.random.randn((100, 100))
|
252 |
+
numpy.linalg.svd(x)
|
253 |
+
|
254 |
+
will time the execution of the numpy svd routine, running the assignment of x
|
255 |
+
as part of the setup phase, which is not timed.
|
256 |
+
|
257 |
+
In a line-oriented client (the terminal or Qt console IPython), starting a new
|
258 |
+
input with %% will automatically enter cell mode, and IPython will continue
|
259 |
+
reading input until a blank line is given. In the notebook, simply type the
|
260 |
+
whole cell as one entity, but keep in mind that the %% escape can only be at
|
261 |
+
the very start of the cell.
|
262 |
+
|
263 |
+
NOTE: If you have 'automagic' enabled (via the command line option or with the
|
264 |
+
%automagic function), you don't need to type in the % explicitly for line
|
265 |
+
magics; cell magics always require an explicit '%%' escape. By default,
|
266 |
+
IPython ships with automagic on, so you should only rarely need the % escape.
|
267 |
+
|
268 |
+
Example: typing '%cd mydir' (without the quotes) changes your working directory
|
269 |
+
to 'mydir', if it exists.
|
270 |
+
|
271 |
+
For a list of the available magic functions, use %lsmagic. For a description
|
272 |
+
of any of them, type %magic_name?, e.g. '%cd?'.
|
273 |
+
|
274 |
+
Currently the magic system has the following functions:""",
|
275 |
+
magic_docs,
|
276 |
+
"Summary of magic functions (from %slsmagic):" % magic_escapes['line'],
|
277 |
+
str(self.lsmagic()),
|
278 |
+
]
|
279 |
+
page.page('\n'.join(out))
|
280 |
+
|
281 |
+
|
282 |
+
@line_magic
|
283 |
+
def page(self, parameter_s=''):
|
284 |
+
"""Pretty print the object and display it through a pager.
|
285 |
+
|
286 |
+
%page [options] OBJECT
|
287 |
+
|
288 |
+
If no object is given, use _ (last output).
|
289 |
+
|
290 |
+
Options:
|
291 |
+
|
292 |
+
-r: page str(object), don't pretty-print it."""
|
293 |
+
|
294 |
+
# After a function contributed by Olivier Aubert, slightly modified.
|
295 |
+
|
296 |
+
# Process options/args
|
297 |
+
opts, args = self.parse_options(parameter_s, 'r')
|
298 |
+
raw = 'r' in opts
|
299 |
+
|
300 |
+
oname = args and args or '_'
|
301 |
+
info = self.shell._ofind(oname)
|
302 |
+
if info.found:
|
303 |
+
if raw:
|
304 |
+
txt = str(info.obj)
|
305 |
+
else:
|
306 |
+
txt = pformat(info.obj)
|
307 |
+
page.page(txt)
|
308 |
+
else:
|
309 |
+
print('Object `%s` not found' % oname)
|
310 |
+
|
311 |
+
@line_magic
|
312 |
+
def pprint(self, parameter_s=''):
|
313 |
+
"""Toggle pretty printing on/off."""
|
314 |
+
ptformatter = self.shell.display_formatter.formatters['text/plain']
|
315 |
+
ptformatter.pprint = bool(1 - ptformatter.pprint)
|
316 |
+
print('Pretty printing has been turned',
|
317 |
+
['OFF','ON'][ptformatter.pprint])
|
318 |
+
|
319 |
+
@line_magic
|
320 |
+
def colors(self, parameter_s=''):
|
321 |
+
"""Switch color scheme for prompts, info system and exception handlers.
|
322 |
+
|
323 |
+
Currently implemented schemes: NoColor, Linux, LightBG.
|
324 |
+
|
325 |
+
Color scheme names are not case-sensitive.
|
326 |
+
|
327 |
+
Examples
|
328 |
+
--------
|
329 |
+
To get a plain black and white terminal::
|
330 |
+
|
331 |
+
%colors nocolor
|
332 |
+
"""
|
333 |
+
def color_switch_err(name):
|
334 |
+
warn('Error changing %s color schemes.\n%s' %
|
335 |
+
(name, sys.exc_info()[1]), stacklevel=2)
|
336 |
+
|
337 |
+
|
338 |
+
new_scheme = parameter_s.strip()
|
339 |
+
if not new_scheme:
|
340 |
+
raise UsageError(
|
341 |
+
"%colors: you must specify a color scheme. See '%colors?'")
|
342 |
+
# local shortcut
|
343 |
+
shell = self.shell
|
344 |
+
|
345 |
+
# Set shell colour scheme
|
346 |
+
try:
|
347 |
+
shell.colors = new_scheme
|
348 |
+
shell.refresh_style()
|
349 |
+
except:
|
350 |
+
color_switch_err('shell')
|
351 |
+
|
352 |
+
# Set exception colors
|
353 |
+
try:
|
354 |
+
shell.InteractiveTB.set_colors(scheme = new_scheme)
|
355 |
+
shell.SyntaxTB.set_colors(scheme = new_scheme)
|
356 |
+
except:
|
357 |
+
color_switch_err('exception')
|
358 |
+
|
359 |
+
# Set info (for 'object?') colors
|
360 |
+
if shell.color_info:
|
361 |
+
try:
|
362 |
+
shell.inspector.set_active_scheme(new_scheme)
|
363 |
+
except:
|
364 |
+
color_switch_err('object inspector')
|
365 |
+
else:
|
366 |
+
shell.inspector.set_active_scheme('NoColor')
|
367 |
+
|
368 |
+
@line_magic
|
369 |
+
def xmode(self, parameter_s=''):
|
370 |
+
"""Switch modes for the exception handlers.
|
371 |
+
|
372 |
+
Valid modes: Plain, Context, Verbose, and Minimal.
|
373 |
+
|
374 |
+
If called without arguments, acts as a toggle.
|
375 |
+
|
376 |
+
When in verbose mode the value `--show` (and `--hide`)
|
377 |
+
will respectively show (or hide) frames with ``__tracebackhide__ =
|
378 |
+
True`` value set.
|
379 |
+
"""
|
380 |
+
|
381 |
+
def xmode_switch_err(name):
|
382 |
+
warn('Error changing %s exception modes.\n%s' %
|
383 |
+
(name,sys.exc_info()[1]))
|
384 |
+
|
385 |
+
shell = self.shell
|
386 |
+
if parameter_s.strip() == "--show":
|
387 |
+
shell.InteractiveTB.skip_hidden = False
|
388 |
+
return
|
389 |
+
if parameter_s.strip() == "--hide":
|
390 |
+
shell.InteractiveTB.skip_hidden = True
|
391 |
+
return
|
392 |
+
|
393 |
+
new_mode = parameter_s.strip().capitalize()
|
394 |
+
try:
|
395 |
+
shell.InteractiveTB.set_mode(mode=new_mode)
|
396 |
+
print('Exception reporting mode:',shell.InteractiveTB.mode)
|
397 |
+
except:
|
398 |
+
xmode_switch_err('user')
|
399 |
+
|
400 |
+
@line_magic
|
401 |
+
def quickref(self, arg):
|
402 |
+
""" Show a quick reference sheet """
|
403 |
+
from IPython.core.usage import quick_reference
|
404 |
+
qr = quick_reference + self._magic_docs(brief=True)
|
405 |
+
page.page(qr)
|
406 |
+
|
407 |
+
@line_magic
|
408 |
+
def doctest_mode(self, parameter_s=''):
|
409 |
+
"""Toggle doctest mode on and off.
|
410 |
+
|
411 |
+
This mode is intended to make IPython behave as much as possible like a
|
412 |
+
plain Python shell, from the perspective of how its prompts, exceptions
|
413 |
+
and output look. This makes it easy to copy and paste parts of a
|
414 |
+
session into doctests. It does so by:
|
415 |
+
|
416 |
+
- Changing the prompts to the classic ``>>>`` ones.
|
417 |
+
- Changing the exception reporting mode to 'Plain'.
|
418 |
+
- Disabling pretty-printing of output.
|
419 |
+
|
420 |
+
Note that IPython also supports the pasting of code snippets that have
|
421 |
+
leading '>>>' and '...' prompts in them. This means that you can paste
|
422 |
+
doctests from files or docstrings (even if they have leading
|
423 |
+
whitespace), and the code will execute correctly. You can then use
|
424 |
+
'%history -t' to see the translated history; this will give you the
|
425 |
+
input after removal of all the leading prompts and whitespace, which
|
426 |
+
can be pasted back into an editor.
|
427 |
+
|
428 |
+
With these features, you can switch into this mode easily whenever you
|
429 |
+
need to do testing and changes to doctests, without having to leave
|
430 |
+
your existing IPython session.
|
431 |
+
"""
|
432 |
+
|
433 |
+
# Shorthands
|
434 |
+
shell = self.shell
|
435 |
+
meta = shell.meta
|
436 |
+
disp_formatter = self.shell.display_formatter
|
437 |
+
ptformatter = disp_formatter.formatters['text/plain']
|
438 |
+
# dstore is a data store kept in the instance metadata bag to track any
|
439 |
+
# changes we make, so we can undo them later.
|
440 |
+
dstore = meta.setdefault('doctest_mode',Struct())
|
441 |
+
save_dstore = dstore.setdefault
|
442 |
+
|
443 |
+
# save a few values we'll need to recover later
|
444 |
+
mode = save_dstore('mode',False)
|
445 |
+
save_dstore('rc_pprint',ptformatter.pprint)
|
446 |
+
save_dstore('xmode',shell.InteractiveTB.mode)
|
447 |
+
save_dstore('rc_separate_out',shell.separate_out)
|
448 |
+
save_dstore('rc_separate_out2',shell.separate_out2)
|
449 |
+
save_dstore('rc_separate_in',shell.separate_in)
|
450 |
+
save_dstore('rc_active_types',disp_formatter.active_types)
|
451 |
+
|
452 |
+
if not mode:
|
453 |
+
# turn on
|
454 |
+
|
455 |
+
# Prompt separators like plain python
|
456 |
+
shell.separate_in = ''
|
457 |
+
shell.separate_out = ''
|
458 |
+
shell.separate_out2 = ''
|
459 |
+
|
460 |
+
|
461 |
+
ptformatter.pprint = False
|
462 |
+
disp_formatter.active_types = ['text/plain']
|
463 |
+
|
464 |
+
shell.run_line_magic("xmode", "Plain")
|
465 |
+
else:
|
466 |
+
# turn off
|
467 |
+
shell.separate_in = dstore.rc_separate_in
|
468 |
+
|
469 |
+
shell.separate_out = dstore.rc_separate_out
|
470 |
+
shell.separate_out2 = dstore.rc_separate_out2
|
471 |
+
|
472 |
+
ptformatter.pprint = dstore.rc_pprint
|
473 |
+
disp_formatter.active_types = dstore.rc_active_types
|
474 |
+
|
475 |
+
shell.run_line_magic("xmode", dstore.xmode)
|
476 |
+
|
477 |
+
# mode here is the state before we switch; switch_doctest_mode takes
|
478 |
+
# the mode we're switching to.
|
479 |
+
shell.switch_doctest_mode(not mode)
|
480 |
+
|
481 |
+
# Store new mode and inform
|
482 |
+
dstore.mode = bool(not mode)
|
483 |
+
mode_label = ['OFF','ON'][dstore.mode]
|
484 |
+
print('Doctest mode is:', mode_label)
|
485 |
+
|
486 |
+
@line_magic
|
487 |
+
def gui(self, parameter_s=''):
|
488 |
+
"""Enable or disable IPython GUI event loop integration.
|
489 |
+
|
490 |
+
%gui [GUINAME]
|
491 |
+
|
492 |
+
This magic replaces IPython's threaded shells that were activated
|
493 |
+
using the (pylab/wthread/etc.) command line flags. GUI toolkits
|
494 |
+
can now be enabled at runtime and keyboard
|
495 |
+
interrupts should work without any problems. The following toolkits
|
496 |
+
are supported: wxPython, PyQt4, PyGTK, Tk and Cocoa (OSX)::
|
497 |
+
|
498 |
+
%gui wx # enable wxPython event loop integration
|
499 |
+
%gui qt # enable PyQt/PySide event loop integration
|
500 |
+
# with the latest version available.
|
501 |
+
%gui qt6 # enable PyQt6/PySide6 event loop integration
|
502 |
+
%gui qt5 # enable PyQt5/PySide2 event loop integration
|
503 |
+
%gui gtk # enable PyGTK event loop integration
|
504 |
+
%gui gtk3 # enable Gtk3 event loop integration
|
505 |
+
%gui gtk4 # enable Gtk4 event loop integration
|
506 |
+
%gui tk # enable Tk event loop integration
|
507 |
+
%gui osx # enable Cocoa event loop integration
|
508 |
+
# (requires %matplotlib 1.1)
|
509 |
+
%gui # disable all event loop integration
|
510 |
+
|
511 |
+
WARNING: after any of these has been called you can simply create
|
512 |
+
an application object, but DO NOT start the event loop yourself, as
|
513 |
+
we have already handled that.
|
514 |
+
"""
|
515 |
+
opts, arg = self.parse_options(parameter_s, '')
|
516 |
+
if arg=='': arg = None
|
517 |
+
try:
|
518 |
+
return self.shell.enable_gui(arg)
|
519 |
+
except Exception as e:
|
520 |
+
# print simple error message, rather than traceback if we can't
|
521 |
+
# hook up the GUI
|
522 |
+
error(str(e))
|
523 |
+
|
524 |
+
@skip_doctest
|
525 |
+
@line_magic
|
526 |
+
def precision(self, s=''):
|
527 |
+
"""Set floating point precision for pretty printing.
|
528 |
+
|
529 |
+
Can set either integer precision or a format string.
|
530 |
+
|
531 |
+
If numpy has been imported and precision is an int,
|
532 |
+
numpy display precision will also be set, via ``numpy.set_printoptions``.
|
533 |
+
|
534 |
+
If no argument is given, defaults will be restored.
|
535 |
+
|
536 |
+
Examples
|
537 |
+
--------
|
538 |
+
::
|
539 |
+
|
540 |
+
In [1]: from math import pi
|
541 |
+
|
542 |
+
In [2]: %precision 3
|
543 |
+
Out[2]: '%.3f'
|
544 |
+
|
545 |
+
In [3]: pi
|
546 |
+
Out[3]: 3.142
|
547 |
+
|
548 |
+
In [4]: %precision %i
|
549 |
+
Out[4]: '%i'
|
550 |
+
|
551 |
+
In [5]: pi
|
552 |
+
Out[5]: 3
|
553 |
+
|
554 |
+
In [6]: %precision %e
|
555 |
+
Out[6]: '%e'
|
556 |
+
|
557 |
+
In [7]: pi**10
|
558 |
+
Out[7]: 9.364805e+04
|
559 |
+
|
560 |
+
In [8]: %precision
|
561 |
+
Out[8]: '%r'
|
562 |
+
|
563 |
+
In [9]: pi**10
|
564 |
+
Out[9]: 93648.047476082982
|
565 |
+
"""
|
566 |
+
ptformatter = self.shell.display_formatter.formatters['text/plain']
|
567 |
+
ptformatter.float_precision = s
|
568 |
+
return ptformatter.float_format
|
569 |
+
|
570 |
+
@magic_arguments.magic_arguments()
|
571 |
+
@magic_arguments.argument(
|
572 |
+
'filename', type=str,
|
573 |
+
help='Notebook name or filename'
|
574 |
+
)
|
575 |
+
@line_magic
|
576 |
+
def notebook(self, s):
|
577 |
+
"""Export and convert IPython notebooks.
|
578 |
+
|
579 |
+
This function can export the current IPython history to a notebook file.
|
580 |
+
For example, to export the history to "foo.ipynb" do "%notebook foo.ipynb".
|
581 |
+
"""
|
582 |
+
args = magic_arguments.parse_argstring(self.notebook, s)
|
583 |
+
outfname = os.path.expanduser(args.filename)
|
584 |
+
|
585 |
+
from nbformat import write, v4
|
586 |
+
|
587 |
+
cells = []
|
588 |
+
hist = list(self.shell.history_manager.get_range())
|
589 |
+
if(len(hist)<=1):
|
590 |
+
raise ValueError('History is empty, cannot export')
|
591 |
+
for session, execution_count, source in hist[:-1]:
|
592 |
+
cells.append(v4.new_code_cell(
|
593 |
+
execution_count=execution_count,
|
594 |
+
source=source
|
595 |
+
))
|
596 |
+
nb = v4.new_notebook(cells=cells)
|
597 |
+
with io.open(outfname, "w", encoding="utf-8") as f:
|
598 |
+
write(nb, f, version=4)
|
599 |
+
|
600 |
+
@magics_class
|
601 |
+
class AsyncMagics(BasicMagics):
|
602 |
+
|
603 |
+
@line_magic
|
604 |
+
def autoawait(self, parameter_s):
|
605 |
+
"""
|
606 |
+
Allow to change the status of the autoawait option.
|
607 |
+
|
608 |
+
This allow you to set a specific asynchronous code runner.
|
609 |
+
|
610 |
+
If no value is passed, print the currently used asynchronous integration
|
611 |
+
and whether it is activated.
|
612 |
+
|
613 |
+
It can take a number of value evaluated in the following order:
|
614 |
+
|
615 |
+
- False/false/off deactivate autoawait integration
|
616 |
+
- True/true/on activate autoawait integration using configured default
|
617 |
+
loop
|
618 |
+
- asyncio/curio/trio activate autoawait integration and use integration
|
619 |
+
with said library.
|
620 |
+
|
621 |
+
- `sync` turn on the pseudo-sync integration (mostly used for
|
622 |
+
`IPython.embed()` which does not run IPython with a real eventloop and
|
623 |
+
deactivate running asynchronous code. Turning on Asynchronous code with
|
624 |
+
the pseudo sync loop is undefined behavior and may lead IPython to crash.
|
625 |
+
|
626 |
+
If the passed parameter does not match any of the above and is a python
|
627 |
+
identifier, get said object from user namespace and set it as the
|
628 |
+
runner, and activate autoawait.
|
629 |
+
|
630 |
+
If the object is a fully qualified object name, attempt to import it and
|
631 |
+
set it as the runner, and activate autoawait.
|
632 |
+
|
633 |
+
The exact behavior of autoawait is experimental and subject to change
|
634 |
+
across version of IPython and Python.
|
635 |
+
"""
|
636 |
+
|
637 |
+
param = parameter_s.strip()
|
638 |
+
d = {True: "on", False: "off"}
|
639 |
+
|
640 |
+
if not param:
|
641 |
+
print("IPython autoawait is `{}`, and set to use `{}`".format(
|
642 |
+
d[self.shell.autoawait],
|
643 |
+
self.shell.loop_runner
|
644 |
+
))
|
645 |
+
return None
|
646 |
+
|
647 |
+
if param.lower() in ('false', 'off'):
|
648 |
+
self.shell.autoawait = False
|
649 |
+
return None
|
650 |
+
if param.lower() in ('true', 'on'):
|
651 |
+
self.shell.autoawait = True
|
652 |
+
return None
|
653 |
+
|
654 |
+
if param in self.shell.loop_runner_map:
|
655 |
+
self.shell.loop_runner, self.shell.autoawait = self.shell.loop_runner_map[param]
|
656 |
+
return None
|
657 |
+
|
658 |
+
if param in self.shell.user_ns :
|
659 |
+
self.shell.loop_runner = self.shell.user_ns[param]
|
660 |
+
self.shell.autoawait = True
|
661 |
+
return None
|
662 |
+
|
663 |
+
runner = import_item(param)
|
664 |
+
|
665 |
+
self.shell.loop_runner = runner
|
666 |
+
self.shell.autoawait = True
|
openalex_env_map/lib/python3.10/site-packages/IPython/core/magics/code.py
ADDED
@@ -0,0 +1,757 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"""Implementation of code management magic functions.
|
2 |
+
"""
|
3 |
+
#-----------------------------------------------------------------------------
|
4 |
+
# Copyright (c) 2012 The IPython Development Team.
|
5 |
+
#
|
6 |
+
# Distributed under the terms of the Modified BSD License.
|
7 |
+
#
|
8 |
+
# The full license is in the file COPYING.txt, distributed with this software.
|
9 |
+
#-----------------------------------------------------------------------------
|
10 |
+
|
11 |
+
#-----------------------------------------------------------------------------
|
12 |
+
# Imports
|
13 |
+
#-----------------------------------------------------------------------------
|
14 |
+
|
15 |
+
# Stdlib
|
16 |
+
import inspect
|
17 |
+
import io
|
18 |
+
import os
|
19 |
+
import re
|
20 |
+
import sys
|
21 |
+
import ast
|
22 |
+
from itertools import chain
|
23 |
+
from urllib.request import Request, urlopen
|
24 |
+
from urllib.parse import urlencode
|
25 |
+
from pathlib import Path
|
26 |
+
|
27 |
+
# Our own packages
|
28 |
+
from IPython.core.error import TryNext, StdinNotImplementedError, UsageError
|
29 |
+
from IPython.core.macro import Macro
|
30 |
+
from IPython.core.magic import Magics, magics_class, line_magic
|
31 |
+
from IPython.core.oinspect import find_file, find_source_lines
|
32 |
+
from IPython.core.release import version
|
33 |
+
from IPython.testing.skipdoctest import skip_doctest
|
34 |
+
from IPython.utils.contexts import preserve_keys
|
35 |
+
from IPython.utils.path import get_py_filename
|
36 |
+
from warnings import warn
|
37 |
+
from logging import error
|
38 |
+
from IPython.utils.text import get_text_list
|
39 |
+
|
40 |
+
#-----------------------------------------------------------------------------
|
41 |
+
# Magic implementation classes
|
42 |
+
#-----------------------------------------------------------------------------
|
43 |
+
|
44 |
+
# Used for exception handling in magic_edit
|
45 |
+
class MacroToEdit(ValueError): pass
|
46 |
+
|
47 |
+
ipython_input_pat = re.compile(r"<ipython\-input\-(\d+)-[a-z\d]+>$")
|
48 |
+
|
49 |
+
# To match, e.g. 8-10 1:5 :10 3-
|
50 |
+
range_re = re.compile(r"""
|
51 |
+
(?P<start>\d+)?
|
52 |
+
((?P<sep>[\-:])
|
53 |
+
(?P<end>\d+)?)?
|
54 |
+
$""", re.VERBOSE)
|
55 |
+
|
56 |
+
|
57 |
+
def extract_code_ranges(ranges_str):
|
58 |
+
"""Turn a string of range for %%load into 2-tuples of (start, stop)
|
59 |
+
ready to use as a slice of the content split by lines.
|
60 |
+
|
61 |
+
Examples
|
62 |
+
--------
|
63 |
+
list(extract_input_ranges("5-10 2"))
|
64 |
+
[(4, 10), (1, 2)]
|
65 |
+
"""
|
66 |
+
for range_str in ranges_str.split():
|
67 |
+
rmatch = range_re.match(range_str)
|
68 |
+
if not rmatch:
|
69 |
+
continue
|
70 |
+
sep = rmatch.group("sep")
|
71 |
+
start = rmatch.group("start")
|
72 |
+
end = rmatch.group("end")
|
73 |
+
|
74 |
+
if sep == '-':
|
75 |
+
start = int(start) - 1 if start else None
|
76 |
+
end = int(end) if end else None
|
77 |
+
elif sep == ':':
|
78 |
+
start = int(start) - 1 if start else None
|
79 |
+
end = int(end) - 1 if end else None
|
80 |
+
else:
|
81 |
+
end = int(start)
|
82 |
+
start = int(start) - 1
|
83 |
+
yield (start, end)
|
84 |
+
|
85 |
+
|
86 |
+
def extract_symbols(code, symbols):
|
87 |
+
"""
|
88 |
+
Return a tuple (blocks, not_found)
|
89 |
+
where ``blocks`` is a list of code fragments
|
90 |
+
for each symbol parsed from code, and ``not_found`` are
|
91 |
+
symbols not found in the code.
|
92 |
+
|
93 |
+
For example::
|
94 |
+
|
95 |
+
In [1]: code = '''a = 10
|
96 |
+
...: def b(): return 42
|
97 |
+
...: class A: pass'''
|
98 |
+
|
99 |
+
In [2]: extract_symbols(code, 'A,b,z')
|
100 |
+
Out[2]: (['class A: pass\\n', 'def b(): return 42\\n'], ['z'])
|
101 |
+
"""
|
102 |
+
symbols = symbols.split(',')
|
103 |
+
|
104 |
+
# this will raise SyntaxError if code isn't valid Python
|
105 |
+
py_code = ast.parse(code)
|
106 |
+
|
107 |
+
marks = [(getattr(s, 'name', None), s.lineno) for s in py_code.body]
|
108 |
+
code = code.split('\n')
|
109 |
+
|
110 |
+
symbols_lines = {}
|
111 |
+
|
112 |
+
# we already know the start_lineno of each symbol (marks).
|
113 |
+
# To find each end_lineno, we traverse in reverse order until each
|
114 |
+
# non-blank line
|
115 |
+
end = len(code)
|
116 |
+
for name, start in reversed(marks):
|
117 |
+
while not code[end - 1].strip():
|
118 |
+
end -= 1
|
119 |
+
if name:
|
120 |
+
symbols_lines[name] = (start - 1, end)
|
121 |
+
end = start - 1
|
122 |
+
|
123 |
+
# Now symbols_lines is a map
|
124 |
+
# {'symbol_name': (start_lineno, end_lineno), ...}
|
125 |
+
|
126 |
+
# fill a list with chunks of codes for each requested symbol
|
127 |
+
blocks = []
|
128 |
+
not_found = []
|
129 |
+
for symbol in symbols:
|
130 |
+
if symbol in symbols_lines:
|
131 |
+
start, end = symbols_lines[symbol]
|
132 |
+
blocks.append('\n'.join(code[start:end]) + '\n')
|
133 |
+
else:
|
134 |
+
not_found.append(symbol)
|
135 |
+
|
136 |
+
return blocks, not_found
|
137 |
+
|
138 |
+
def strip_initial_indent(lines):
|
139 |
+
"""For %load, strip indent from lines until finding an unindented line.
|
140 |
+
|
141 |
+
https://github.com/ipython/ipython/issues/9775
|
142 |
+
"""
|
143 |
+
indent_re = re.compile(r'\s+')
|
144 |
+
|
145 |
+
it = iter(lines)
|
146 |
+
first_line = next(it)
|
147 |
+
indent_match = indent_re.match(first_line)
|
148 |
+
|
149 |
+
if indent_match:
|
150 |
+
# First line was indented
|
151 |
+
indent = indent_match.group()
|
152 |
+
yield first_line[len(indent):]
|
153 |
+
|
154 |
+
for line in it:
|
155 |
+
if line.startswith(indent):
|
156 |
+
yield line[len(indent) :]
|
157 |
+
elif line in ("\n", "\r\n") or len(line) == 0:
|
158 |
+
yield line
|
159 |
+
else:
|
160 |
+
# Less indented than the first line - stop dedenting
|
161 |
+
yield line
|
162 |
+
break
|
163 |
+
else:
|
164 |
+
yield first_line
|
165 |
+
|
166 |
+
# Pass the remaining lines through without dedenting
|
167 |
+
for line in it:
|
168 |
+
yield line
|
169 |
+
|
170 |
+
|
171 |
+
class InteractivelyDefined(Exception):
|
172 |
+
"""Exception for interactively defined variable in magic_edit"""
|
173 |
+
def __init__(self, index):
|
174 |
+
self.index = index
|
175 |
+
|
176 |
+
|
177 |
+
@magics_class
|
178 |
+
class CodeMagics(Magics):
|
179 |
+
"""Magics related to code management (loading, saving, editing, ...)."""
|
180 |
+
|
181 |
+
def __init__(self, *args, **kwargs):
|
182 |
+
self._knowntemps = set()
|
183 |
+
super(CodeMagics, self).__init__(*args, **kwargs)
|
184 |
+
|
185 |
+
@line_magic
|
186 |
+
def save(self, parameter_s=''):
|
187 |
+
"""Save a set of lines or a macro to a given filename.
|
188 |
+
|
189 |
+
Usage:\\
|
190 |
+
%save [options] filename [history]
|
191 |
+
|
192 |
+
Options:
|
193 |
+
|
194 |
+
-r: use 'raw' input. By default, the 'processed' history is used,
|
195 |
+
so that magics are loaded in their transformed version to valid
|
196 |
+
Python. If this option is given, the raw input as typed as the
|
197 |
+
command line is used instead.
|
198 |
+
|
199 |
+
-f: force overwrite. If file exists, %save will prompt for overwrite
|
200 |
+
unless -f is given.
|
201 |
+
|
202 |
+
-a: append to the file instead of overwriting it.
|
203 |
+
|
204 |
+
The history argument uses the same syntax as %history for input ranges,
|
205 |
+
then saves the lines to the filename you specify.
|
206 |
+
|
207 |
+
If no ranges are specified, saves history of the current session up to
|
208 |
+
this point.
|
209 |
+
|
210 |
+
It adds a '.py' extension to the file if you don't do so yourself, and
|
211 |
+
it asks for confirmation before overwriting existing files.
|
212 |
+
|
213 |
+
If `-r` option is used, the default extension is `.ipy`.
|
214 |
+
"""
|
215 |
+
|
216 |
+
opts,args = self.parse_options(parameter_s,'fra',mode='list')
|
217 |
+
if not args:
|
218 |
+
raise UsageError('Missing filename.')
|
219 |
+
raw = 'r' in opts
|
220 |
+
force = 'f' in opts
|
221 |
+
append = 'a' in opts
|
222 |
+
mode = 'a' if append else 'w'
|
223 |
+
ext = '.ipy' if raw else '.py'
|
224 |
+
fname, codefrom = args[0], " ".join(args[1:])
|
225 |
+
if not fname.endswith(('.py','.ipy')):
|
226 |
+
fname += ext
|
227 |
+
fname = os.path.expanduser(fname)
|
228 |
+
file_exists = os.path.isfile(fname)
|
229 |
+
if file_exists and not force and not append:
|
230 |
+
try:
|
231 |
+
overwrite = self.shell.ask_yes_no('File `%s` exists. Overwrite (y/[N])? ' % fname, default='n')
|
232 |
+
except StdinNotImplementedError:
|
233 |
+
print("File `%s` exists. Use `%%save -f %s` to force overwrite" % (fname, parameter_s))
|
234 |
+
return
|
235 |
+
if not overwrite :
|
236 |
+
print('Operation cancelled.')
|
237 |
+
return
|
238 |
+
try:
|
239 |
+
cmds = self.shell.find_user_code(codefrom,raw)
|
240 |
+
except (TypeError, ValueError) as e:
|
241 |
+
print(e.args[0])
|
242 |
+
return
|
243 |
+
with io.open(fname, mode, encoding="utf-8") as f:
|
244 |
+
if not file_exists or not append:
|
245 |
+
f.write("# coding: utf-8\n")
|
246 |
+
f.write(cmds)
|
247 |
+
# make sure we end on a newline
|
248 |
+
if not cmds.endswith('\n'):
|
249 |
+
f.write('\n')
|
250 |
+
print('The following commands were written to file `%s`:' % fname)
|
251 |
+
print(cmds)
|
252 |
+
|
253 |
+
@line_magic
|
254 |
+
def pastebin(self, parameter_s=''):
|
255 |
+
"""Upload code to dpaste.com, returning the URL.
|
256 |
+
|
257 |
+
Usage:\\
|
258 |
+
%pastebin [-d "Custom description"][-e 24] 1-7
|
259 |
+
|
260 |
+
The argument can be an input history range, a filename, or the name of a
|
261 |
+
string or macro.
|
262 |
+
|
263 |
+
If no arguments are given, uploads the history of this session up to
|
264 |
+
this point.
|
265 |
+
|
266 |
+
Options:
|
267 |
+
|
268 |
+
-d: Pass a custom description. The default will say
|
269 |
+
"Pasted from IPython".
|
270 |
+
-e: Pass number of days for the link to be expired.
|
271 |
+
The default will be 7 days.
|
272 |
+
"""
|
273 |
+
opts, args = self.parse_options(parameter_s, "d:e:")
|
274 |
+
|
275 |
+
try:
|
276 |
+
code = self.shell.find_user_code(args)
|
277 |
+
except (ValueError, TypeError) as e:
|
278 |
+
print(e.args[0])
|
279 |
+
return
|
280 |
+
|
281 |
+
expiry_days = 7
|
282 |
+
try:
|
283 |
+
expiry_days = int(opts.get("e", 7))
|
284 |
+
except ValueError as e:
|
285 |
+
print(e.args[0].capitalize())
|
286 |
+
return
|
287 |
+
if expiry_days < 1 or expiry_days > 365:
|
288 |
+
print("Expiry days should be in range of 1 to 365")
|
289 |
+
return
|
290 |
+
|
291 |
+
post_data = urlencode(
|
292 |
+
{
|
293 |
+
"title": opts.get("d", "Pasted from IPython"),
|
294 |
+
"syntax": "python",
|
295 |
+
"content": code,
|
296 |
+
"expiry_days": expiry_days,
|
297 |
+
}
|
298 |
+
).encode("utf-8")
|
299 |
+
|
300 |
+
request = Request(
|
301 |
+
"https://dpaste.com/api/v2/",
|
302 |
+
headers={"User-Agent": "IPython v{}".format(version)},
|
303 |
+
)
|
304 |
+
response = urlopen(request, post_data)
|
305 |
+
return response.headers.get('Location')
|
306 |
+
|
307 |
+
@line_magic
|
308 |
+
def loadpy(self, arg_s):
|
309 |
+
"""Alias of `%load`
|
310 |
+
|
311 |
+
`%loadpy` has gained some flexibility and dropped the requirement of a `.py`
|
312 |
+
extension. So it has been renamed simply into %load. You can look at
|
313 |
+
`%load`'s docstring for more info.
|
314 |
+
"""
|
315 |
+
self.load(arg_s)
|
316 |
+
|
317 |
+
@line_magic
|
318 |
+
def load(self, arg_s):
|
319 |
+
"""Load code into the current frontend.
|
320 |
+
|
321 |
+
Usage:\\
|
322 |
+
%load [options] source
|
323 |
+
|
324 |
+
where source can be a filename, URL, input history range, macro, or
|
325 |
+
element in the user namespace
|
326 |
+
|
327 |
+
If no arguments are given, loads the history of this session up to this
|
328 |
+
point.
|
329 |
+
|
330 |
+
Options:
|
331 |
+
|
332 |
+
-r <lines>: Specify lines or ranges of lines to load from the source.
|
333 |
+
Ranges could be specified as x-y (x..y) or in python-style x:y
|
334 |
+
(x..(y-1)). Both limits x and y can be left blank (meaning the
|
335 |
+
beginning and end of the file, respectively).
|
336 |
+
|
337 |
+
-s <symbols>: Specify function or classes to load from python source.
|
338 |
+
|
339 |
+
-y : Don't ask confirmation for loading source above 200 000 characters.
|
340 |
+
|
341 |
+
-n : Include the user's namespace when searching for source code.
|
342 |
+
|
343 |
+
This magic command can either take a local filename, a URL, an history
|
344 |
+
range (see %history) or a macro as argument, it will prompt for
|
345 |
+
confirmation before loading source with more than 200 000 characters, unless
|
346 |
+
-y flag is passed or if the frontend does not support raw_input::
|
347 |
+
|
348 |
+
%load
|
349 |
+
%load myscript.py
|
350 |
+
%load 7-27
|
351 |
+
%load myMacro
|
352 |
+
%load http://www.example.com/myscript.py
|
353 |
+
%load -r 5-10 myscript.py
|
354 |
+
%load -r 10-20,30,40: foo.py
|
355 |
+
%load -s MyClass,wonder_function myscript.py
|
356 |
+
%load -n MyClass
|
357 |
+
%load -n my_module.wonder_function
|
358 |
+
"""
|
359 |
+
opts,args = self.parse_options(arg_s,'yns:r:')
|
360 |
+
search_ns = 'n' in opts
|
361 |
+
contents = self.shell.find_user_code(args, search_ns=search_ns)
|
362 |
+
|
363 |
+
if 's' in opts:
|
364 |
+
try:
|
365 |
+
blocks, not_found = extract_symbols(contents, opts['s'])
|
366 |
+
except SyntaxError:
|
367 |
+
# non python code
|
368 |
+
error("Unable to parse the input as valid Python code")
|
369 |
+
return
|
370 |
+
|
371 |
+
if len(not_found) == 1:
|
372 |
+
warn('The symbol `%s` was not found' % not_found[0])
|
373 |
+
elif len(not_found) > 1:
|
374 |
+
warn('The symbols %s were not found' % get_text_list(not_found,
|
375 |
+
wrap_item_with='`')
|
376 |
+
)
|
377 |
+
|
378 |
+
contents = '\n'.join(blocks)
|
379 |
+
|
380 |
+
if 'r' in opts:
|
381 |
+
ranges = opts['r'].replace(',', ' ')
|
382 |
+
lines = contents.split('\n')
|
383 |
+
slices = extract_code_ranges(ranges)
|
384 |
+
contents = [lines[slice(*slc)] for slc in slices]
|
385 |
+
contents = '\n'.join(strip_initial_indent(chain.from_iterable(contents)))
|
386 |
+
|
387 |
+
l = len(contents)
|
388 |
+
|
389 |
+
# 200 000 is ~ 2500 full 80 character lines
|
390 |
+
# so in average, more than 5000 lines
|
391 |
+
if l > 200000 and 'y' not in opts:
|
392 |
+
try:
|
393 |
+
ans = self.shell.ask_yes_no(("The text you're trying to load seems pretty big"\
|
394 |
+
" (%d characters). Continue (y/[N]) ?" % l), default='n' )
|
395 |
+
except StdinNotImplementedError:
|
396 |
+
#assume yes if raw input not implemented
|
397 |
+
ans = True
|
398 |
+
|
399 |
+
if ans is False :
|
400 |
+
print('Operation cancelled.')
|
401 |
+
return
|
402 |
+
|
403 |
+
contents = "# %load {}\n".format(arg_s) + contents
|
404 |
+
|
405 |
+
self.shell.set_next_input(contents, replace=True)
|
406 |
+
|
407 |
+
@staticmethod
|
408 |
+
def _find_edit_target(shell, args, opts, last_call):
|
409 |
+
"""Utility method used by magic_edit to find what to edit."""
|
410 |
+
|
411 |
+
def make_filename(arg):
|
412 |
+
"Make a filename from the given args"
|
413 |
+
try:
|
414 |
+
filename = get_py_filename(arg)
|
415 |
+
except IOError:
|
416 |
+
# If it ends with .py but doesn't already exist, assume we want
|
417 |
+
# a new file.
|
418 |
+
if arg.endswith('.py'):
|
419 |
+
filename = arg
|
420 |
+
else:
|
421 |
+
filename = None
|
422 |
+
return filename
|
423 |
+
|
424 |
+
# Set a few locals from the options for convenience:
|
425 |
+
opts_prev = 'p' in opts
|
426 |
+
opts_raw = 'r' in opts
|
427 |
+
|
428 |
+
# custom exceptions
|
429 |
+
class DataIsObject(Exception): pass
|
430 |
+
|
431 |
+
# Default line number value
|
432 |
+
lineno = opts.get('n',None)
|
433 |
+
|
434 |
+
if opts_prev:
|
435 |
+
args = '_%s' % last_call[0]
|
436 |
+
if args not in shell.user_ns:
|
437 |
+
args = last_call[1]
|
438 |
+
|
439 |
+
# by default this is done with temp files, except when the given
|
440 |
+
# arg is a filename
|
441 |
+
use_temp = True
|
442 |
+
|
443 |
+
data = ''
|
444 |
+
|
445 |
+
# First, see if the arguments should be a filename.
|
446 |
+
filename = make_filename(args)
|
447 |
+
if filename:
|
448 |
+
use_temp = False
|
449 |
+
elif args:
|
450 |
+
# Mode where user specifies ranges of lines, like in %macro.
|
451 |
+
data = shell.extract_input_lines(args, opts_raw)
|
452 |
+
if not data:
|
453 |
+
try:
|
454 |
+
# Load the parameter given as a variable. If not a string,
|
455 |
+
# process it as an object instead (below)
|
456 |
+
|
457 |
+
# print('*** args',args,'type',type(args)) # dbg
|
458 |
+
data = eval(args, shell.user_ns)
|
459 |
+
if not isinstance(data, str):
|
460 |
+
raise DataIsObject
|
461 |
+
|
462 |
+
except (NameError,SyntaxError):
|
463 |
+
# given argument is not a variable, try as a filename
|
464 |
+
filename = make_filename(args)
|
465 |
+
if filename is None:
|
466 |
+
warn("Argument given (%s) can't be found as a variable "
|
467 |
+
"or as a filename." % args)
|
468 |
+
return (None, None, None)
|
469 |
+
use_temp = False
|
470 |
+
|
471 |
+
except DataIsObject as e:
|
472 |
+
# macros have a special edit function
|
473 |
+
if isinstance(data, Macro):
|
474 |
+
raise MacroToEdit(data) from e
|
475 |
+
|
476 |
+
# For objects, try to edit the file where they are defined
|
477 |
+
filename = find_file(data)
|
478 |
+
if filename:
|
479 |
+
if 'fakemodule' in filename.lower() and \
|
480 |
+
inspect.isclass(data):
|
481 |
+
# class created by %edit? Try to find source
|
482 |
+
# by looking for method definitions instead, the
|
483 |
+
# __module__ in those classes is FakeModule.
|
484 |
+
attrs = [getattr(data, aname) for aname in dir(data)]
|
485 |
+
for attr in attrs:
|
486 |
+
if not inspect.ismethod(attr):
|
487 |
+
continue
|
488 |
+
filename = find_file(attr)
|
489 |
+
if filename and \
|
490 |
+
'fakemodule' not in filename.lower():
|
491 |
+
# change the attribute to be the edit
|
492 |
+
# target instead
|
493 |
+
data = attr
|
494 |
+
break
|
495 |
+
|
496 |
+
m = ipython_input_pat.match(os.path.basename(filename))
|
497 |
+
if m:
|
498 |
+
raise InteractivelyDefined(int(m.groups()[0])) from e
|
499 |
+
|
500 |
+
datafile = 1
|
501 |
+
if filename is None:
|
502 |
+
filename = make_filename(args)
|
503 |
+
datafile = 1
|
504 |
+
if filename is not None:
|
505 |
+
# only warn about this if we get a real name
|
506 |
+
warn('Could not find file where `%s` is defined.\n'
|
507 |
+
'Opening a file named `%s`' % (args, filename))
|
508 |
+
# Now, make sure we can actually read the source (if it was
|
509 |
+
# in a temp file it's gone by now).
|
510 |
+
if datafile:
|
511 |
+
if lineno is None:
|
512 |
+
lineno = find_source_lines(data)
|
513 |
+
if lineno is None:
|
514 |
+
filename = make_filename(args)
|
515 |
+
if filename is None:
|
516 |
+
warn('The file where `%s` was defined '
|
517 |
+
'cannot be read or found.' % data)
|
518 |
+
return (None, None, None)
|
519 |
+
use_temp = False
|
520 |
+
|
521 |
+
if use_temp:
|
522 |
+
filename = shell.mktempfile(data)
|
523 |
+
print('IPython will make a temporary file named:',filename)
|
524 |
+
|
525 |
+
# use last_call to remember the state of the previous call, but don't
|
526 |
+
# let it be clobbered by successive '-p' calls.
|
527 |
+
try:
|
528 |
+
last_call[0] = shell.displayhook.prompt_count
|
529 |
+
if not opts_prev:
|
530 |
+
last_call[1] = args
|
531 |
+
except:
|
532 |
+
pass
|
533 |
+
|
534 |
+
|
535 |
+
return filename, lineno, use_temp
|
536 |
+
|
537 |
+
def _edit_macro(self,mname,macro):
|
538 |
+
"""open an editor with the macro data in a file"""
|
539 |
+
filename = self.shell.mktempfile(macro.value)
|
540 |
+
self.shell.hooks.editor(filename)
|
541 |
+
|
542 |
+
# and make a new macro object, to replace the old one
|
543 |
+
mvalue = Path(filename).read_text(encoding="utf-8")
|
544 |
+
self.shell.user_ns[mname] = Macro(mvalue)
|
545 |
+
|
546 |
+
@skip_doctest
|
547 |
+
@line_magic
|
548 |
+
def edit(self, parameter_s='',last_call=['','']):
|
549 |
+
"""Bring up an editor and execute the resulting code.
|
550 |
+
|
551 |
+
Usage:
|
552 |
+
%edit [options] [args]
|
553 |
+
|
554 |
+
%edit runs IPython's editor hook. The default version of this hook is
|
555 |
+
set to call the editor specified by your $EDITOR environment variable.
|
556 |
+
If this isn't found, it will default to vi under Linux/Unix and to
|
557 |
+
notepad under Windows. See the end of this docstring for how to change
|
558 |
+
the editor hook.
|
559 |
+
|
560 |
+
You can also set the value of this editor via the
|
561 |
+
``TerminalInteractiveShell.editor`` option in your configuration file.
|
562 |
+
This is useful if you wish to use a different editor from your typical
|
563 |
+
default with IPython (and for Windows users who typically don't set
|
564 |
+
environment variables).
|
565 |
+
|
566 |
+
This command allows you to conveniently edit multi-line code right in
|
567 |
+
your IPython session.
|
568 |
+
|
569 |
+
If called without arguments, %edit opens up an empty editor with a
|
570 |
+
temporary file and will execute the contents of this file when you
|
571 |
+
close it (don't forget to save it!).
|
572 |
+
|
573 |
+
|
574 |
+
Options:
|
575 |
+
|
576 |
+
-n <number>: open the editor at a specified line number. By default,
|
577 |
+
the IPython editor hook uses the unix syntax 'editor +N filename', but
|
578 |
+
you can configure this by providing your own modified hook if your
|
579 |
+
favorite editor supports line-number specifications with a different
|
580 |
+
syntax.
|
581 |
+
|
582 |
+
-p: this will call the editor with the same data as the previous time
|
583 |
+
it was used, regardless of how long ago (in your current session) it
|
584 |
+
was.
|
585 |
+
|
586 |
+
-r: use 'raw' input. This option only applies to input taken from the
|
587 |
+
user's history. By default, the 'processed' history is used, so that
|
588 |
+
magics are loaded in their transformed version to valid Python. If
|
589 |
+
this option is given, the raw input as typed as the command line is
|
590 |
+
used instead. When you exit the editor, it will be executed by
|
591 |
+
IPython's own processor.
|
592 |
+
|
593 |
+
-x: do not execute the edited code immediately upon exit. This is
|
594 |
+
mainly useful if you are editing programs which need to be called with
|
595 |
+
command line arguments, which you can then do using %run.
|
596 |
+
|
597 |
+
|
598 |
+
Arguments:
|
599 |
+
|
600 |
+
If arguments are given, the following possibilities exist:
|
601 |
+
|
602 |
+
- If the argument is a filename, IPython will load that into the
|
603 |
+
editor. It will execute its contents with execfile() when you exit,
|
604 |
+
loading any code in the file into your interactive namespace.
|
605 |
+
|
606 |
+
- The arguments are ranges of input history, e.g. "7 ~1/4-6".
|
607 |
+
The syntax is the same as in the %history magic.
|
608 |
+
|
609 |
+
- If the argument is a string variable, its contents are loaded
|
610 |
+
into the editor. You can thus edit any string which contains
|
611 |
+
python code (including the result of previous edits).
|
612 |
+
|
613 |
+
- If the argument is the name of an object (other than a string),
|
614 |
+
IPython will try to locate the file where it was defined and open the
|
615 |
+
editor at the point where it is defined. You can use `%edit function`
|
616 |
+
to load an editor exactly at the point where 'function' is defined,
|
617 |
+
edit it and have the file be executed automatically.
|
618 |
+
|
619 |
+
- If the object is a macro (see %macro for details), this opens up your
|
620 |
+
specified editor with a temporary file containing the macro's data.
|
621 |
+
Upon exit, the macro is reloaded with the contents of the file.
|
622 |
+
|
623 |
+
Note: opening at an exact line is only supported under Unix, and some
|
624 |
+
editors (like kedit and gedit up to Gnome 2.8) do not understand the
|
625 |
+
'+NUMBER' parameter necessary for this feature. Good editors like
|
626 |
+
(X)Emacs, vi, jed, pico and joe all do.
|
627 |
+
|
628 |
+
After executing your code, %edit will return as output the code you
|
629 |
+
typed in the editor (except when it was an existing file). This way
|
630 |
+
you can reload the code in further invocations of %edit as a variable,
|
631 |
+
via _<NUMBER> or Out[<NUMBER>], where <NUMBER> is the prompt number of
|
632 |
+
the output.
|
633 |
+
|
634 |
+
Note that %edit is also available through the alias %ed.
|
635 |
+
|
636 |
+
This is an example of creating a simple function inside the editor and
|
637 |
+
then modifying it. First, start up the editor::
|
638 |
+
|
639 |
+
In [1]: edit
|
640 |
+
Editing... done. Executing edited code...
|
641 |
+
Out[1]: 'def foo():\\n print("foo() was defined in an editing
|
642 |
+
session")\\n'
|
643 |
+
|
644 |
+
We can then call the function foo()::
|
645 |
+
|
646 |
+
In [2]: foo()
|
647 |
+
foo() was defined in an editing session
|
648 |
+
|
649 |
+
Now we edit foo. IPython automatically loads the editor with the
|
650 |
+
(temporary) file where foo() was previously defined::
|
651 |
+
|
652 |
+
In [3]: edit foo
|
653 |
+
Editing... done. Executing edited code...
|
654 |
+
|
655 |
+
And if we call foo() again we get the modified version::
|
656 |
+
|
657 |
+
In [4]: foo()
|
658 |
+
foo() has now been changed!
|
659 |
+
|
660 |
+
Here is an example of how to edit a code snippet successive
|
661 |
+
times. First we call the editor::
|
662 |
+
|
663 |
+
In [5]: edit
|
664 |
+
Editing... done. Executing edited code...
|
665 |
+
hello
|
666 |
+
Out[5]: "print('hello')\\n"
|
667 |
+
|
668 |
+
Now we call it again with the previous output (stored in _)::
|
669 |
+
|
670 |
+
In [6]: edit _
|
671 |
+
Editing... done. Executing edited code...
|
672 |
+
hello world
|
673 |
+
Out[6]: "print('hello world')\\n"
|
674 |
+
|
675 |
+
Now we call it with the output #8 (stored in _8, also as Out[8])::
|
676 |
+
|
677 |
+
In [7]: edit _8
|
678 |
+
Editing... done. Executing edited code...
|
679 |
+
hello again
|
680 |
+
Out[7]: "print('hello again')\\n"
|
681 |
+
|
682 |
+
|
683 |
+
Changing the default editor hook:
|
684 |
+
|
685 |
+
If you wish to write your own editor hook, you can put it in a
|
686 |
+
configuration file which you load at startup time. The default hook
|
687 |
+
is defined in the IPython.core.hooks module, and you can use that as a
|
688 |
+
starting example for further modifications. That file also has
|
689 |
+
general instructions on how to set a new hook for use once you've
|
690 |
+
defined it."""
|
691 |
+
opts,args = self.parse_options(parameter_s,'prxn:')
|
692 |
+
|
693 |
+
try:
|
694 |
+
filename, lineno, is_temp = self._find_edit_target(self.shell,
|
695 |
+
args, opts, last_call)
|
696 |
+
except MacroToEdit as e:
|
697 |
+
self._edit_macro(args, e.args[0])
|
698 |
+
return
|
699 |
+
except InteractivelyDefined as e:
|
700 |
+
print("Editing In[%i]" % e.index)
|
701 |
+
args = str(e.index)
|
702 |
+
filename, lineno, is_temp = self._find_edit_target(self.shell,
|
703 |
+
args, opts, last_call)
|
704 |
+
if filename is None:
|
705 |
+
# nothing was found, warnings have already been issued,
|
706 |
+
# just give up.
|
707 |
+
return
|
708 |
+
|
709 |
+
if is_temp:
|
710 |
+
self._knowntemps.add(filename)
|
711 |
+
elif (filename in self._knowntemps):
|
712 |
+
is_temp = True
|
713 |
+
|
714 |
+
|
715 |
+
# do actual editing here
|
716 |
+
print('Editing...', end=' ')
|
717 |
+
sys.stdout.flush()
|
718 |
+
filepath = Path(filename)
|
719 |
+
try:
|
720 |
+
# Quote filenames that may have spaces in them when opening
|
721 |
+
# the editor
|
722 |
+
quoted = filename = str(filepath.absolute())
|
723 |
+
if " " in quoted:
|
724 |
+
quoted = "'%s'" % quoted
|
725 |
+
self.shell.hooks.editor(quoted, lineno)
|
726 |
+
except TryNext:
|
727 |
+
warn('Could not open editor')
|
728 |
+
return
|
729 |
+
|
730 |
+
# XXX TODO: should this be generalized for all string vars?
|
731 |
+
# For now, this is special-cased to blocks created by cpaste
|
732 |
+
if args.strip() == "pasted_block":
|
733 |
+
self.shell.user_ns["pasted_block"] = filepath.read_text(encoding="utf-8")
|
734 |
+
|
735 |
+
if 'x' in opts: # -x prevents actual execution
|
736 |
+
print()
|
737 |
+
else:
|
738 |
+
print('done. Executing edited code...')
|
739 |
+
with preserve_keys(self.shell.user_ns, '__file__'):
|
740 |
+
if not is_temp:
|
741 |
+
self.shell.user_ns["__file__"] = filename
|
742 |
+
if "r" in opts: # Untranslated IPython code
|
743 |
+
source = filepath.read_text(encoding="utf-8")
|
744 |
+
self.shell.run_cell(source, store_history=False)
|
745 |
+
else:
|
746 |
+
self.shell.safe_execfile(filename, self.shell.user_ns,
|
747 |
+
self.shell.user_ns)
|
748 |
+
|
749 |
+
if is_temp:
|
750 |
+
try:
|
751 |
+
return filepath.read_text(encoding="utf-8")
|
752 |
+
except IOError as msg:
|
753 |
+
if Path(msg.filename) == filepath:
|
754 |
+
warn('File not found. Did you forget to save?')
|
755 |
+
return
|
756 |
+
else:
|
757 |
+
self.shell.showtraceback()
|
openalex_env_map/lib/python3.10/site-packages/IPython/core/magics/config.py
ADDED
@@ -0,0 +1,140 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"""Implementation of configuration-related magic functions.
|
2 |
+
"""
|
3 |
+
#-----------------------------------------------------------------------------
|
4 |
+
# Copyright (c) 2012 The IPython Development Team.
|
5 |
+
#
|
6 |
+
# Distributed under the terms of the Modified BSD License.
|
7 |
+
#
|
8 |
+
# The full license is in the file COPYING.txt, distributed with this software.
|
9 |
+
#-----------------------------------------------------------------------------
|
10 |
+
|
11 |
+
#-----------------------------------------------------------------------------
|
12 |
+
# Imports
|
13 |
+
#-----------------------------------------------------------------------------
|
14 |
+
|
15 |
+
# Stdlib
|
16 |
+
import re
|
17 |
+
|
18 |
+
# Our own packages
|
19 |
+
from IPython.core.error import UsageError
|
20 |
+
from IPython.core.magic import Magics, magics_class, line_magic
|
21 |
+
from logging import error
|
22 |
+
|
23 |
+
#-----------------------------------------------------------------------------
|
24 |
+
# Magic implementation classes
|
25 |
+
#-----------------------------------------------------------------------------
|
26 |
+
|
27 |
+
reg = re.compile(r'^\w+\.\w+$')
|
28 |
+
@magics_class
|
29 |
+
class ConfigMagics(Magics):
|
30 |
+
|
31 |
+
def __init__(self, shell):
|
32 |
+
super(ConfigMagics, self).__init__(shell)
|
33 |
+
self.configurables = []
|
34 |
+
|
35 |
+
@line_magic
|
36 |
+
def config(self, s):
|
37 |
+
"""configure IPython
|
38 |
+
|
39 |
+
%config Class[.trait=value]
|
40 |
+
|
41 |
+
This magic exposes most of the IPython config system. Any
|
42 |
+
Configurable class should be able to be configured with the simple
|
43 |
+
line::
|
44 |
+
|
45 |
+
%config Class.trait=value
|
46 |
+
|
47 |
+
Where `value` will be resolved in the user's namespace, if it is an
|
48 |
+
expression or variable name.
|
49 |
+
|
50 |
+
Examples
|
51 |
+
--------
|
52 |
+
|
53 |
+
To see what classes are available for config, pass no arguments::
|
54 |
+
|
55 |
+
In [1]: %config
|
56 |
+
Available objects for config:
|
57 |
+
AliasManager
|
58 |
+
DisplayFormatter
|
59 |
+
HistoryManager
|
60 |
+
IPCompleter
|
61 |
+
LoggingMagics
|
62 |
+
MagicsManager
|
63 |
+
OSMagics
|
64 |
+
PrefilterManager
|
65 |
+
ScriptMagics
|
66 |
+
TerminalInteractiveShell
|
67 |
+
|
68 |
+
To view what is configurable on a given class, just pass the class
|
69 |
+
name::
|
70 |
+
|
71 |
+
In [2]: %config LoggingMagics
|
72 |
+
LoggingMagics(Magics) options
|
73 |
+
---------------------------
|
74 |
+
LoggingMagics.quiet=<Bool>
|
75 |
+
Suppress output of log state when logging is enabled
|
76 |
+
Current: False
|
77 |
+
|
78 |
+
but the real use is in setting values::
|
79 |
+
|
80 |
+
In [3]: %config LoggingMagics.quiet = True
|
81 |
+
|
82 |
+
and these values are read from the user_ns if they are variables::
|
83 |
+
|
84 |
+
In [4]: feeling_quiet=False
|
85 |
+
|
86 |
+
In [5]: %config LoggingMagics.quiet = feeling_quiet
|
87 |
+
|
88 |
+
"""
|
89 |
+
from traitlets.config.loader import Config
|
90 |
+
# some IPython objects are Configurable, but do not yet have
|
91 |
+
# any configurable traits. Exclude them from the effects of
|
92 |
+
# this magic, as their presence is just noise:
|
93 |
+
configurables = sorted(set([ c for c in self.shell.configurables
|
94 |
+
if c.__class__.class_traits(config=True)
|
95 |
+
]), key=lambda x: x.__class__.__name__)
|
96 |
+
classnames = [ c.__class__.__name__ for c in configurables ]
|
97 |
+
|
98 |
+
line = s.strip()
|
99 |
+
if not line:
|
100 |
+
# print available configurable names
|
101 |
+
print("Available objects for config:")
|
102 |
+
for name in classnames:
|
103 |
+
print(" ", name)
|
104 |
+
return
|
105 |
+
elif line in classnames:
|
106 |
+
# `%config TerminalInteractiveShell` will print trait info for
|
107 |
+
# TerminalInteractiveShell
|
108 |
+
c = configurables[classnames.index(line)]
|
109 |
+
cls = c.__class__
|
110 |
+
help = cls.class_get_help(c)
|
111 |
+
# strip leading '--' from cl-args:
|
112 |
+
help = re.sub(re.compile(r'^--', re.MULTILINE), '', help)
|
113 |
+
print(help)
|
114 |
+
return
|
115 |
+
elif reg.match(line):
|
116 |
+
cls, attr = line.split('.')
|
117 |
+
return getattr(configurables[classnames.index(cls)],attr)
|
118 |
+
elif '=' not in line:
|
119 |
+
msg = "Invalid config statement: %r, "\
|
120 |
+
"should be `Class.trait = value`."
|
121 |
+
|
122 |
+
ll = line.lower()
|
123 |
+
for classname in classnames:
|
124 |
+
if ll == classname.lower():
|
125 |
+
msg = msg + '\nDid you mean %s (note the case)?' % classname
|
126 |
+
break
|
127 |
+
|
128 |
+
raise UsageError( msg % line)
|
129 |
+
|
130 |
+
# otherwise, assume we are setting configurables.
|
131 |
+
# leave quotes on args when splitting, because we want
|
132 |
+
# unquoted args to eval in user_ns
|
133 |
+
cfg = Config()
|
134 |
+
exec("cfg."+line, self.shell.user_ns, locals())
|
135 |
+
|
136 |
+
for configurable in configurables:
|
137 |
+
try:
|
138 |
+
configurable.update_config(cfg)
|
139 |
+
except Exception as e:
|
140 |
+
error(e)
|
openalex_env_map/lib/python3.10/site-packages/IPython/core/magics/display.py
ADDED
@@ -0,0 +1,93 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
"""Simple magics for display formats"""
|
2 |
+
#-----------------------------------------------------------------------------
|
3 |
+
# Copyright (c) 2012 The IPython Development Team.
|
4 |
+
#
|
5 |
+
# Distributed under the terms of the Modified BSD License.
|
6 |
+
#
|
7 |
+
# The full license is in the file COPYING.txt, distributed with this software.
|
8 |
+
#-----------------------------------------------------------------------------
|
9 |
+
|
10 |
+
#-----------------------------------------------------------------------------
|
11 |
+
# Imports
|
12 |
+
#-----------------------------------------------------------------------------
|
13 |
+
|
14 |
+
# Our own packages
|
15 |
+
from IPython.display import display, Javascript, Latex, SVG, HTML, Markdown
|
16 |
+
from IPython.core.magic import (
|
17 |
+
Magics, magics_class, cell_magic
|
18 |
+
)
|
19 |
+
from IPython.core import magic_arguments
|
20 |
+
|
21 |
+
#-----------------------------------------------------------------------------
|
22 |
+
# Magic implementation classes
|
23 |
+
#-----------------------------------------------------------------------------
|
24 |
+
|
25 |
+
|
26 |
+
@magics_class
|
27 |
+
class DisplayMagics(Magics):
|
28 |
+
"""Magics for displaying various output types with literals
|
29 |
+
|
30 |
+
Defines javascript/latex/svg/html cell magics for writing
|
31 |
+
blocks in those languages, to be rendered in the frontend.
|
32 |
+
"""
|
33 |
+
|
34 |
+
@cell_magic
|
35 |
+
def js(self, line, cell):
|
36 |
+
"""Run the cell block of Javascript code
|
37 |
+
|
38 |
+
Alias of `%%javascript`
|
39 |
+
|
40 |
+
Starting with IPython 8.0 %%javascript is pending deprecation to be replaced
|
41 |
+
by a more flexible system
|
42 |
+
|
43 |
+
Please See https://github.com/ipython/ipython/issues/13376
|
44 |
+
"""
|
45 |
+
self.javascript(line, cell)
|
46 |
+
|
47 |
+
@cell_magic
|
48 |
+
def javascript(self, line, cell):
|
49 |
+
"""Run the cell block of Javascript code
|
50 |
+
|
51 |
+
Starting with IPython 8.0 %%javascript is pending deprecation to be replaced
|
52 |
+
by a more flexible system
|
53 |
+
|
54 |
+
Please See https://github.com/ipython/ipython/issues/13376
|
55 |
+
"""
|
56 |
+
display(Javascript(cell))
|
57 |
+
|
58 |
+
|
59 |
+
@cell_magic
|
60 |
+
def latex(self, line, cell):
|
61 |
+
"""Render the cell as a block of LaTeX
|
62 |
+
|
63 |
+
The subset of LaTeX which is supported depends on the implementation in
|
64 |
+
the client. In the Jupyter Notebook, this magic only renders the subset
|
65 |
+
of LaTeX defined by MathJax
|
66 |
+
[here](https://docs.mathjax.org/en/v2.5-latest/tex.html)."""
|
67 |
+
display(Latex(cell))
|
68 |
+
|
69 |
+
@cell_magic
|
70 |
+
def svg(self, line, cell):
|
71 |
+
"""Render the cell as an SVG literal"""
|
72 |
+
display(SVG(cell))
|
73 |
+
|
74 |
+
@magic_arguments.magic_arguments()
|
75 |
+
@magic_arguments.argument(
|
76 |
+
'--isolated', action='store_true', default=False,
|
77 |
+
help="""Annotate the cell as 'isolated'.
|
78 |
+
Isolated cells are rendered inside their own <iframe> tag"""
|
79 |
+
)
|
80 |
+
@cell_magic
|
81 |
+
def html(self, line, cell):
|
82 |
+
"""Render the cell as a block of HTML"""
|
83 |
+
args = magic_arguments.parse_argstring(self.html, line)
|
84 |
+
html = HTML(cell)
|
85 |
+
if args.isolated:
|
86 |
+
display(html, metadata={'text/html':{'isolated':True}})
|
87 |
+
else:
|
88 |
+
display(html)
|
89 |
+
|
90 |
+
@cell_magic
|
91 |
+
def markdown(self, line, cell):
|
92 |
+
"""Render the cell as Markdown text block"""
|
93 |
+
display(Markdown(cell))
|
openalex_env_map/lib/python3.10/site-packages/IPython/core/magics/execution.py
ADDED
@@ -0,0 +1,1624 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
# -*- coding: utf-8 -*-
|
2 |
+
"""Implementation of execution-related magic functions."""
|
3 |
+
|
4 |
+
# Copyright (c) IPython Development Team.
|
5 |
+
# Distributed under the terms of the Modified BSD License.
|
6 |
+
|
7 |
+
|
8 |
+
import ast
|
9 |
+
import bdb
|
10 |
+
import builtins as builtin_mod
|
11 |
+
import copy
|
12 |
+
import cProfile as profile
|
13 |
+
import gc
|
14 |
+
import itertools
|
15 |
+
import math
|
16 |
+
import os
|
17 |
+
import pstats
|
18 |
+
import re
|
19 |
+
import shlex
|
20 |
+
import sys
|
21 |
+
import time
|
22 |
+
import timeit
|
23 |
+
from typing import Dict, Any
|
24 |
+
from ast import (
|
25 |
+
Assign,
|
26 |
+
Call,
|
27 |
+
Expr,
|
28 |
+
Load,
|
29 |
+
Module,
|
30 |
+
Name,
|
31 |
+
NodeTransformer,
|
32 |
+
Store,
|
33 |
+
parse,
|
34 |
+
unparse,
|
35 |
+
)
|
36 |
+
from io import StringIO
|
37 |
+
from logging import error
|
38 |
+
from pathlib import Path
|
39 |
+
from pdb import Restart
|
40 |
+
from textwrap import dedent, indent
|
41 |
+
from warnings import warn
|
42 |
+
|
43 |
+
from IPython.core import magic_arguments, oinspect, page
|
44 |
+
from IPython.core.displayhook import DisplayHook
|
45 |
+
from IPython.core.error import UsageError
|
46 |
+
from IPython.core.macro import Macro
|
47 |
+
from IPython.core.magic import (
|
48 |
+
Magics,
|
49 |
+
cell_magic,
|
50 |
+
line_cell_magic,
|
51 |
+
line_magic,
|
52 |
+
magics_class,
|
53 |
+
needs_local_scope,
|
54 |
+
no_var_expand,
|
55 |
+
on_off,
|
56 |
+
output_can_be_silenced,
|
57 |
+
)
|
58 |
+
from IPython.testing.skipdoctest import skip_doctest
|
59 |
+
from IPython.utils.capture import capture_output
|
60 |
+
from IPython.utils.contexts import preserve_keys
|
61 |
+
from IPython.utils.ipstruct import Struct
|
62 |
+
from IPython.utils.module_paths import find_mod
|
63 |
+
from IPython.utils.path import get_py_filename, shellglob
|
64 |
+
from IPython.utils.timing import clock, clock2
|
65 |
+
from IPython.core.magics.ast_mod import ReplaceCodeTransformer
|
66 |
+
|
67 |
+
#-----------------------------------------------------------------------------
|
68 |
+
# Magic implementation classes
|
69 |
+
#-----------------------------------------------------------------------------
|
70 |
+
|
71 |
+
|
72 |
+
class TimeitResult(object):
|
73 |
+
"""
|
74 |
+
Object returned by the timeit magic with info about the run.
|
75 |
+
|
76 |
+
Contains the following attributes :
|
77 |
+
|
78 |
+
loops: (int) number of loops done per measurement
|
79 |
+
repeat: (int) number of times the measurement has been repeated
|
80 |
+
best: (float) best execution time / number
|
81 |
+
all_runs: (list of float) execution time of each run (in s)
|
82 |
+
compile_time: (float) time of statement compilation (s)
|
83 |
+
|
84 |
+
"""
|
85 |
+
def __init__(self, loops, repeat, best, worst, all_runs, compile_time, precision):
|
86 |
+
self.loops = loops
|
87 |
+
self.repeat = repeat
|
88 |
+
self.best = best
|
89 |
+
self.worst = worst
|
90 |
+
self.all_runs = all_runs
|
91 |
+
self.compile_time = compile_time
|
92 |
+
self._precision = precision
|
93 |
+
self.timings = [ dt / self.loops for dt in all_runs]
|
94 |
+
|
95 |
+
@property
|
96 |
+
def average(self):
|
97 |
+
return math.fsum(self.timings) / len(self.timings)
|
98 |
+
|
99 |
+
@property
|
100 |
+
def stdev(self):
|
101 |
+
mean = self.average
|
102 |
+
return (math.fsum([(x - mean) ** 2 for x in self.timings]) / len(self.timings)) ** 0.5
|
103 |
+
|
104 |
+
def __str__(self):
|
105 |
+
pm = '+-'
|
106 |
+
if hasattr(sys.stdout, 'encoding') and sys.stdout.encoding:
|
107 |
+
try:
|
108 |
+
u'\xb1'.encode(sys.stdout.encoding)
|
109 |
+
pm = u'\xb1'
|
110 |
+
except:
|
111 |
+
pass
|
112 |
+
return "{mean} {pm} {std} per loop (mean {pm} std. dev. of {runs} run{run_plural}, {loops:,} loop{loop_plural} each)".format(
|
113 |
+
pm=pm,
|
114 |
+
runs=self.repeat,
|
115 |
+
loops=self.loops,
|
116 |
+
loop_plural="" if self.loops == 1 else "s",
|
117 |
+
run_plural="" if self.repeat == 1 else "s",
|
118 |
+
mean=_format_time(self.average, self._precision),
|
119 |
+
std=_format_time(self.stdev, self._precision),
|
120 |
+
)
|
121 |
+
|
122 |
+
def _repr_pretty_(self, p , cycle):
|
123 |
+
unic = self.__str__()
|
124 |
+
p.text(u'<TimeitResult : '+unic+u'>')
|
125 |
+
|
126 |
+
|
127 |
+
class TimeitTemplateFiller(ast.NodeTransformer):
|
128 |
+
"""Fill in the AST template for timing execution.
|
129 |
+
|
130 |
+
This is quite closely tied to the template definition, which is in
|
131 |
+
:meth:`ExecutionMagics.timeit`.
|
132 |
+
"""
|
133 |
+
def __init__(self, ast_setup, ast_stmt):
|
134 |
+
self.ast_setup = ast_setup
|
135 |
+
self.ast_stmt = ast_stmt
|
136 |
+
|
137 |
+
def visit_FunctionDef(self, node):
|
138 |
+
"Fill in the setup statement"
|
139 |
+
self.generic_visit(node)
|
140 |
+
if node.name == "inner":
|
141 |
+
node.body[:1] = self.ast_setup.body
|
142 |
+
|
143 |
+
return node
|
144 |
+
|
145 |
+
def visit_For(self, node):
|
146 |
+
"Fill in the statement to be timed"
|
147 |
+
if getattr(getattr(node.body[0], 'value', None), 'id', None) == 'stmt':
|
148 |
+
node.body = self.ast_stmt.body
|
149 |
+
return node
|
150 |
+
|
151 |
+
|
152 |
+
class Timer(timeit.Timer):
|
153 |
+
"""Timer class that explicitly uses self.inner
|
154 |
+
|
155 |
+
which is an undocumented implementation detail of CPython,
|
156 |
+
not shared by PyPy.
|
157 |
+
"""
|
158 |
+
# Timer.timeit copied from CPython 3.4.2
|
159 |
+
def timeit(self, number=timeit.default_number):
|
160 |
+
"""Time 'number' executions of the main statement.
|
161 |
+
|
162 |
+
To be precise, this executes the setup statement once, and
|
163 |
+
then returns the time it takes to execute the main statement
|
164 |
+
a number of times, as a float measured in seconds. The
|
165 |
+
argument is the number of times through the loop, defaulting
|
166 |
+
to one million. The main statement, the setup statement and
|
167 |
+
the timer function to be used are passed to the constructor.
|
168 |
+
"""
|
169 |
+
it = itertools.repeat(None, number)
|
170 |
+
gcold = gc.isenabled()
|
171 |
+
gc.disable()
|
172 |
+
try:
|
173 |
+
timing = self.inner(it, self.timer)
|
174 |
+
finally:
|
175 |
+
if gcold:
|
176 |
+
gc.enable()
|
177 |
+
return timing
|
178 |
+
|
179 |
+
|
180 |
+
@magics_class
|
181 |
+
class ExecutionMagics(Magics):
|
182 |
+
"""Magics related to code execution, debugging, profiling, etc."""
|
183 |
+
|
184 |
+
_transformers: Dict[str, Any] = {}
|
185 |
+
|
186 |
+
def __init__(self, shell):
|
187 |
+
super(ExecutionMagics, self).__init__(shell)
|
188 |
+
# Default execution function used to actually run user code.
|
189 |
+
self.default_runner = None
|
190 |
+
|
191 |
+
@skip_doctest
|
192 |
+
@no_var_expand
|
193 |
+
@line_cell_magic
|
194 |
+
def prun(self, parameter_s='', cell=None):
|
195 |
+
|
196 |
+
"""Run a statement through the python code profiler.
|
197 |
+
|
198 |
+
**Usage, in line mode:**
|
199 |
+
|
200 |
+
%prun [options] statement
|
201 |
+
|
202 |
+
**Usage, in cell mode:**
|
203 |
+
|
204 |
+
%%prun [options] [statement]
|
205 |
+
|
206 |
+
code...
|
207 |
+
|
208 |
+
code...
|
209 |
+
|
210 |
+
In cell mode, the additional code lines are appended to the (possibly
|
211 |
+
empty) statement in the first line. Cell mode allows you to easily
|
212 |
+
profile multiline blocks without having to put them in a separate
|
213 |
+
function.
|
214 |
+
|
215 |
+
The given statement (which doesn't require quote marks) is run via the
|
216 |
+
python profiler in a manner similar to the profile.run() function.
|
217 |
+
Namespaces are internally managed to work correctly; profile.run
|
218 |
+
cannot be used in IPython because it makes certain assumptions about
|
219 |
+
namespaces which do not hold under IPython.
|
220 |
+
|
221 |
+
Options:
|
222 |
+
|
223 |
+
-l <limit>
|
224 |
+
you can place restrictions on what or how much of the
|
225 |
+
profile gets printed. The limit value can be:
|
226 |
+
|
227 |
+
* A string: only information for function names containing this string
|
228 |
+
is printed.
|
229 |
+
|
230 |
+
* An integer: only these many lines are printed.
|
231 |
+
|
232 |
+
* A float (between 0 and 1): this fraction of the report is printed
|
233 |
+
(for example, use a limit of 0.4 to see the topmost 40% only).
|
234 |
+
|
235 |
+
You can combine several limits with repeated use of the option. For
|
236 |
+
example, ``-l __init__ -l 5`` will print only the topmost 5 lines of
|
237 |
+
information about class constructors.
|
238 |
+
|
239 |
+
-r
|
240 |
+
return the pstats.Stats object generated by the profiling. This
|
241 |
+
object has all the information about the profile in it, and you can
|
242 |
+
later use it for further analysis or in other functions.
|
243 |
+
|
244 |
+
-s <key>
|
245 |
+
sort profile by given key. You can provide more than one key
|
246 |
+
by using the option several times: '-s key1 -s key2 -s key3...'. The
|
247 |
+
default sorting key is 'time'.
|
248 |
+
|
249 |
+
The following is copied verbatim from the profile documentation
|
250 |
+
referenced below:
|
251 |
+
|
252 |
+
When more than one key is provided, additional keys are used as
|
253 |
+
secondary criteria when the there is equality in all keys selected
|
254 |
+
before them.
|
255 |
+
|
256 |
+
Abbreviations can be used for any key names, as long as the
|
257 |
+
abbreviation is unambiguous. The following are the keys currently
|
258 |
+
defined:
|
259 |
+
|
260 |
+
============ =====================
|
261 |
+
Valid Arg Meaning
|
262 |
+
============ =====================
|
263 |
+
"calls" call count
|
264 |
+
"cumulative" cumulative time
|
265 |
+
"file" file name
|
266 |
+
"module" file name
|
267 |
+
"pcalls" primitive call count
|
268 |
+
"line" line number
|
269 |
+
"name" function name
|
270 |
+
"nfl" name/file/line
|
271 |
+
"stdname" standard name
|
272 |
+
"time" internal time
|
273 |
+
============ =====================
|
274 |
+
|
275 |
+
Note that all sorts on statistics are in descending order (placing
|
276 |
+
most time consuming items first), where as name, file, and line number
|
277 |
+
searches are in ascending order (i.e., alphabetical). The subtle
|
278 |
+
distinction between "nfl" and "stdname" is that the standard name is a
|
279 |
+
sort of the name as printed, which means that the embedded line
|
280 |
+
numbers get compared in an odd way. For example, lines 3, 20, and 40
|
281 |
+
would (if the file names were the same) appear in the string order
|
282 |
+
"20" "3" and "40". In contrast, "nfl" does a numeric compare of the
|
283 |
+
line numbers. In fact, sort_stats("nfl") is the same as
|
284 |
+
sort_stats("name", "file", "line").
|
285 |
+
|
286 |
+
-T <filename>
|
287 |
+
save profile results as shown on screen to a text
|
288 |
+
file. The profile is still shown on screen.
|
289 |
+
|
290 |
+
-D <filename>
|
291 |
+
save (via dump_stats) profile statistics to given
|
292 |
+
filename. This data is in a format understood by the pstats module, and
|
293 |
+
is generated by a call to the dump_stats() method of profile
|
294 |
+
objects. The profile is still shown on screen.
|
295 |
+
|
296 |
+
-q
|
297 |
+
suppress output to the pager. Best used with -T and/or -D above.
|
298 |
+
|
299 |
+
If you want to run complete programs under the profiler's control, use
|
300 |
+
``%run -p [prof_opts] filename.py [args to program]`` where prof_opts
|
301 |
+
contains profiler specific options as described here.
|
302 |
+
|
303 |
+
You can read the complete documentation for the profile module with::
|
304 |
+
|
305 |
+
In [1]: import profile; profile.help()
|
306 |
+
|
307 |
+
.. versionchanged:: 7.3
|
308 |
+
User variables are no longer expanded,
|
309 |
+
the magic line is always left unmodified.
|
310 |
+
|
311 |
+
"""
|
312 |
+
opts, arg_str = self.parse_options(parameter_s, 'D:l:rs:T:q',
|
313 |
+
list_all=True, posix=False)
|
314 |
+
if cell is not None:
|
315 |
+
arg_str += '\n' + cell
|
316 |
+
arg_str = self.shell.transform_cell(arg_str)
|
317 |
+
return self._run_with_profiler(arg_str, opts, self.shell.user_ns)
|
318 |
+
|
319 |
+
def _run_with_profiler(self, code, opts, namespace):
|
320 |
+
"""
|
321 |
+
Run `code` with profiler. Used by ``%prun`` and ``%run -p``.
|
322 |
+
|
323 |
+
Parameters
|
324 |
+
----------
|
325 |
+
code : str
|
326 |
+
Code to be executed.
|
327 |
+
opts : Struct
|
328 |
+
Options parsed by `self.parse_options`.
|
329 |
+
namespace : dict
|
330 |
+
A dictionary for Python namespace (e.g., `self.shell.user_ns`).
|
331 |
+
|
332 |
+
"""
|
333 |
+
|
334 |
+
# Fill default values for unspecified options:
|
335 |
+
opts.merge(Struct(D=[''], l=[], s=['time'], T=['']))
|
336 |
+
|
337 |
+
prof = profile.Profile()
|
338 |
+
try:
|
339 |
+
prof = prof.runctx(code, namespace, namespace)
|
340 |
+
sys_exit = ''
|
341 |
+
except SystemExit:
|
342 |
+
sys_exit = """*** SystemExit exception caught in code being profiled."""
|
343 |
+
|
344 |
+
stats = pstats.Stats(prof).strip_dirs().sort_stats(*opts.s)
|
345 |
+
|
346 |
+
lims = opts.l
|
347 |
+
if lims:
|
348 |
+
lims = [] # rebuild lims with ints/floats/strings
|
349 |
+
for lim in opts.l:
|
350 |
+
try:
|
351 |
+
lims.append(int(lim))
|
352 |
+
except ValueError:
|
353 |
+
try:
|
354 |
+
lims.append(float(lim))
|
355 |
+
except ValueError:
|
356 |
+
lims.append(lim)
|
357 |
+
|
358 |
+
# Trap output.
|
359 |
+
stdout_trap = StringIO()
|
360 |
+
stats_stream = stats.stream
|
361 |
+
try:
|
362 |
+
stats.stream = stdout_trap
|
363 |
+
stats.print_stats(*lims)
|
364 |
+
finally:
|
365 |
+
stats.stream = stats_stream
|
366 |
+
|
367 |
+
output = stdout_trap.getvalue()
|
368 |
+
output = output.rstrip()
|
369 |
+
|
370 |
+
if 'q' not in opts:
|
371 |
+
page.page(output)
|
372 |
+
print(sys_exit, end=' ')
|
373 |
+
|
374 |
+
dump_file = opts.D[0]
|
375 |
+
text_file = opts.T[0]
|
376 |
+
if dump_file:
|
377 |
+
prof.dump_stats(dump_file)
|
378 |
+
print(
|
379 |
+
f"\n*** Profile stats marshalled to file {repr(dump_file)}.{sys_exit}"
|
380 |
+
)
|
381 |
+
if text_file:
|
382 |
+
pfile = Path(text_file)
|
383 |
+
pfile.touch(exist_ok=True)
|
384 |
+
pfile.write_text(output, encoding="utf-8")
|
385 |
+
|
386 |
+
print(
|
387 |
+
f"\n*** Profile printout saved to text file {repr(text_file)}.{sys_exit}"
|
388 |
+
)
|
389 |
+
|
390 |
+
if 'r' in opts:
|
391 |
+
return stats
|
392 |
+
|
393 |
+
return None
|
394 |
+
|
395 |
+
@line_magic
|
396 |
+
def pdb(self, parameter_s=''):
|
397 |
+
"""Control the automatic calling of the pdb interactive debugger.
|
398 |
+
|
399 |
+
Call as '%pdb on', '%pdb 1', '%pdb off' or '%pdb 0'. If called without
|
400 |
+
argument it works as a toggle.
|
401 |
+
|
402 |
+
When an exception is triggered, IPython can optionally call the
|
403 |
+
interactive pdb debugger after the traceback printout. %pdb toggles
|
404 |
+
this feature on and off.
|
405 |
+
|
406 |
+
The initial state of this feature is set in your configuration
|
407 |
+
file (the option is ``InteractiveShell.pdb``).
|
408 |
+
|
409 |
+
If you want to just activate the debugger AFTER an exception has fired,
|
410 |
+
without having to type '%pdb on' and rerunning your code, you can use
|
411 |
+
the %debug magic."""
|
412 |
+
|
413 |
+
par = parameter_s.strip().lower()
|
414 |
+
|
415 |
+
if par:
|
416 |
+
try:
|
417 |
+
new_pdb = {'off':0,'0':0,'on':1,'1':1}[par]
|
418 |
+
except KeyError:
|
419 |
+
print ('Incorrect argument. Use on/1, off/0, '
|
420 |
+
'or nothing for a toggle.')
|
421 |
+
return
|
422 |
+
else:
|
423 |
+
# toggle
|
424 |
+
new_pdb = not self.shell.call_pdb
|
425 |
+
|
426 |
+
# set on the shell
|
427 |
+
self.shell.call_pdb = new_pdb
|
428 |
+
print('Automatic pdb calling has been turned',on_off(new_pdb))
|
429 |
+
|
430 |
+
@magic_arguments.magic_arguments()
|
431 |
+
@magic_arguments.argument('--breakpoint', '-b', metavar='FILE:LINE',
|
432 |
+
help="""
|
433 |
+
Set break point at LINE in FILE.
|
434 |
+
"""
|
435 |
+
)
|
436 |
+
@magic_arguments.argument('statement', nargs='*',
|
437 |
+
help="""
|
438 |
+
Code to run in debugger.
|
439 |
+
You can omit this in cell magic mode.
|
440 |
+
"""
|
441 |
+
)
|
442 |
+
@no_var_expand
|
443 |
+
@line_cell_magic
|
444 |
+
@needs_local_scope
|
445 |
+
def debug(self, line="", cell=None, local_ns=None):
|
446 |
+
"""Activate the interactive debugger.
|
447 |
+
|
448 |
+
This magic command support two ways of activating debugger.
|
449 |
+
One is to activate debugger before executing code. This way, you
|
450 |
+
can set a break point, to step through the code from the point.
|
451 |
+
You can use this mode by giving statements to execute and optionally
|
452 |
+
a breakpoint.
|
453 |
+
|
454 |
+
The other one is to activate debugger in post-mortem mode. You can
|
455 |
+
activate this mode simply running %debug without any argument.
|
456 |
+
If an exception has just occurred, this lets you inspect its stack
|
457 |
+
frames interactively. Note that this will always work only on the last
|
458 |
+
traceback that occurred, so you must call this quickly after an
|
459 |
+
exception that you wish to inspect has fired, because if another one
|
460 |
+
occurs, it clobbers the previous one.
|
461 |
+
|
462 |
+
If you want IPython to automatically do this on every exception, see
|
463 |
+
the %pdb magic for more details.
|
464 |
+
|
465 |
+
.. versionchanged:: 7.3
|
466 |
+
When running code, user variables are no longer expanded,
|
467 |
+
the magic line is always left unmodified.
|
468 |
+
|
469 |
+
"""
|
470 |
+
args = magic_arguments.parse_argstring(self.debug, line)
|
471 |
+
|
472 |
+
if not (args.breakpoint or args.statement or cell):
|
473 |
+
self._debug_post_mortem()
|
474 |
+
elif not (args.breakpoint or cell):
|
475 |
+
# If there is no breakpoints, the line is just code to execute
|
476 |
+
self._debug_exec(line, None, local_ns)
|
477 |
+
else:
|
478 |
+
# Here we try to reconstruct the code from the output of
|
479 |
+
# parse_argstring. This might not work if the code has spaces
|
480 |
+
# For example this fails for `print("a b")`
|
481 |
+
code = "\n".join(args.statement)
|
482 |
+
if cell:
|
483 |
+
code += "\n" + cell
|
484 |
+
self._debug_exec(code, args.breakpoint, local_ns)
|
485 |
+
|
486 |
+
def _debug_post_mortem(self):
|
487 |
+
self.shell.debugger(force=True)
|
488 |
+
|
489 |
+
def _debug_exec(self, code, breakpoint, local_ns=None):
|
490 |
+
if breakpoint:
|
491 |
+
(filename, bp_line) = breakpoint.rsplit(':', 1)
|
492 |
+
bp_line = int(bp_line)
|
493 |
+
else:
|
494 |
+
(filename, bp_line) = (None, None)
|
495 |
+
self._run_with_debugger(
|
496 |
+
code, self.shell.user_ns, filename, bp_line, local_ns=local_ns
|
497 |
+
)
|
498 |
+
|
499 |
+
@line_magic
|
500 |
+
def tb(self, s):
|
501 |
+
"""Print the last traceback.
|
502 |
+
|
503 |
+
Optionally, specify an exception reporting mode, tuning the
|
504 |
+
verbosity of the traceback. By default the currently-active exception
|
505 |
+
mode is used. See %xmode for changing exception reporting modes.
|
506 |
+
|
507 |
+
Valid modes: Plain, Context, Verbose, and Minimal.
|
508 |
+
"""
|
509 |
+
interactive_tb = self.shell.InteractiveTB
|
510 |
+
if s:
|
511 |
+
# Switch exception reporting mode for this one call.
|
512 |
+
# Ensure it is switched back.
|
513 |
+
def xmode_switch_err(name):
|
514 |
+
warn('Error changing %s exception modes.\n%s' %
|
515 |
+
(name,sys.exc_info()[1]))
|
516 |
+
|
517 |
+
new_mode = s.strip().capitalize()
|
518 |
+
original_mode = interactive_tb.mode
|
519 |
+
try:
|
520 |
+
try:
|
521 |
+
interactive_tb.set_mode(mode=new_mode)
|
522 |
+
except Exception:
|
523 |
+
xmode_switch_err('user')
|
524 |
+
else:
|
525 |
+
self.shell.showtraceback()
|
526 |
+
finally:
|
527 |
+
interactive_tb.set_mode(mode=original_mode)
|
528 |
+
else:
|
529 |
+
self.shell.showtraceback()
|
530 |
+
|
531 |
+
@skip_doctest
|
532 |
+
@line_magic
|
533 |
+
def run(self, parameter_s='', runner=None,
|
534 |
+
file_finder=get_py_filename):
|
535 |
+
"""Run the named file inside IPython as a program.
|
536 |
+
|
537 |
+
Usage::
|
538 |
+
|
539 |
+
%run [-n -i -e -G]
|
540 |
+
[( -t [-N<N>] | -d [-b<N>] | -p [profile options] )]
|
541 |
+
( -m mod | filename ) [args]
|
542 |
+
|
543 |
+
The filename argument should be either a pure Python script (with
|
544 |
+
extension ``.py``), or a file with custom IPython syntax (such as
|
545 |
+
magics). If the latter, the file can be either a script with ``.ipy``
|
546 |
+
extension, or a Jupyter notebook with ``.ipynb`` extension. When running
|
547 |
+
a Jupyter notebook, the output from print statements and other
|
548 |
+
displayed objects will appear in the terminal (even matplotlib figures
|
549 |
+
will open, if a terminal-compliant backend is being used). Note that,
|
550 |
+
at the system command line, the ``jupyter run`` command offers similar
|
551 |
+
functionality for executing notebooks (albeit currently with some
|
552 |
+
differences in supported options).
|
553 |
+
|
554 |
+
Parameters after the filename are passed as command-line arguments to
|
555 |
+
the program (put in sys.argv). Then, control returns to IPython's
|
556 |
+
prompt.
|
557 |
+
|
558 |
+
This is similar to running at a system prompt ``python file args``,
|
559 |
+
but with the advantage of giving you IPython's tracebacks, and of
|
560 |
+
loading all variables into your interactive namespace for further use
|
561 |
+
(unless -p is used, see below).
|
562 |
+
|
563 |
+
The file is executed in a namespace initially consisting only of
|
564 |
+
``__name__=='__main__'`` and sys.argv constructed as indicated. It thus
|
565 |
+
sees its environment as if it were being run as a stand-alone program
|
566 |
+
(except for sharing global objects such as previously imported
|
567 |
+
modules). But after execution, the IPython interactive namespace gets
|
568 |
+
updated with all variables defined in the program (except for __name__
|
569 |
+
and sys.argv). This allows for very convenient loading of code for
|
570 |
+
interactive work, while giving each program a 'clean sheet' to run in.
|
571 |
+
|
572 |
+
Arguments are expanded using shell-like glob match. Patterns
|
573 |
+
'*', '?', '[seq]' and '[!seq]' can be used. Additionally,
|
574 |
+
tilde '~' will be expanded into user's home directory. Unlike
|
575 |
+
real shells, quotation does not suppress expansions. Use
|
576 |
+
*two* back slashes (e.g. ``\\\\*``) to suppress expansions.
|
577 |
+
To completely disable these expansions, you can use -G flag.
|
578 |
+
|
579 |
+
On Windows systems, the use of single quotes `'` when specifying
|
580 |
+
a file is not supported. Use double quotes `"`.
|
581 |
+
|
582 |
+
Options:
|
583 |
+
|
584 |
+
-n
|
585 |
+
__name__ is NOT set to '__main__', but to the running file's name
|
586 |
+
without extension (as python does under import). This allows running
|
587 |
+
scripts and reloading the definitions in them without calling code
|
588 |
+
protected by an ``if __name__ == "__main__"`` clause.
|
589 |
+
|
590 |
+
-i
|
591 |
+
run the file in IPython's namespace instead of an empty one. This
|
592 |
+
is useful if you are experimenting with code written in a text editor
|
593 |
+
which depends on variables defined interactively.
|
594 |
+
|
595 |
+
-e
|
596 |
+
ignore sys.exit() calls or SystemExit exceptions in the script
|
597 |
+
being run. This is particularly useful if IPython is being used to
|
598 |
+
run unittests, which always exit with a sys.exit() call. In such
|
599 |
+
cases you are interested in the output of the test results, not in
|
600 |
+
seeing a traceback of the unittest module.
|
601 |
+
|
602 |
+
-t
|
603 |
+
print timing information at the end of the run. IPython will give
|
604 |
+
you an estimated CPU time consumption for your script, which under
|
605 |
+
Unix uses the resource module to avoid the wraparound problems of
|
606 |
+
time.clock(). Under Unix, an estimate of time spent on system tasks
|
607 |
+
is also given (for Windows platforms this is reported as 0.0).
|
608 |
+
|
609 |
+
If -t is given, an additional ``-N<N>`` option can be given, where <N>
|
610 |
+
must be an integer indicating how many times you want the script to
|
611 |
+
run. The final timing report will include total and per run results.
|
612 |
+
|
613 |
+
For example (testing the script uniq_stable.py)::
|
614 |
+
|
615 |
+
In [1]: run -t uniq_stable
|
616 |
+
|
617 |
+
IPython CPU timings (estimated):
|
618 |
+
User : 0.19597 s.
|
619 |
+
System: 0.0 s.
|
620 |
+
|
621 |
+
In [2]: run -t -N5 uniq_stable
|
622 |
+
|
623 |
+
IPython CPU timings (estimated):
|
624 |
+
Total runs performed: 5
|
625 |
+
Times : Total Per run
|
626 |
+
User : 0.910862 s, 0.1821724 s.
|
627 |
+
System: 0.0 s, 0.0 s.
|
628 |
+
|
629 |
+
-d
|
630 |
+
run your program under the control of pdb, the Python debugger.
|
631 |
+
This allows you to execute your program step by step, watch variables,
|
632 |
+
etc. Internally, what IPython does is similar to calling::
|
633 |
+
|
634 |
+
pdb.run('execfile("YOURFILENAME")')
|
635 |
+
|
636 |
+
with a breakpoint set on line 1 of your file. You can change the line
|
637 |
+
number for this automatic breakpoint to be <N> by using the -bN option
|
638 |
+
(where N must be an integer). For example::
|
639 |
+
|
640 |
+
%run -d -b40 myscript
|
641 |
+
|
642 |
+
will set the first breakpoint at line 40 in myscript.py. Note that
|
643 |
+
the first breakpoint must be set on a line which actually does
|
644 |
+
something (not a comment or docstring) for it to stop execution.
|
645 |
+
|
646 |
+
Or you can specify a breakpoint in a different file::
|
647 |
+
|
648 |
+
%run -d -b myotherfile.py:20 myscript
|
649 |
+
|
650 |
+
When the pdb debugger starts, you will see a (Pdb) prompt. You must
|
651 |
+
first enter 'c' (without quotes) to start execution up to the first
|
652 |
+
breakpoint.
|
653 |
+
|
654 |
+
Entering 'help' gives information about the use of the debugger. You
|
655 |
+
can easily see pdb's full documentation with "import pdb;pdb.help()"
|
656 |
+
at a prompt.
|
657 |
+
|
658 |
+
-p
|
659 |
+
run program under the control of the Python profiler module (which
|
660 |
+
prints a detailed report of execution times, function calls, etc).
|
661 |
+
|
662 |
+
You can pass other options after -p which affect the behavior of the
|
663 |
+
profiler itself. See the docs for %prun for details.
|
664 |
+
|
665 |
+
In this mode, the program's variables do NOT propagate back to the
|
666 |
+
IPython interactive namespace (because they remain in the namespace
|
667 |
+
where the profiler executes them).
|
668 |
+
|
669 |
+
Internally this triggers a call to %prun, see its documentation for
|
670 |
+
details on the options available specifically for profiling.
|
671 |
+
|
672 |
+
There is one special usage for which the text above doesn't apply:
|
673 |
+
if the filename ends with .ipy[nb], the file is run as ipython script,
|
674 |
+
just as if the commands were written on IPython prompt.
|
675 |
+
|
676 |
+
-m
|
677 |
+
specify module name to load instead of script path. Similar to
|
678 |
+
the -m option for the python interpreter. Use this option last if you
|
679 |
+
want to combine with other %run options. Unlike the python interpreter
|
680 |
+
only source modules are allowed no .pyc or .pyo files.
|
681 |
+
For example::
|
682 |
+
|
683 |
+
%run -m example
|
684 |
+
|
685 |
+
will run the example module.
|
686 |
+
|
687 |
+
-G
|
688 |
+
disable shell-like glob expansion of arguments.
|
689 |
+
|
690 |
+
"""
|
691 |
+
|
692 |
+
# Logic to handle issue #3664
|
693 |
+
# Add '--' after '-m <module_name>' to ignore additional args passed to a module.
|
694 |
+
if '-m' in parameter_s and '--' not in parameter_s:
|
695 |
+
argv = shlex.split(parameter_s, posix=(os.name == 'posix'))
|
696 |
+
for idx, arg in enumerate(argv):
|
697 |
+
if arg and arg.startswith('-') and arg != '-':
|
698 |
+
if arg == '-m':
|
699 |
+
argv.insert(idx + 2, '--')
|
700 |
+
break
|
701 |
+
else:
|
702 |
+
# Positional arg, break
|
703 |
+
break
|
704 |
+
parameter_s = ' '.join(shlex.quote(arg) for arg in argv)
|
705 |
+
|
706 |
+
# get arguments and set sys.argv for program to be run.
|
707 |
+
opts, arg_lst = self.parse_options(parameter_s,
|
708 |
+
'nidtN:b:pD:l:rs:T:em:G',
|
709 |
+
mode='list', list_all=1)
|
710 |
+
if "m" in opts:
|
711 |
+
modulename = opts["m"][0]
|
712 |
+
modpath = find_mod(modulename)
|
713 |
+
if modpath is None:
|
714 |
+
msg = '%r is not a valid modulename on sys.path'%modulename
|
715 |
+
raise Exception(msg)
|
716 |
+
arg_lst = [modpath] + arg_lst
|
717 |
+
try:
|
718 |
+
fpath = None # initialize to make sure fpath is in scope later
|
719 |
+
fpath = arg_lst[0]
|
720 |
+
filename = file_finder(fpath)
|
721 |
+
except IndexError as e:
|
722 |
+
msg = 'you must provide at least a filename.'
|
723 |
+
raise Exception(msg) from e
|
724 |
+
except IOError as e:
|
725 |
+
try:
|
726 |
+
msg = str(e)
|
727 |
+
except UnicodeError:
|
728 |
+
msg = e.message
|
729 |
+
if os.name == 'nt' and re.match(r"^'.*'$",fpath):
|
730 |
+
warn('For Windows, use double quotes to wrap a filename: %run "mypath\\myfile.py"')
|
731 |
+
raise Exception(msg) from e
|
732 |
+
except TypeError:
|
733 |
+
if fpath in sys.meta_path:
|
734 |
+
filename = ""
|
735 |
+
else:
|
736 |
+
raise
|
737 |
+
|
738 |
+
if filename.lower().endswith(('.ipy', '.ipynb')):
|
739 |
+
with preserve_keys(self.shell.user_ns, '__file__'):
|
740 |
+
self.shell.user_ns['__file__'] = filename
|
741 |
+
self.shell.safe_execfile_ipy(filename, raise_exceptions=True)
|
742 |
+
return
|
743 |
+
|
744 |
+
# Control the response to exit() calls made by the script being run
|
745 |
+
exit_ignore = 'e' in opts
|
746 |
+
|
747 |
+
# Make sure that the running script gets a proper sys.argv as if it
|
748 |
+
# were run from a system shell.
|
749 |
+
save_argv = sys.argv # save it for later restoring
|
750 |
+
|
751 |
+
if 'G' in opts:
|
752 |
+
args = arg_lst[1:]
|
753 |
+
else:
|
754 |
+
# tilde and glob expansion
|
755 |
+
args = shellglob(map(os.path.expanduser, arg_lst[1:]))
|
756 |
+
|
757 |
+
sys.argv = [filename] + args # put in the proper filename
|
758 |
+
|
759 |
+
if 'n' in opts:
|
760 |
+
name = Path(filename).stem
|
761 |
+
else:
|
762 |
+
name = '__main__'
|
763 |
+
|
764 |
+
if 'i' in opts:
|
765 |
+
# Run in user's interactive namespace
|
766 |
+
prog_ns = self.shell.user_ns
|
767 |
+
__name__save = self.shell.user_ns['__name__']
|
768 |
+
prog_ns['__name__'] = name
|
769 |
+
main_mod = self.shell.user_module
|
770 |
+
|
771 |
+
# Since '%run foo' emulates 'python foo.py' at the cmd line, we must
|
772 |
+
# set the __file__ global in the script's namespace
|
773 |
+
# TK: Is this necessary in interactive mode?
|
774 |
+
prog_ns['__file__'] = filename
|
775 |
+
else:
|
776 |
+
# Run in a fresh, empty namespace
|
777 |
+
|
778 |
+
# The shell MUST hold a reference to prog_ns so after %run
|
779 |
+
# exits, the python deletion mechanism doesn't zero it out
|
780 |
+
# (leaving dangling references). See interactiveshell for details
|
781 |
+
main_mod = self.shell.new_main_mod(filename, name)
|
782 |
+
prog_ns = main_mod.__dict__
|
783 |
+
|
784 |
+
# pickle fix. See interactiveshell for an explanation. But we need to
|
785 |
+
# make sure that, if we overwrite __main__, we replace it at the end
|
786 |
+
main_mod_name = prog_ns['__name__']
|
787 |
+
|
788 |
+
if main_mod_name == '__main__':
|
789 |
+
restore_main = sys.modules['__main__']
|
790 |
+
else:
|
791 |
+
restore_main = False
|
792 |
+
|
793 |
+
# This needs to be undone at the end to prevent holding references to
|
794 |
+
# every single object ever created.
|
795 |
+
sys.modules[main_mod_name] = main_mod
|
796 |
+
|
797 |
+
if 'p' in opts or 'd' in opts:
|
798 |
+
if 'm' in opts:
|
799 |
+
code = 'run_module(modulename, prog_ns)'
|
800 |
+
code_ns = {
|
801 |
+
'run_module': self.shell.safe_run_module,
|
802 |
+
'prog_ns': prog_ns,
|
803 |
+
'modulename': modulename,
|
804 |
+
}
|
805 |
+
else:
|
806 |
+
if 'd' in opts:
|
807 |
+
# allow exceptions to raise in debug mode
|
808 |
+
code = 'execfile(filename, prog_ns, raise_exceptions=True)'
|
809 |
+
else:
|
810 |
+
code = 'execfile(filename, prog_ns)'
|
811 |
+
code_ns = {
|
812 |
+
'execfile': self.shell.safe_execfile,
|
813 |
+
'prog_ns': prog_ns,
|
814 |
+
'filename': get_py_filename(filename),
|
815 |
+
}
|
816 |
+
|
817 |
+
try:
|
818 |
+
stats = None
|
819 |
+
if 'p' in opts:
|
820 |
+
stats = self._run_with_profiler(code, opts, code_ns)
|
821 |
+
else:
|
822 |
+
if 'd' in opts:
|
823 |
+
bp_file, bp_line = parse_breakpoint(
|
824 |
+
opts.get('b', ['1'])[0], filename)
|
825 |
+
self._run_with_debugger(
|
826 |
+
code, code_ns, filename, bp_line, bp_file)
|
827 |
+
else:
|
828 |
+
if 'm' in opts:
|
829 |
+
def run():
|
830 |
+
self.shell.safe_run_module(modulename, prog_ns)
|
831 |
+
else:
|
832 |
+
if runner is None:
|
833 |
+
runner = self.default_runner
|
834 |
+
if runner is None:
|
835 |
+
runner = self.shell.safe_execfile
|
836 |
+
|
837 |
+
def run():
|
838 |
+
runner(filename, prog_ns, prog_ns,
|
839 |
+
exit_ignore=exit_ignore)
|
840 |
+
|
841 |
+
if 't' in opts:
|
842 |
+
# timed execution
|
843 |
+
try:
|
844 |
+
nruns = int(opts['N'][0])
|
845 |
+
if nruns < 1:
|
846 |
+
error('Number of runs must be >=1')
|
847 |
+
return
|
848 |
+
except (KeyError):
|
849 |
+
nruns = 1
|
850 |
+
self._run_with_timing(run, nruns)
|
851 |
+
else:
|
852 |
+
# regular execution
|
853 |
+
run()
|
854 |
+
|
855 |
+
if 'i' in opts:
|
856 |
+
self.shell.user_ns['__name__'] = __name__save
|
857 |
+
else:
|
858 |
+
# update IPython interactive namespace
|
859 |
+
|
860 |
+
# Some forms of read errors on the file may mean the
|
861 |
+
# __name__ key was never set; using pop we don't have to
|
862 |
+
# worry about a possible KeyError.
|
863 |
+
prog_ns.pop('__name__', None)
|
864 |
+
|
865 |
+
with preserve_keys(self.shell.user_ns, '__file__'):
|
866 |
+
self.shell.user_ns.update(prog_ns)
|
867 |
+
finally:
|
868 |
+
# It's a bit of a mystery why, but __builtins__ can change from
|
869 |
+
# being a module to becoming a dict missing some key data after
|
870 |
+
# %run. As best I can see, this is NOT something IPython is doing
|
871 |
+
# at all, and similar problems have been reported before:
|
872 |
+
# http://coding.derkeiler.com/Archive/Python/comp.lang.python/2004-10/0188.html
|
873 |
+
# Since this seems to be done by the interpreter itself, the best
|
874 |
+
# we can do is to at least restore __builtins__ for the user on
|
875 |
+
# exit.
|
876 |
+
self.shell.user_ns['__builtins__'] = builtin_mod
|
877 |
+
|
878 |
+
# Ensure key global structures are restored
|
879 |
+
sys.argv = save_argv
|
880 |
+
if restore_main:
|
881 |
+
sys.modules['__main__'] = restore_main
|
882 |
+
if '__mp_main__' in sys.modules:
|
883 |
+
sys.modules['__mp_main__'] = restore_main
|
884 |
+
else:
|
885 |
+
# Remove from sys.modules the reference to main_mod we'd
|
886 |
+
# added. Otherwise it will trap references to objects
|
887 |
+
# contained therein.
|
888 |
+
del sys.modules[main_mod_name]
|
889 |
+
|
890 |
+
return stats
|
891 |
+
|
892 |
+
def _run_with_debugger(
|
893 |
+
self, code, code_ns, filename=None, bp_line=None, bp_file=None, local_ns=None
|
894 |
+
):
|
895 |
+
"""
|
896 |
+
Run `code` in debugger with a break point.
|
897 |
+
|
898 |
+
Parameters
|
899 |
+
----------
|
900 |
+
code : str
|
901 |
+
Code to execute.
|
902 |
+
code_ns : dict
|
903 |
+
A namespace in which `code` is executed.
|
904 |
+
filename : str
|
905 |
+
`code` is ran as if it is in `filename`.
|
906 |
+
bp_line : int, optional
|
907 |
+
Line number of the break point.
|
908 |
+
bp_file : str, optional
|
909 |
+
Path to the file in which break point is specified.
|
910 |
+
`filename` is used if not given.
|
911 |
+
local_ns : dict, optional
|
912 |
+
A local namespace in which `code` is executed.
|
913 |
+
|
914 |
+
Raises
|
915 |
+
------
|
916 |
+
UsageError
|
917 |
+
If the break point given by `bp_line` is not valid.
|
918 |
+
|
919 |
+
"""
|
920 |
+
deb = self.shell.InteractiveTB.pdb
|
921 |
+
if not deb:
|
922 |
+
self.shell.InteractiveTB.pdb = self.shell.InteractiveTB.debugger_cls()
|
923 |
+
deb = self.shell.InteractiveTB.pdb
|
924 |
+
|
925 |
+
# deb.checkline() fails if deb.curframe exists but is None; it can
|
926 |
+
# handle it not existing. https://github.com/ipython/ipython/issues/10028
|
927 |
+
if hasattr(deb, 'curframe'):
|
928 |
+
del deb.curframe
|
929 |
+
|
930 |
+
# reset Breakpoint state, which is moronically kept
|
931 |
+
# in a class
|
932 |
+
bdb.Breakpoint.next = 1
|
933 |
+
bdb.Breakpoint.bplist = {}
|
934 |
+
bdb.Breakpoint.bpbynumber = [None]
|
935 |
+
deb.clear_all_breaks()
|
936 |
+
if bp_line is not None:
|
937 |
+
# Set an initial breakpoint to stop execution
|
938 |
+
maxtries = 10
|
939 |
+
bp_file = bp_file or filename
|
940 |
+
checkline = deb.checkline(bp_file, bp_line)
|
941 |
+
if not checkline:
|
942 |
+
for bp in range(bp_line + 1, bp_line + maxtries + 1):
|
943 |
+
if deb.checkline(bp_file, bp):
|
944 |
+
break
|
945 |
+
else:
|
946 |
+
msg = ("\nI failed to find a valid line to set "
|
947 |
+
"a breakpoint\n"
|
948 |
+
"after trying up to line: %s.\n"
|
949 |
+
"Please set a valid breakpoint manually "
|
950 |
+
"with the -b option." % bp)
|
951 |
+
raise UsageError(msg)
|
952 |
+
# if we find a good linenumber, set the breakpoint
|
953 |
+
deb.do_break('%s:%s' % (bp_file, bp_line))
|
954 |
+
|
955 |
+
if filename:
|
956 |
+
# Mimic Pdb._runscript(...)
|
957 |
+
deb._wait_for_mainpyfile = True
|
958 |
+
deb.mainpyfile = deb.canonic(filename)
|
959 |
+
|
960 |
+
# Start file run
|
961 |
+
print("NOTE: Enter 'c' at the %s prompt to continue execution." % deb.prompt)
|
962 |
+
try:
|
963 |
+
if filename:
|
964 |
+
# save filename so it can be used by methods on the deb object
|
965 |
+
deb._exec_filename = filename
|
966 |
+
while True:
|
967 |
+
try:
|
968 |
+
trace = sys.gettrace()
|
969 |
+
deb.run(code, code_ns, local_ns)
|
970 |
+
except Restart:
|
971 |
+
print("Restarting")
|
972 |
+
if filename:
|
973 |
+
deb._wait_for_mainpyfile = True
|
974 |
+
deb.mainpyfile = deb.canonic(filename)
|
975 |
+
continue
|
976 |
+
else:
|
977 |
+
break
|
978 |
+
finally:
|
979 |
+
sys.settrace(trace)
|
980 |
+
|
981 |
+
|
982 |
+
except:
|
983 |
+
etype, value, tb = sys.exc_info()
|
984 |
+
# Skip three frames in the traceback: the %run one,
|
985 |
+
# one inside bdb.py, and the command-line typed by the
|
986 |
+
# user (run by exec in pdb itself).
|
987 |
+
self.shell.InteractiveTB(etype, value, tb, tb_offset=3)
|
988 |
+
|
989 |
+
@staticmethod
|
990 |
+
def _run_with_timing(run, nruns):
|
991 |
+
"""
|
992 |
+
Run function `run` and print timing information.
|
993 |
+
|
994 |
+
Parameters
|
995 |
+
----------
|
996 |
+
run : callable
|
997 |
+
Any callable object which takes no argument.
|
998 |
+
nruns : int
|
999 |
+
Number of times to execute `run`.
|
1000 |
+
|
1001 |
+
"""
|
1002 |
+
twall0 = time.perf_counter()
|
1003 |
+
if nruns == 1:
|
1004 |
+
t0 = clock2()
|
1005 |
+
run()
|
1006 |
+
t1 = clock2()
|
1007 |
+
t_usr = t1[0] - t0[0]
|
1008 |
+
t_sys = t1[1] - t0[1]
|
1009 |
+
print("\nIPython CPU timings (estimated):")
|
1010 |
+
print(" User : %10.2f s." % t_usr)
|
1011 |
+
print(" System : %10.2f s." % t_sys)
|
1012 |
+
else:
|
1013 |
+
runs = range(nruns)
|
1014 |
+
t0 = clock2()
|
1015 |
+
for nr in runs:
|
1016 |
+
run()
|
1017 |
+
t1 = clock2()
|
1018 |
+
t_usr = t1[0] - t0[0]
|
1019 |
+
t_sys = t1[1] - t0[1]
|
1020 |
+
print("\nIPython CPU timings (estimated):")
|
1021 |
+
print("Total runs performed:", nruns)
|
1022 |
+
print(" Times : %10s %10s" % ('Total', 'Per run'))
|
1023 |
+
print(" User : %10.2f s, %10.2f s." % (t_usr, t_usr / nruns))
|
1024 |
+
print(" System : %10.2f s, %10.2f s." % (t_sys, t_sys / nruns))
|
1025 |
+
twall1 = time.perf_counter()
|
1026 |
+
print("Wall time: %10.2f s." % (twall1 - twall0))
|
1027 |
+
|
1028 |
+
@skip_doctest
|
1029 |
+
@no_var_expand
|
1030 |
+
@line_cell_magic
|
1031 |
+
@needs_local_scope
|
1032 |
+
def timeit(self, line='', cell=None, local_ns=None):
|
1033 |
+
"""Time execution of a Python statement or expression
|
1034 |
+
|
1035 |
+
**Usage, in line mode:**
|
1036 |
+
|
1037 |
+
%timeit [-n<N> -r<R> [-t|-c] -q -p<P> -o] statement
|
1038 |
+
|
1039 |
+
**or in cell mode:**
|
1040 |
+
|
1041 |
+
%%timeit [-n<N> -r<R> [-t|-c] -q -p<P> -o] setup_code
|
1042 |
+
|
1043 |
+
code
|
1044 |
+
|
1045 |
+
code...
|
1046 |
+
|
1047 |
+
Time execution of a Python statement or expression using the timeit
|
1048 |
+
module. This function can be used both as a line and cell magic:
|
1049 |
+
|
1050 |
+
- In line mode you can time a single-line statement (though multiple
|
1051 |
+
ones can be chained with using semicolons).
|
1052 |
+
|
1053 |
+
- In cell mode, the statement in the first line is used as setup code
|
1054 |
+
(executed but not timed) and the body of the cell is timed. The cell
|
1055 |
+
body has access to any variables created in the setup code.
|
1056 |
+
|
1057 |
+
Options:
|
1058 |
+
|
1059 |
+
-n<N>: execute the given statement <N> times in a loop. If <N> is not
|
1060 |
+
provided, <N> is determined so as to get sufficient accuracy.
|
1061 |
+
|
1062 |
+
-r<R>: number of repeats <R>, each consisting of <N> loops, and take the
|
1063 |
+
average result.
|
1064 |
+
Default: 7
|
1065 |
+
|
1066 |
+
-t: use time.time to measure the time, which is the default on Unix.
|
1067 |
+
This function measures wall time.
|
1068 |
+
|
1069 |
+
-c: use time.clock to measure the time, which is the default on
|
1070 |
+
Windows and measures wall time. On Unix, resource.getrusage is used
|
1071 |
+
instead and returns the CPU user time.
|
1072 |
+
|
1073 |
+
-p<P>: use a precision of <P> digits to display the timing result.
|
1074 |
+
Default: 3
|
1075 |
+
|
1076 |
+
-q: Quiet, do not print result.
|
1077 |
+
|
1078 |
+
-o: return a TimeitResult that can be stored in a variable to inspect
|
1079 |
+
the result in more details.
|
1080 |
+
|
1081 |
+
.. versionchanged:: 7.3
|
1082 |
+
User variables are no longer expanded,
|
1083 |
+
the magic line is always left unmodified.
|
1084 |
+
|
1085 |
+
Examples
|
1086 |
+
--------
|
1087 |
+
::
|
1088 |
+
|
1089 |
+
In [1]: %timeit pass
|
1090 |
+
8.26 ns ± 0.12 ns per loop (mean ± std. dev. of 7 runs, 100000000 loops each)
|
1091 |
+
|
1092 |
+
In [2]: u = None
|
1093 |
+
|
1094 |
+
In [3]: %timeit u is None
|
1095 |
+
29.9 ns ± 0.643 ns per loop (mean ± std. dev. of 7 runs, 10000000 loops each)
|
1096 |
+
|
1097 |
+
In [4]: %timeit -r 4 u == None
|
1098 |
+
|
1099 |
+
In [5]: import time
|
1100 |
+
|
1101 |
+
In [6]: %timeit -n1 time.sleep(2)
|
1102 |
+
|
1103 |
+
The times reported by %timeit will be slightly higher than those
|
1104 |
+
reported by the timeit.py script when variables are accessed. This is
|
1105 |
+
due to the fact that %timeit executes the statement in the namespace
|
1106 |
+
of the shell, compared with timeit.py, which uses a single setup
|
1107 |
+
statement to import function or create variables. Generally, the bias
|
1108 |
+
does not matter as long as results from timeit.py are not mixed with
|
1109 |
+
those from %timeit."""
|
1110 |
+
|
1111 |
+
opts, stmt = self.parse_options(
|
1112 |
+
line, "n:r:tcp:qo", posix=False, strict=False, preserve_non_opts=True
|
1113 |
+
)
|
1114 |
+
if stmt == "" and cell is None:
|
1115 |
+
return
|
1116 |
+
|
1117 |
+
timefunc = timeit.default_timer
|
1118 |
+
number = int(getattr(opts, "n", 0))
|
1119 |
+
default_repeat = 7 if timeit.default_repeat < 7 else timeit.default_repeat
|
1120 |
+
repeat = int(getattr(opts, "r", default_repeat))
|
1121 |
+
precision = int(getattr(opts, "p", 3))
|
1122 |
+
quiet = 'q' in opts
|
1123 |
+
return_result = 'o' in opts
|
1124 |
+
if hasattr(opts, "t"):
|
1125 |
+
timefunc = time.time
|
1126 |
+
if hasattr(opts, "c"):
|
1127 |
+
timefunc = clock
|
1128 |
+
|
1129 |
+
timer = Timer(timer=timefunc)
|
1130 |
+
# this code has tight coupling to the inner workings of timeit.Timer,
|
1131 |
+
# but is there a better way to achieve that the code stmt has access
|
1132 |
+
# to the shell namespace?
|
1133 |
+
transform = self.shell.transform_cell
|
1134 |
+
|
1135 |
+
if cell is None:
|
1136 |
+
# called as line magic
|
1137 |
+
ast_setup = self.shell.compile.ast_parse("pass")
|
1138 |
+
ast_stmt = self.shell.compile.ast_parse(transform(stmt))
|
1139 |
+
else:
|
1140 |
+
ast_setup = self.shell.compile.ast_parse(transform(stmt))
|
1141 |
+
ast_stmt = self.shell.compile.ast_parse(transform(cell))
|
1142 |
+
|
1143 |
+
ast_setup = self.shell.transform_ast(ast_setup)
|
1144 |
+
ast_stmt = self.shell.transform_ast(ast_stmt)
|
1145 |
+
|
1146 |
+
# Check that these compile to valid Python code *outside* the timer func
|
1147 |
+
# Invalid code may become valid when put inside the function & loop,
|
1148 |
+
# which messes up error messages.
|
1149 |
+
# https://github.com/ipython/ipython/issues/10636
|
1150 |
+
self.shell.compile(ast_setup, "<magic-timeit-setup>", "exec")
|
1151 |
+
self.shell.compile(ast_stmt, "<magic-timeit-stmt>", "exec")
|
1152 |
+
|
1153 |
+
# This codestring is taken from timeit.template - we fill it in as an
|
1154 |
+
# AST, so that we can apply our AST transformations to the user code
|
1155 |
+
# without affecting the timing code.
|
1156 |
+
timeit_ast_template = ast.parse('def inner(_it, _timer):\n'
|
1157 |
+
' setup\n'
|
1158 |
+
' _t0 = _timer()\n'
|
1159 |
+
' for _i in _it:\n'
|
1160 |
+
' stmt\n'
|
1161 |
+
' _t1 = _timer()\n'
|
1162 |
+
' return _t1 - _t0\n')
|
1163 |
+
|
1164 |
+
timeit_ast = TimeitTemplateFiller(ast_setup, ast_stmt).visit(timeit_ast_template)
|
1165 |
+
timeit_ast = ast.fix_missing_locations(timeit_ast)
|
1166 |
+
|
1167 |
+
# Track compilation time so it can be reported if too long
|
1168 |
+
# Minimum time above which compilation time will be reported
|
1169 |
+
tc_min = 0.1
|
1170 |
+
|
1171 |
+
t0 = clock()
|
1172 |
+
code = self.shell.compile(timeit_ast, "<magic-timeit>", "exec")
|
1173 |
+
tc = clock()-t0
|
1174 |
+
|
1175 |
+
ns = {}
|
1176 |
+
glob = self.shell.user_ns
|
1177 |
+
# handles global vars with same name as local vars. We store them in conflict_globs.
|
1178 |
+
conflict_globs = {}
|
1179 |
+
if local_ns and cell is None:
|
1180 |
+
for var_name, var_val in glob.items():
|
1181 |
+
if var_name in local_ns:
|
1182 |
+
conflict_globs[var_name] = var_val
|
1183 |
+
glob.update(local_ns)
|
1184 |
+
|
1185 |
+
exec(code, glob, ns)
|
1186 |
+
timer.inner = ns["inner"]
|
1187 |
+
|
1188 |
+
# This is used to check if there is a huge difference between the
|
1189 |
+
# best and worst timings.
|
1190 |
+
# Issue: https://github.com/ipython/ipython/issues/6471
|
1191 |
+
if number == 0:
|
1192 |
+
# determine number so that 0.2 <= total time < 2.0
|
1193 |
+
for index in range(0, 10):
|
1194 |
+
number = 10 ** index
|
1195 |
+
time_number = timer.timeit(number)
|
1196 |
+
if time_number >= 0.2:
|
1197 |
+
break
|
1198 |
+
|
1199 |
+
all_runs = timer.repeat(repeat, number)
|
1200 |
+
best = min(all_runs) / number
|
1201 |
+
worst = max(all_runs) / number
|
1202 |
+
timeit_result = TimeitResult(number, repeat, best, worst, all_runs, tc, precision)
|
1203 |
+
|
1204 |
+
# Restore global vars from conflict_globs
|
1205 |
+
if conflict_globs:
|
1206 |
+
glob.update(conflict_globs)
|
1207 |
+
|
1208 |
+
if not quiet :
|
1209 |
+
# Check best timing is greater than zero to avoid a
|
1210 |
+
# ZeroDivisionError.
|
1211 |
+
# In cases where the slowest timing is lesser than a microsecond
|
1212 |
+
# we assume that it does not really matter if the fastest
|
1213 |
+
# timing is 4 times faster than the slowest timing or not.
|
1214 |
+
if worst > 4 * best and best > 0 and worst > 1e-6:
|
1215 |
+
print("The slowest run took %0.2f times longer than the "
|
1216 |
+
"fastest. This could mean that an intermediate result "
|
1217 |
+
"is being cached." % (worst / best))
|
1218 |
+
|
1219 |
+
print( timeit_result )
|
1220 |
+
|
1221 |
+
if tc > tc_min:
|
1222 |
+
print("Compiler time: %.2f s" % tc)
|
1223 |
+
if return_result:
|
1224 |
+
return timeit_result
|
1225 |
+
|
1226 |
+
@skip_doctest
|
1227 |
+
@no_var_expand
|
1228 |
+
@needs_local_scope
|
1229 |
+
@line_cell_magic
|
1230 |
+
@output_can_be_silenced
|
1231 |
+
def time(self,line='', cell=None, local_ns=None):
|
1232 |
+
"""Time execution of a Python statement or expression.
|
1233 |
+
|
1234 |
+
The CPU and wall clock times are printed, and the value of the
|
1235 |
+
expression (if any) is returned. Note that under Win32, system time
|
1236 |
+
is always reported as 0, since it can not be measured.
|
1237 |
+
|
1238 |
+
This function can be used both as a line and cell magic:
|
1239 |
+
|
1240 |
+
- In line mode you can time a single-line statement (though multiple
|
1241 |
+
ones can be chained with using semicolons).
|
1242 |
+
|
1243 |
+
- In cell mode, you can time the cell body (a directly
|
1244 |
+
following statement raises an error).
|
1245 |
+
|
1246 |
+
This function provides very basic timing functionality. Use the timeit
|
1247 |
+
magic for more control over the measurement.
|
1248 |
+
|
1249 |
+
.. versionchanged:: 7.3
|
1250 |
+
User variables are no longer expanded,
|
1251 |
+
the magic line is always left unmodified.
|
1252 |
+
|
1253 |
+
Examples
|
1254 |
+
--------
|
1255 |
+
::
|
1256 |
+
|
1257 |
+
In [1]: %time 2**128
|
1258 |
+
CPU times: user 0.00 s, sys: 0.00 s, total: 0.00 s
|
1259 |
+
Wall time: 0.00
|
1260 |
+
Out[1]: 340282366920938463463374607431768211456L
|
1261 |
+
|
1262 |
+
In [2]: n = 1000000
|
1263 |
+
|
1264 |
+
In [3]: %time sum(range(n))
|
1265 |
+
CPU times: user 1.20 s, sys: 0.05 s, total: 1.25 s
|
1266 |
+
Wall time: 1.37
|
1267 |
+
Out[3]: 499999500000L
|
1268 |
+
|
1269 |
+
In [4]: %time print('hello world')
|
1270 |
+
hello world
|
1271 |
+
CPU times: user 0.00 s, sys: 0.00 s, total: 0.00 s
|
1272 |
+
Wall time: 0.00
|
1273 |
+
|
1274 |
+
.. note::
|
1275 |
+
The time needed by Python to compile the given expression will be
|
1276 |
+
reported if it is more than 0.1s.
|
1277 |
+
|
1278 |
+
In the example below, the actual exponentiation is done by Python
|
1279 |
+
at compilation time, so while the expression can take a noticeable
|
1280 |
+
amount of time to compute, that time is purely due to the
|
1281 |
+
compilation::
|
1282 |
+
|
1283 |
+
In [5]: %time 3**9999;
|
1284 |
+
CPU times: user 0.00 s, sys: 0.00 s, total: 0.00 s
|
1285 |
+
Wall time: 0.00 s
|
1286 |
+
|
1287 |
+
In [6]: %time 3**999999;
|
1288 |
+
CPU times: user 0.00 s, sys: 0.00 s, total: 0.00 s
|
1289 |
+
Wall time: 0.00 s
|
1290 |
+
Compiler : 0.78 s
|
1291 |
+
"""
|
1292 |
+
# fail immediately if the given expression can't be compiled
|
1293 |
+
|
1294 |
+
if line and cell:
|
1295 |
+
raise UsageError("Can't use statement directly after '%%time'!")
|
1296 |
+
|
1297 |
+
if cell:
|
1298 |
+
expr = self.shell.transform_cell(cell)
|
1299 |
+
else:
|
1300 |
+
expr = self.shell.transform_cell(line)
|
1301 |
+
|
1302 |
+
# Minimum time above which parse time will be reported
|
1303 |
+
tp_min = 0.1
|
1304 |
+
|
1305 |
+
t0 = clock()
|
1306 |
+
expr_ast = self.shell.compile.ast_parse(expr)
|
1307 |
+
tp = clock()-t0
|
1308 |
+
|
1309 |
+
# Apply AST transformations
|
1310 |
+
expr_ast = self.shell.transform_ast(expr_ast)
|
1311 |
+
|
1312 |
+
# Minimum time above which compilation time will be reported
|
1313 |
+
tc_min = 0.1
|
1314 |
+
|
1315 |
+
expr_val=None
|
1316 |
+
if len(expr_ast.body)==1 and isinstance(expr_ast.body[0], ast.Expr):
|
1317 |
+
mode = 'eval'
|
1318 |
+
source = '<timed eval>'
|
1319 |
+
expr_ast = ast.Expression(expr_ast.body[0].value)
|
1320 |
+
else:
|
1321 |
+
mode = 'exec'
|
1322 |
+
source = '<timed exec>'
|
1323 |
+
# multi-line %%time case
|
1324 |
+
if len(expr_ast.body) > 1 and isinstance(expr_ast.body[-1], ast.Expr):
|
1325 |
+
expr_val= expr_ast.body[-1]
|
1326 |
+
expr_ast = expr_ast.body[:-1]
|
1327 |
+
expr_ast = Module(expr_ast, [])
|
1328 |
+
expr_val = ast.Expression(expr_val.value)
|
1329 |
+
|
1330 |
+
t0 = clock()
|
1331 |
+
code = self.shell.compile(expr_ast, source, mode)
|
1332 |
+
tc = clock()-t0
|
1333 |
+
|
1334 |
+
# skew measurement as little as possible
|
1335 |
+
glob = self.shell.user_ns
|
1336 |
+
wtime = time.time
|
1337 |
+
# time execution
|
1338 |
+
wall_st = wtime()
|
1339 |
+
if mode=='eval':
|
1340 |
+
st = clock2()
|
1341 |
+
try:
|
1342 |
+
out = eval(code, glob, local_ns)
|
1343 |
+
except:
|
1344 |
+
self.shell.showtraceback()
|
1345 |
+
return
|
1346 |
+
end = clock2()
|
1347 |
+
else:
|
1348 |
+
st = clock2()
|
1349 |
+
try:
|
1350 |
+
exec(code, glob, local_ns)
|
1351 |
+
out=None
|
1352 |
+
# multi-line %%time case
|
1353 |
+
if expr_val is not None:
|
1354 |
+
code_2 = self.shell.compile(expr_val, source, 'eval')
|
1355 |
+
out = eval(code_2, glob, local_ns)
|
1356 |
+
except:
|
1357 |
+
self.shell.showtraceback()
|
1358 |
+
return
|
1359 |
+
end = clock2()
|
1360 |
+
|
1361 |
+
wall_end = wtime()
|
1362 |
+
# Compute actual times and report
|
1363 |
+
wall_time = wall_end - wall_st
|
1364 |
+
cpu_user = end[0] - st[0]
|
1365 |
+
cpu_sys = end[1] - st[1]
|
1366 |
+
cpu_tot = cpu_user + cpu_sys
|
1367 |
+
# On windows cpu_sys is always zero, so only total is displayed
|
1368 |
+
if sys.platform != "win32":
|
1369 |
+
print(
|
1370 |
+
f"CPU times: user {_format_time(cpu_user)}, sys: {_format_time(cpu_sys)}, total: {_format_time(cpu_tot)}"
|
1371 |
+
)
|
1372 |
+
else:
|
1373 |
+
print(f"CPU times: total: {_format_time(cpu_tot)}")
|
1374 |
+
print(f"Wall time: {_format_time(wall_time)}")
|
1375 |
+
if tc > tc_min:
|
1376 |
+
print(f"Compiler : {_format_time(tc)}")
|
1377 |
+
if tp > tp_min:
|
1378 |
+
print(f"Parser : {_format_time(tp)}")
|
1379 |
+
return out
|
1380 |
+
|
1381 |
+
@skip_doctest
|
1382 |
+
@line_magic
|
1383 |
+
def macro(self, parameter_s=''):
|
1384 |
+
"""Define a macro for future re-execution. It accepts ranges of history,
|
1385 |
+
filenames or string objects.
|
1386 |
+
|
1387 |
+
Usage:\\
|
1388 |
+
%macro [options] name n1-n2 n3-n4 ... n5 .. n6 ...
|
1389 |
+
|
1390 |
+
Options:
|
1391 |
+
|
1392 |
+
-r: use 'raw' input. By default, the 'processed' history is used,
|
1393 |
+
so that magics are loaded in their transformed version to valid
|
1394 |
+
Python. If this option is given, the raw input as typed at the
|
1395 |
+
command line is used instead.
|
1396 |
+
|
1397 |
+
-q: quiet macro definition. By default, a tag line is printed
|
1398 |
+
to indicate the macro has been created, and then the contents of
|
1399 |
+
the macro are printed. If this option is given, then no printout
|
1400 |
+
is produced once the macro is created.
|
1401 |
+
|
1402 |
+
This will define a global variable called `name` which is a string
|
1403 |
+
made of joining the slices and lines you specify (n1,n2,... numbers
|
1404 |
+
above) from your input history into a single string. This variable
|
1405 |
+
acts like an automatic function which re-executes those lines as if
|
1406 |
+
you had typed them. You just type 'name' at the prompt and the code
|
1407 |
+
executes.
|
1408 |
+
|
1409 |
+
The syntax for indicating input ranges is described in %history.
|
1410 |
+
|
1411 |
+
Note: as a 'hidden' feature, you can also use traditional python slice
|
1412 |
+
notation, where N:M means numbers N through M-1.
|
1413 |
+
|
1414 |
+
For example, if your history contains (print using %hist -n )::
|
1415 |
+
|
1416 |
+
44: x=1
|
1417 |
+
45: y=3
|
1418 |
+
46: z=x+y
|
1419 |
+
47: print(x)
|
1420 |
+
48: a=5
|
1421 |
+
49: print('x',x,'y',y)
|
1422 |
+
|
1423 |
+
you can create a macro with lines 44 through 47 (included) and line 49
|
1424 |
+
called my_macro with::
|
1425 |
+
|
1426 |
+
In [55]: %macro my_macro 44-47 49
|
1427 |
+
|
1428 |
+
Now, typing `my_macro` (without quotes) will re-execute all this code
|
1429 |
+
in one pass.
|
1430 |
+
|
1431 |
+
You don't need to give the line-numbers in order, and any given line
|
1432 |
+
number can appear multiple times. You can assemble macros with any
|
1433 |
+
lines from your input history in any order.
|
1434 |
+
|
1435 |
+
The macro is a simple object which holds its value in an attribute,
|
1436 |
+
but IPython's display system checks for macros and executes them as
|
1437 |
+
code instead of printing them when you type their name.
|
1438 |
+
|
1439 |
+
You can view a macro's contents by explicitly printing it with::
|
1440 |
+
|
1441 |
+
print(macro_name)
|
1442 |
+
|
1443 |
+
"""
|
1444 |
+
opts,args = self.parse_options(parameter_s,'rq',mode='list')
|
1445 |
+
if not args: # List existing macros
|
1446 |
+
return sorted(k for k,v in self.shell.user_ns.items() if isinstance(v, Macro))
|
1447 |
+
if len(args) == 1:
|
1448 |
+
raise UsageError(
|
1449 |
+
"%macro insufficient args; usage '%macro name n1-n2 n3-4...")
|
1450 |
+
name, codefrom = args[0], " ".join(args[1:])
|
1451 |
+
|
1452 |
+
# print('rng',ranges) # dbg
|
1453 |
+
try:
|
1454 |
+
lines = self.shell.find_user_code(codefrom, 'r' in opts)
|
1455 |
+
except (ValueError, TypeError) as e:
|
1456 |
+
print(e.args[0])
|
1457 |
+
return
|
1458 |
+
macro = Macro(lines)
|
1459 |
+
self.shell.define_macro(name, macro)
|
1460 |
+
if not ( 'q' in opts) :
|
1461 |
+
print('Macro `%s` created. To execute, type its name (without quotes).' % name)
|
1462 |
+
print('=== Macro contents: ===')
|
1463 |
+
print(macro, end=' ')
|
1464 |
+
|
1465 |
+
@magic_arguments.magic_arguments()
|
1466 |
+
@magic_arguments.argument('output', type=str, default='', nargs='?',
|
1467 |
+
help="""The name of the variable in which to store output.
|
1468 |
+
This is a utils.io.CapturedIO object with stdout/err attributes
|
1469 |
+
for the text of the captured output.
|
1470 |
+
|
1471 |
+
CapturedOutput also has a show() method for displaying the output,
|
1472 |
+
and __call__ as well, so you can use that to quickly display the
|
1473 |
+
output.
|
1474 |
+
|
1475 |
+
If unspecified, captured output is discarded.
|
1476 |
+
"""
|
1477 |
+
)
|
1478 |
+
@magic_arguments.argument('--no-stderr', action="store_true",
|
1479 |
+
help="""Don't capture stderr."""
|
1480 |
+
)
|
1481 |
+
@magic_arguments.argument('--no-stdout', action="store_true",
|
1482 |
+
help="""Don't capture stdout."""
|
1483 |
+
)
|
1484 |
+
@magic_arguments.argument('--no-display', action="store_true",
|
1485 |
+
help="""Don't capture IPython's rich display."""
|
1486 |
+
)
|
1487 |
+
@cell_magic
|
1488 |
+
def capture(self, line, cell):
|
1489 |
+
"""run the cell, capturing stdout, stderr, and IPython's rich display() calls."""
|
1490 |
+
args = magic_arguments.parse_argstring(self.capture, line)
|
1491 |
+
out = not args.no_stdout
|
1492 |
+
err = not args.no_stderr
|
1493 |
+
disp = not args.no_display
|
1494 |
+
with capture_output(out, err, disp) as io:
|
1495 |
+
self.shell.run_cell(cell)
|
1496 |
+
if DisplayHook.semicolon_at_end_of_expression(cell):
|
1497 |
+
if args.output in self.shell.user_ns:
|
1498 |
+
del self.shell.user_ns[args.output]
|
1499 |
+
elif args.output:
|
1500 |
+
self.shell.user_ns[args.output] = io
|
1501 |
+
|
1502 |
+
@skip_doctest
|
1503 |
+
@magic_arguments.magic_arguments()
|
1504 |
+
@magic_arguments.argument("name", type=str, default="default", nargs="?")
|
1505 |
+
@magic_arguments.argument(
|
1506 |
+
"--remove", action="store_true", help="remove the current transformer"
|
1507 |
+
)
|
1508 |
+
@magic_arguments.argument(
|
1509 |
+
"--list", action="store_true", help="list existing transformers name"
|
1510 |
+
)
|
1511 |
+
@magic_arguments.argument(
|
1512 |
+
"--list-all",
|
1513 |
+
action="store_true",
|
1514 |
+
help="list existing transformers name and code template",
|
1515 |
+
)
|
1516 |
+
@line_cell_magic
|
1517 |
+
def code_wrap(self, line, cell=None):
|
1518 |
+
"""
|
1519 |
+
Simple magic to quickly define a code transformer for all IPython's future input.
|
1520 |
+
|
1521 |
+
``__code__`` and ``__ret__`` are special variable that represent the code to run
|
1522 |
+
and the value of the last expression of ``__code__`` respectively.
|
1523 |
+
|
1524 |
+
Examples
|
1525 |
+
--------
|
1526 |
+
|
1527 |
+
.. ipython::
|
1528 |
+
|
1529 |
+
In [1]: %%code_wrap before_after
|
1530 |
+
...: print('before')
|
1531 |
+
...: __code__
|
1532 |
+
...: print('after')
|
1533 |
+
...: __ret__
|
1534 |
+
|
1535 |
+
|
1536 |
+
In [2]: 1
|
1537 |
+
before
|
1538 |
+
after
|
1539 |
+
Out[2]: 1
|
1540 |
+
|
1541 |
+
In [3]: %code_wrap --list
|
1542 |
+
before_after
|
1543 |
+
|
1544 |
+
In [4]: %code_wrap --list-all
|
1545 |
+
before_after :
|
1546 |
+
print('before')
|
1547 |
+
__code__
|
1548 |
+
print('after')
|
1549 |
+
__ret__
|
1550 |
+
|
1551 |
+
In [5]: %code_wrap --remove before_after
|
1552 |
+
|
1553 |
+
"""
|
1554 |
+
args = magic_arguments.parse_argstring(self.code_wrap, line)
|
1555 |
+
|
1556 |
+
if args.list:
|
1557 |
+
for name in self._transformers.keys():
|
1558 |
+
print(name)
|
1559 |
+
return
|
1560 |
+
if args.list_all:
|
1561 |
+
for name, _t in self._transformers.items():
|
1562 |
+
print(name, ":")
|
1563 |
+
print(indent(ast.unparse(_t.template), " "))
|
1564 |
+
print()
|
1565 |
+
return
|
1566 |
+
|
1567 |
+
to_remove = self._transformers.pop(args.name, None)
|
1568 |
+
if to_remove in self.shell.ast_transformers:
|
1569 |
+
self.shell.ast_transformers.remove(to_remove)
|
1570 |
+
if cell is None or args.remove:
|
1571 |
+
return
|
1572 |
+
|
1573 |
+
_trs = ReplaceCodeTransformer(ast.parse(cell))
|
1574 |
+
|
1575 |
+
self._transformers[args.name] = _trs
|
1576 |
+
self.shell.ast_transformers.append(_trs)
|
1577 |
+
|
1578 |
+
|
1579 |
+
def parse_breakpoint(text, current_file):
|
1580 |
+
'''Returns (file, line) for file:line and (current_file, line) for line'''
|
1581 |
+
colon = text.find(':')
|
1582 |
+
if colon == -1:
|
1583 |
+
return current_file, int(text)
|
1584 |
+
else:
|
1585 |
+
return text[:colon], int(text[colon+1:])
|
1586 |
+
|
1587 |
+
def _format_time(timespan, precision=3):
|
1588 |
+
"""Formats the timespan in a human readable form"""
|
1589 |
+
|
1590 |
+
if timespan >= 60.0:
|
1591 |
+
# we have more than a minute, format that in a human readable form
|
1592 |
+
# Idea from http://snipplr.com/view/5713/
|
1593 |
+
parts = [("d", 60*60*24),("h", 60*60),("min", 60), ("s", 1)]
|
1594 |
+
time = []
|
1595 |
+
leftover = timespan
|
1596 |
+
for suffix, length in parts:
|
1597 |
+
value = int(leftover / length)
|
1598 |
+
if value > 0:
|
1599 |
+
leftover = leftover % length
|
1600 |
+
time.append(u'%s%s' % (str(value), suffix))
|
1601 |
+
if leftover < 1:
|
1602 |
+
break
|
1603 |
+
return " ".join(time)
|
1604 |
+
|
1605 |
+
|
1606 |
+
# Unfortunately characters outside of range(128) can cause problems in
|
1607 |
+
# certain terminals.
|
1608 |
+
# See bug: https://bugs.launchpad.net/ipython/+bug/348466
|
1609 |
+
# Try to prevent crashes by being more secure than it needs to
|
1610 |
+
# E.g. eclipse is able to print a µ, but has no sys.stdout.encoding set.
|
1611 |
+
units = ["s", "ms", "us", "ns"] # the safe value
|
1612 |
+
if hasattr(sys.stdout, "encoding") and sys.stdout.encoding:
|
1613 |
+
try:
|
1614 |
+
"μ".encode(sys.stdout.encoding)
|
1615 |
+
units = ["s", "ms", "μs", "ns"]
|
1616 |
+
except:
|
1617 |
+
pass
|
1618 |
+
scaling = [1, 1e3, 1e6, 1e9]
|
1619 |
+
|
1620 |
+
if timespan > 0.0:
|
1621 |
+
order = min(-int(math.floor(math.log10(timespan)) // 3), 3)
|
1622 |
+
else:
|
1623 |
+
order = 3
|
1624 |
+
return "%.*g %s" % (precision, timespan * scaling[order], units[order])
|