def test_update_geojson(): with app.test_client() as c: with ExitStack() as stack: custom_geojson_data = [] build_data_inst({c.port: None}) stack.enter_context( mock.patch( "dtale.dash_application.custom_geojson.CUSTOM_GEOJSON", custom_geojson_data, )) params = { "output": "..output-geojson-upload.children...geojson-dropdown.options..", "changedPropIds": ["upload-geojson.content"], "inputs": [{ "id": "upload-geojson", "property": "content", "value": build_geojson_data(), }], "state": [{ "id": "upload-geojson", "property": "filename", "value": "USA.json", }], } response = c.post("/dtale/charts/_dash-update-component", json=params) resp_data = response.get_json()["response"] assert resp_data["output-geojson-upload"][ "children"] == "USA uploaded!" assert resp_data["geojson-dropdown"]["options"] == [{ "label": "USA", "value": "USA" }] assert len(custom_geojson_data) == 1 assert custom_geojson_data[0]["key"] == "USA" response = c.post("/dtale/charts/_dash-update-component", json=params) resp_data = response.get_json()["response"] assert resp_data["output-geojson-upload"][ "children"] == "USA2 uploaded!" assert resp_data["geojson-dropdown"]["options"][-1] == { "label": "USA2", "value": "USA2", } assert len(custom_geojson_data) == 2 assert custom_geojson_data[-1]["key"] == "USA2" africa_fname = "/../../../".join([ os.path.dirname(__file__), "dtale/static/maps/africa_110m.json" ]) africa_data = build_geojson_data(fname=africa_fname) params["inputs"][0]["value"] = africa_data params["state"][0]["value"] = "africa_110m.json" response = c.post("/dtale/charts/_dash-update-component", json=params) resp_data = response.get_json()["response"] assert (resp_data["output-geojson-upload"]["children"] == "africa_110m uploaded!") assert resp_data["geojson-dropdown"]["options"][-1] == { "label": "africa_110m", "value": "africa_110m", } assert len(custom_geojson_data) == 3 assert custom_geojson_data[-1]["key"] == "africa_110m" assert custom_geojson_data[-1].get("properties") is None params["state"][0]["value"] = None response = c.post("/dtale/charts/_dash_update-component", json=params) assert response.status_code == 405 params["state"][0]["value"] = "USA.json" params["inputs"][0]["value"] = None response = c.post("/dtale/charts/_dash-update-component", json=params) resp_data = response.get_json()["response"] assert (resp_data["output-geojson-upload"]["children"] == "'NoneType' object has no attribute 'split'")
def test_view(unittest): from dtale.views import startup import dtale.global_state as global_state global_state.clear_store() with app.test_client() as c: global_state.new_data_inst(c.port) startup(URL, data=xarray_data(), data_id=c.port) assert global_state.get_dataset(c.port) is not None response = c.get("/dtale/main/{}".format(c.port)) assert 'input id="xarray" value="True"' not in str(response.data) assert 'input id="xarray_dim" value="{}"' not in str(response.data) resp = c.get("/dtale/code-export/{}".format(c.port)) assert resp.status_code == 200 response_data = resp.json assert response_data["success"] resp = c.get("/dtale/xarray-coordinates/{}".format(c.port)) response_data = resp.json expected = [ { "count": 3, "dtype": "str64" if PY3 else "string16", "name": "location", }, { "count": 731, "dtype": "datetime64[ns]", "name": "time" }, ] unittest.assertEqual( sorted(response_data["data"], key=lambda c: c["name"]), expected) resp = c.get("/dtale/xarray-dimension-values/{}/location".format( c.port)) response_data = resp.json unittest.assertEqual( response_data["data"], [{ "value": "IA" }, { "value": "IN" }, { "value": "IL" }], ) resp = c.get( "/dtale/update-xarray-selection/{}".format(c.port), query_string=dict(selection=json.dumps(dict(location="IA"))), ) assert resp.status_code == 200 assert list(global_state.get_data(c.port).location.unique()) == ["IA"] assert global_state.get_dataset_dim(c.port)["location"] == "IA" resp = c.get( "/dtale/update-xarray-selection/{}".format(c.port), query_string=dict(selection=json.dumps(dict())), ) assert resp.status_code == 200 assert list(global_state.get_data(c.port).location.unique()) == [ "IA", "IN", "IL", ] resp = c.get("/dtale/code-export/{}".format(c.port)) assert resp.status_code == 200 response_data = resp.json assert response_data["success"] with app.test_client() as c: zero_dim_xarray = xarray_data().sel(location="IA", time="2000-01-01") startup(URL, data=zero_dim_xarray, data_id=c.port) assert global_state.get_dataset(c.port) is not None response = c.get("/dtale/main/{}".format(c.port)) assert 'input id="xarray" value="True"' not in str(response.data) assert 'input id="xarray_dim" value="{}"' not in str(response.data)
def test_get_column_analysis(unittest, test_data): import dtale.views as views with app.test_client() as c: with ExitStack(): build_data_inst({c.port: test_data}) build_dtypes({c.port: views.build_dtypes_state(test_data)}) build_settings({c.port: {}}) response = c.get( "/dtale/column-analysis/{}".format(c.port), query_string=dict(col="foo", filtered="true"), ) response_data = json.loads(response.data) expected = dict( labels=[ "0.6", "0.6", "0.7", "0.7", "0.8", "0.8", "0.9", "0.9", "0.9", "1.0", "1.1", "1.1", "1.1", "1.2", "1.2", "1.3", "1.4", "1.4", "1.5", "1.5", ], data=[ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 50, 0, 0, 0, 0, 0, 0, 0, 0, 0 ], desc={ "count": "50", "std": "0", "min": "1", "max": "1", "50%": "1", "25%": "1", "75%": "1", "mean": "1", "missing_ct": "0", "missing_pct": 0.0, "total_count": "50", "kurt": 0.0, "skew": 0.0, }, chart_type="histogram", dtype="int64", query="", timestamp=response_data["timestamp"], ) unittest.assertEqual( { k: v for k, v in response_data.items() if k not in ["code", "cols"] }, expected, "should return 20-bin histogram for foo", ) unittest.assertEqual(response_data["code"], HISTOGRAM_CODE) response = c.get( "/dtale/column-analysis/{}".format(c.port), query_string=dict(col="foo", bins=5, filtered="true"), ) response_data = json.loads(response.data) expected = dict( labels=["0.7", "0.9", "1.1", "1.3", "1.5"], data=[0, 0, 50, 0, 0], desc={ "count": "50", "std": "0", "min": "1", "max": "1", "50%": "1", "25%": "1", "75%": "1", "mean": "1", "missing_ct": "0", "missing_pct": 0.0, "total_count": "50", "kurt": 0.0, "skew": 0.0, }, chart_type="histogram", dtype="int64", query="", timestamp=response_data["timestamp"], ) unittest.assertEqual( { k: v for k, v in response_data.items() if k not in ["code", "cols"] }, expected, "should return 5-bin histogram for foo", ) response = c.get( "/dtale/column-analysis/{}".format(c.port), query_string=dict(col="foo", bins=5, target="baz", filtered="true"), ) response_data = json.loads(response.data) assert len(response_data["targets"]) assert response_data["targets"][0]["target"] == "baz" global_state.set_settings(c.port, dict(query="security_id > 10")) response = c.get( "/dtale/column-analysis/{}".format(c.port), query_string=dict(col="foo", bins=5, filtered="true"), ) response_data = json.loads(response.data) expected = dict( labels=["0.7", "0.9", "1.1", "1.3", "1.5"], data=[0, 0, 39, 0, 0], desc={ "count": "39", "std": "0", "min": "1", "max": "1", "50%": "1", "25%": "1", "75%": "1", "mean": "1", "missing_ct": "0", "missing_pct": 0.0, "total_count": "39", "kurt": 0.0, "skew": 0.0, }, chart_type="histogram", dtype="int64", query="security_id > 10", timestamp=response_data["timestamp"], ) unittest.assertEqual( { k: v for k, v in response_data.items() if k not in ["code", "cols"] }, expected, "should return a filtered 5-bin histogram for foo", ) global_state.set_settings(c.port, {}) response = c.get( "/dtale/column-analysis/{}".format(c.port), query_string=dict(col="foo", type="value_counts", top=2, filtered="true"), ) response_data = json.loads(response.data) assert response_data["chart_type"] == "value_counts" response = c.get( "/dtale/column-analysis/{}".format(c.port), query_string=dict( col="foo", type="value_counts", ordinalCol="bar", ordinalAgg="mean", filtered="true", ), ) response_data = json.loads(response.data) assert "ordinal" in response_data response = c.get( "/dtale/column-analysis/{}".format(c.port), query_string=dict( col="foo", type="value_counts", ordinalCol="bar", ordinalAgg="pctsum", filtered="true", ), ) response_data = json.loads(response.data) assert "ordinal" in response_data response = c.get( "/dtale/column-analysis/{}".format(c.port), query_string=dict( col="bar", type="categories", categoryCol="foo", categoryAgg="mean", filtered="true", ), ) response_data = json.loads(response.data) assert "count" in response_data response = c.get( "/dtale/column-analysis/{}".format(c.port), query_string=dict( col="bar", type="categories", categoryCol="foo", categoryAgg="pctsum", filtered="true", ), ) response_data = json.loads(response.data) assert "count" in response_data with app.test_client() as c: with ExitStack() as stack: build_data_inst({c.port: test_data}) stack.enter_context( mock.patch( "numpy.histogram", mock.Mock(side_effect=Exception("histogram failure")), )) response = c.get( "/dtale/column-analysis/{}".format(c.port), query_string=dict(col="foo", filtered="true"), ) response_data = json.loads(response.data) unittest.assertEqual( response_data["error"], "histogram failure", "should handle histogram exception", )
def test_save_chart(unittest): from dtale.dash_application.saved_charts import SAVED_CHART_IDS config_ids = "...".join( ["saved-chart-config-{}.data".format(i) for i in SAVED_CHART_IDS] ) delete_ids = "...".join( ["saved-deletes-{}.data".format(i) for i in SAVED_CHART_IDS] ) delete_clicks = [ dict(id="delete-saved-btn-{}".format(i), property="n_clicks", value=None) for i in SAVED_CHART_IDS ] configs = [ dict(id="saved-chart-config-{}".format(i), property="data", value=None) for i in SAVED_CHART_IDS ] deletes = [ dict(id="saved-deletes-{}".format(i), property="data", value=None) for i in SAVED_CHART_IDS ] with app.test_client() as c: params = { "output": "..save-clicks.data...{}...{}..".format(config_ids, delete_ids), "changedPropIds": ["collapse-data-btn.n_clicks"], "inputs": [ {"id": "save-btn", "property": "n_clicks", "value": 1}, ] + delete_clicks, "state": [ {"id": "input-data", "property": "data", "value": {}}, {"id": "chart-input-data", "property": "data", "value": {}}, {"id": "yaxis-data", "property": "data", "value": {}}, {"id": "map-input-data", "property": "data", "value": {}}, {"id": "candlestick-input-data", "property": "data", "value": {}}, {"id": "treemap-input-data", "property": "data", "value": {}}, {"id": "save-clicks", "property": "data", "value": 0}, ] + configs + deletes, } response = c.post("/dtale/charts/_dash-update-component", json=params) assert response.status_code == 204 input_data = { "data_id": str(c.port), "chart_type": "line", "agg": "sum", "x": "a", "y": ["b"], "yaxis": {}, } params["state"][0]["value"] = input_data params["inputs"][0]["value"] = 1 response = c.post("/dtale/charts/_dash-update-component", json=params) assert response.status_code == 200 response = response.json["response"] unittest.assertEqual(input_data, response["saved-chart-config-1"]["data"]) params["inputs"][0]["value"] = 1 params["inputs"][1]["value"] = 1 params["state"][6]["value"] = 1 response = c.post("/dtale/charts/_dash-update-component", json=params) response = response.json["response"] unittest.assertEqual(None, response["saved-chart-config-1"]["data"])
def test_build_x_dropdown(): import dtale.views as views df = pd.DataFrame( dict( a=[1, 2, 3], b=[4, 5, 6], c=[7, 8, 9], d=pd.date_range("20200101", "20200103"), e=[10, 11, 12], )) with app.test_client() as c: df, _ = views.format_data(df) build_data_inst({c.port: df}) fig_data_outputs = ( "..drilldown-x-dropdown-1.options...drilldown-x-dropdown-1.value.." ) inputs = { "id": "input-data", "property": "data", "value": { "chart_type": "scatter3d", "x": "a", "y": ["b"], "z": "c", "group": None, "agg": None, "window": None, "rolling_comp": None, "animate_by": "date", }, } params = build_dash_request( fig_data_outputs, "chart-1.clickData", { "id": "drilldown-modal-1", "property": "is_open", "value": False }, [ path_builder(c.port), inputs, { "id": "chart-input-data", "property": "data", "value": { "cpg": False, "barmode": "group", "barsort": None }, }, { "id": "yaxis-data", "property": "data", "value": {} }, { "id": "map-input-data", "property": "data", "value": {} }, ], ) response = c.post("/dtale/charts/_dash-update-component", json=params) assert response.get_json() is None assert response.status_code == 204 params["inputs"][0]["value"] = True response = c.post("/dtale/charts/_dash-update-component", json=params) response = response.get_json()["response"]["drilldown-x-dropdown-1"] assert len(response["options"]) == 11 assert response["options"][0] == {"label": "a", "value": "a"} assert response["options"][-1] == {"label": "e", "value": "e"} assert response["value"] == "a" params["state"][1]["value"]["chart_type"] = "maps" params["state"][-1]["value"] = { "map_type": "choropleth", "loc": "a", "map_val": "b", } response = c.post("/dtale/charts/_dash-update-component", json=params) assert response.get_json( )["response"]["drilldown-x-dropdown-1"]["value"] == "a" params["state"][-1]["value"] = { "map_type": "scattergeo", "lat": "a", "lon": "b", "map_val": "c", } response = c.post("/dtale/charts/_dash-update-component", json=params) assert (response.get_json()["response"]["drilldown-x-dropdown-1"] ["value"] == "lat_lon")
def test_load_drilldown_content(custom_data): import dtale.views as views with app.test_client() as c: custom_data.loc[:, "Col4"] = 4 df, _ = views.format_data(custom_data) build_data_inst({c.port: df}) fig_data_outputs = ( "..drilldown-content-1.children...drilldown-x-input-1.style..") inputs = { "id": "input-data", "property": "data", "value": { "chart_type": "bar", "x": "security_id", "y": ["Col0"], "z": None, "group": None, "agg": None, "window": None, "rolling_comp": None, "animate_by": "date", }, } params = build_dash_request( fig_data_outputs, "chart-click-data-1.modified_timestamp", [ ts_builder("chart-click-data-1"), { "id": "drilldown-chart-type-1", "property": "value", "value": None, }, { "id": "drilldown-x-dropdown-1", "property": "value", "value": None, }, ], [ path_builder(c.port), inputs, { "id": "chart-input-data", "property": "data", "value": { "cpg": False, "barmode": "group", "barsort": None }, }, { "id": "yaxis-data", "property": "data", "value": {} }, { "id": "map-input-data", "property": "data", "value": {} }, { "id": "chart-click-data-1", "property": "data", "value": {} }, { "id": "drilldown-toggle", "property": "on", "value": False }, ], ) response = c.post("/dtale/charts/_dash-update-component", json=params) assert response.get_json() is None assert response.status_code == 204 params["state"][-1]["value"] = True response = c.post("/dtale/charts/_dash-update-component", json=params) assert response.get_json() is None assert response.status_code == 204 params["state"][1]["value"]["agg"] = "mean" response = c.post("/dtale/charts/_dash-update-component", json=params) assert response.get_json() is None assert response.status_code == 204 params["inputs"][-1]["value"] = "security_id" response = c.post("/dtale/charts/_dash-update-component", json=params) response = response.get_json()["response"] assert response["drilldown-content-1"]["children"] is None assert response["drilldown-x-input-1"]["style"]["display"] == "none" params["state"][-2]["value"] = { "points": [{ "x": 100000, "y": 1.23, "customdata": pd.Timestamp(df.date.values[0]).strftime("%Y%m%d"), }] } response = c.post("/dtale/charts/_dash-update-component", json=params) exception = print_traceback(response, chart_key="drilldown-content-1", return_output=True) assert "NotImplementedError: chart type: None" in exception params["inputs"][-2]["value"] = "histogram" response = c.post("/dtale/charts/_dash-update-component", json=params) def _chart_title(resp, histogram=False): if histogram: return resp.get_json()["response"]["drilldown-content-1"][ "children"]["props"]["figure"]["layout"]["title"]["text"] return resp.get_json( )["response"]["drilldown-content-1"]["children"]["props"][ "children"][1]["props"]["figure"]["layout"]["title"]["text"] assert _chart_title(response, True) == "Histogram of Col0 (1 data points)" params["inputs"][-2]["value"] = "bar" response = c.post("/dtale/charts/_dash-update-component", json=params) assert _chart_title(response) == "Col0 by security_id (No Aggregation)" params["inputs"][-2]["value"] = "histogram" params["state"][1]["value"]["chart_type"] = "3d_scatter" params["state"][1]["value"]["y"] = "Col4" params["state"][1]["value"]["z"] = "Col0" params["state"][-2]["value"]["points"][0]["y"] = 4 response = c.post("/dtale/charts/_dash-update-component", json=params) assert _chart_title(response, True) == "Histogram of Col0 (1 data points)" params["inputs"][-2]["value"] = "bar" response = c.post("/dtale/charts/_dash-update-component", json=params) assert _chart_title(response) == "Col0 by security_id (No Aggregation)" params["inputs"][-2]["value"] = "histogram" params["state"][1]["value"]["chart_type"] = "heatmap" date_val = pd.Timestamp( df[(df.security_id == 100000) & (df.Col4 == 4)].date.values[0]).strftime("%Y%m%d") params["state"][-2]["value"] = { "points": [{ "x": 100000, "y": 4, "z": 1, "text": "date: {}<br>security_id: 100000<br>Col4: 4<br>Col0: 1".format( date_val), "customdata": date_val, }] } response = c.post("/dtale/charts/_dash-update-component", json=params) assert _chart_title(response, True) == "Histogram of Col0 (1 data points)" params["inputs"][-2]["value"] = "bar" response = c.post("/dtale/charts/_dash-update-component", json=params) assert _chart_title(response) == "Col0 by security_id (No Aggregation)" params["inputs"][-2]["value"] = "histogram" params["state"][1]["value"]["chart_type"] = "maps" params["state"][4]["value"] = { "map_type": "choropleth", "loc": "security_id", "map_val": "Col0", } params["state"][-2]["value"]["points"][0]["location"] = 100000 params["state"][-2]["value"]["points"][0]["z"] = 1.23 response = c.post("/dtale/charts/_dash-update-component", json=params) assert _chart_title(response, True) == "Histogram of Col0 (1 data points)" params["inputs"][-2]["value"] = "bar" response = c.post("/dtale/charts/_dash-update-component", json=params) assert _chart_title(response) == "Col0 by security_id (No Aggregation)" params["inputs"][-2]["value"] = "histogram" params["state"][4]["value"] = { "map_type": "scattergeo", "lat": "security_id", "lon": "Col4", "map_val": "Col0", } params["state"][-2]["value"]["points"][0]["lat"] = 100000 params["state"][-2]["value"]["points"][0]["lon"] = 4 response = c.post("/dtale/charts/_dash-update-component", json=params) assert _chart_title(response, True) == "Histogram of Col0 (1 data points)" params["inputs"][-2]["value"] = "bar" response = c.post("/dtale/charts/_dash-update-component", json=params) assert _chart_title(response) == "Col0 by lat_lon (No Aggregation)"
def test_update_click_data(): import dtale.views as views df = pd.DataFrame( dict( a=[1, 2, 3], b=[4, 5, 6], c=[7, 8, 9], e=[10, 11, 12], d=pd.date_range("20200101", "20200103"), )) with app.test_client() as c: df, _ = views.format_data(df) build_data_inst({c.port: df}) fig_data_outputs = ( "..chart-click-data-1.data...drilldown-modal-header-1.children..") inputs = { "id": "input-data", "property": "data", "value": { "chart_type": "scatter3d", "x": "a", "y": ["b"], "z": "c", "group": None, "agg": None, "window": None, "rolling_comp": None, "animate_by": "date", }, } params = build_dash_request( fig_data_outputs, "chart-1.clickData", { "id": "chart-1", "property": "clickData", "value": None }, [ path_builder(c.port), inputs, { "id": "chart-input-data", "property": "data", "value": { "cpg": False, "barmode": "group", "barsort": None }, }, { "id": "yaxis-data", "property": "data", "value": {} }, { "id": "map-input-data", "property": "data", "value": {} }, { "id": "chart-click-data-1", "property": "data", "value": {} }, { "id": "drilldown-toggle", "property": "on", "value": False }, ], ) response = c.post("/dtale/charts/_dash-update-component", json=params) assert response.get_json() is None assert response.status_code == 204 params["state"][-1]["value"] = True response = c.post("/dtale/charts/_dash-update-component", json=params) assert response.get_json() is None assert response.status_code == 204 params["state"][1]["value"]["agg"] = "mean" response = c.post("/dtale/charts/_dash-update-component", json=params) assert response.get_json() is None assert response.status_code == 204 # Scatter 3D params["inputs"][0]["value"] = { "points": [{ "x": "x", "y": "y", "z": "z", "customdata": "customdata" }] } response = c.post("/dtale/charts/_dash-update-component", json=params) header = response.get_json( )["response"]["drilldown-modal-header-1"]["children"] assert header == "Drilldown for: date (customdata), a (x), b (y), Mean c (z)" # Heatmap Animation params["inputs"][0]["value"] = { "points": [{ "x": "x", "y": "y", "z": "z", "text": "date: date<br>x: x<br>y: y<br>z: z", }] } params["state"][1]["value"]["chart_type"] = "heatmap" response = c.post("/dtale/charts/_dash-update-component", json=params) header = response.get_json( )["response"]["drilldown-modal-header-1"]["children"] assert header == "Drilldown for: date (date), x (x), y (y), z (z)" # Choropleth params["inputs"][0]["value"] = { "points": [{ "location": "x", "z": "z", "customdata": "customdata" }] } params["state"][1]["value"]["chart_type"] = "maps" params["state"][4]["value"] = { "map_type": "choropleth", "loc": "b", "map_val": "c", } response = c.post("/dtale/charts/_dash-update-component", json=params) header = response.get_json( )["response"]["drilldown-modal-header-1"]["children"] assert header == "Drilldown for: date (customdata), b (x), Mean c (z)" # Scattergeo params["inputs"][0]["value"] = { "points": [{ "lat": "x", "lon": "y", "z": "z", "customdata": "customdata" }] } params["state"][1]["value"]["chart_type"] = "maps" params["state"][4]["value"] = { "map_type": "scattergeo", "lat": "b", "lon": "e", "map_val": "c", } response = c.post("/dtale/charts/_dash-update-component", json=params) header = response.get_json( )["response"]["drilldown-modal-header-1"]["children"] assert header == "Drilldown for: date (customdata), b (x), e (y), Mean c (z)"
def test_toggle_modal(): with app.test_client() as c: fig_data_outputs = "drilldown-modal-1.is_open" inputs = { "id": "input-data", "property": "data", "value": { "chart_type": "scatter3d", "x": "a", "y": ["b"], "z": "c", "group": None, "agg": None, "window": None, "rolling_comp": None, "animate_by": "date", }, } params = build_dash_request( fig_data_outputs, "chart-1.clickData", [ { "id": "close-drilldown-modal-1", "property": "n_clicks", "value": None, }, { "id": "close-drilldown-modal-header-1", "property": "n_clicks", "value": None, }, { "id": "chart-1", "property": "clickData", "value": None }, ], [ { "id": "drilldown-modal-1", "property": "is_open", "value": False }, inputs, { "id": "drilldown-toggle", "property": "on", "value": False }, ], ) response = c.post("/dtale/charts/_dash-update-component", json=params) assert response.get_json() is None assert response.status_code == 204 params["state"][-1]["value"] = True response = c.post("/dtale/charts/_dash-update-component", json=params) assert response.get_json() is None assert response.status_code == 204 params["state"][1]["value"]["agg"] = "mean" response = c.post("/dtale/charts/_dash-update-component", json=params) assert not response.get_json( )["response"]["drilldown-modal-1"]["is_open"] params["inputs"][-1]["value"] = {"points": []} response = c.post("/dtale/charts/_dash-update-component", json=params) assert response.get_json()["response"]["drilldown-modal-1"]["is_open"]
def test_merge(unittest): from dtale.views import build_dtypes_state import dtale.global_state as global_state global_state.clear_store() left = pd.DataFrame({ "key1": ["K0", "K0", "K1", "K2"], "key2": ["K0", "K1", "K0", "K1"], "A": ["A0", "A1", "A2", "A3"], "B": ["B0", "B1", "B2", "B3"], }) right = pd.DataFrame({ "key1": ["K0", "K1", "K1", "K2"], "key2": ["K0", "K0", "K0", "K0"], "C": ["C0", "C1", "C2", "C3"], "D": ["D0", "D1", "D2", "D3"], }) right2 = pd.DataFrame({ "key1": ["K0", "K1"], "key2": ["K0", "K0"], "E": ["E0", "E1"], "F": ["F0", "F1"], }) with app.test_client() as c: data = {"1": left, "2": right, "3": right2} dtypes = {k: build_dtypes_state(v) for k, v in data.items()} settings = {k: {} for k in data.keys()} build_data_inst(data) build_dtypes(dtypes) build_settings(settings) datasets = [ dict(dataId="1", columns=[], index=["key1", "key2"], suffix=""), dict(dataId="2", columns=[], index=["key1", "key2"], suffix=""), ] config = dict(how="inner", sort=False, indicator=False) resp = c.post( "/dtale/merge", data=dict( action="merge", config=json.dumps(config), datasets=json.dumps(datasets), ), ) assert resp.status_code == 200 final_df = global_state.get_data(resp.json["data_id"]) unittest.assertEqual(list(final_df.columns), ["key1", "key2", "A", "B", "C", "D"]) assert len(final_df) == 3 datasets[0]["columns"] = ["A"] datasets[1]["columns"] = ["C"] config["how"] = "left" config["indicator"] = True resp = c.post( "/dtale/merge", data=dict( action="merge", config=json.dumps(config), datasets=json.dumps(datasets), ), ) assert resp.status_code == 200 final_df = global_state.get_data(resp.json["data_id"]) unittest.assertEqual(list(final_df.columns), ["key1", "key2", "A", "C", "merge_1"]) unittest.assertEqual( list(final_df["merge_1"].values), ["both", "left_only", "both", "both", "left_only"], ) datasets.append(dict(dataId="3", index=["key1", "key2"], suffix="3")) resp = c.post( "/dtale/merge", data=dict( action="merge", config=json.dumps(config), datasets=json.dumps(datasets), ), ) assert resp.status_code == 200 final_df = global_state.get_data(resp.json["data_id"]) unittest.assertEqual( list(final_df.columns), ["key1", "key2", "A", "C", "merge_1", "E", "F", "merge_2"], ) unittest.assertEqual( list(final_df["merge_2"].values), ["both", "left_only", "both", "both", "left_only"], )