diff --git a/taxcalc/policy.py b/taxcalc/policy.py index fbee0bfe9..67b32ec9f 100644 --- a/taxcalc/policy.py +++ b/taxcalc/policy.py @@ -204,20 +204,20 @@ def read_json_reform_text(text_string): implement_reform(reform_dict) method (see below). """ # strip out //-comments without changing line numbers - json_without_comments = re.sub('//.*', '', text_string) + json_without_comments = re.sub('//.*', ' ', text_string) # convert JSON text into a dictionary with year skeys as strings try: reform_dict_raw = json.loads(json_without_comments) - except ValueError: - msg = 'Policy reform text contains invalid JSON' - txt = ('\nTO FIND FIRST JSON SYNTAX ERROR,\n' - 'COPY TEXT BETWEEN LINES AND ' - 'PASTE INTO BOX AT jsonlint.com\n') - line = '----------------------------------------------------------' - txt += line + '\n' - txt += json_without_comments.strip() + '\n' - txt += line + '\n' - raise ValueError(msg + txt) + except ValueError as valerr: + msg = 'Policy reform text below contains invalid JSON:\n' + msg += str(valerr) + '\n' + msg += 'The invalid JSON reform text is between the lines:\n' + line = '---------|---------|---------|' + line += '---------|---------|---------|' + msg += line + '\n' + msg += json_without_comments + '\n' + msg += line + '\n' + raise ValueError(msg) return Policy.convert_reform_dictionary(reform_dict_raw) def implement_reform(self, reform): diff --git a/taxcalc/tests/test_utils.py b/taxcalc/tests/test_utils.py index 33d1958a8..99b0e2a8b 100644 --- a/taxcalc/tests/test_utils.py +++ b/taxcalc/tests/test_utils.py @@ -536,6 +536,20 @@ def test_mtr_plot(records_2009): plot = mtr_plot(source) +def test_mtr_plot_force_no_bokeh(records_2009): + import taxcalc + taxcalc.utils.BOKEH_AVAILABLE = False + pol = Policy() + behv = Behavior() + calc = Calculator(policy=pol, records=records_2009, behavior=behv) + calc.calc_all() + source = get_mtr_data(calc, calc, weights=wage_weighted, + complex_weight=True) + with pytest.raises(RuntimeError): + plot = mtr_plot(source) + taxcalc.utils.BOKEH_AVAILABLE = True + + def test_multiyear_diagnostic_table(records_2009): pol = Policy() behv = Behavior() diff --git a/taxcalc/utils.py b/taxcalc/utils.py index 9b62ee716..749911165 100644 --- a/taxcalc/utils.py +++ b/taxcalc/utils.py @@ -3,14 +3,16 @@ import pandas as pd from pandas import DataFrame from collections import defaultdict, OrderedDict -from bokeh.models import Plot, Range1d, ImageURL, DataRange1d -from bokeh.embed import components -from bokeh.layouts import layout -from bokeh.palettes import Blues4, Reds4 -from bokeh.plotting import figure, hplot, vplot, output_file, show -from bokeh.models import (ColumnDataSource, LogAxis, LinearAxis, Rect, - FactorRange, CategoricalAxis, Line, Text, Square, - HoverTool) + +try: + import bokeh + BOKEH_AVAILABLE = True + from bokeh.palettes import Blues4, Reds4 + from bokeh.plotting import figure + +except ImportError: + BOKEH_AVAILABLE = False +# STATS_COLUMNS = ['_expanded_income', 'c00100', '_standard', 'c04470', 'c04600', 'c04800', 'c05200', 'c62100', 'c09600', @@ -733,6 +735,26 @@ def get_mtr_data(calcX, calcY, weights, MARS='ALL', return merged +def requires_bokeh(fn): + """ + Decorator for functions that require bokeh. + If BOKEH_AVAILABLE=True, this does nothing. + IF BOKEH_AVAILABEL=False, we raise an exception and tell the caller + that they must install bokeh in order to use the function. + """ + def wrapped_f(*args, **kwargs): + if BOKEH_AVAILABLE: + return fn(*args, **kwargs) + else: + msg = ("`bokeh` is not installed. Please install " + "`bokeh` to use this package (`conda install " + "bokeh`)") + raise RuntimeError(msg) + + return wrapped_f + + +@requires_bokeh def mtr_plot(source, xlab='Percentile', ylab='Avg. MTR', title='MTR plot', plot_width=425, plot_height=250, loc='top_left'): """