Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[BUG] Callback executes but return value has no effect #1075

Closed
zupalex opened this issue Jan 9, 2020 · 6 comments
Closed

[BUG] Callback executes but return value has no effect #1075

zupalex opened this issue Jan 9, 2020 · 6 comments

Comments

@zupalex
Copy link

zupalex commented Jan 9, 2020

Describe your context
dash 1.7.0
dash-core-components 1.6.0
dash-html-components 1.0.2
dash-renderer 1.2.2
dash-table 4.5.1

Tried on Google Chrome and Firefox

Describe the bug

Hi, I have an issue with a callback. It executes just fine (I can see print outs from it) but the return value has no effect.

Basically I hooked a Div with a custom class (a spinner) to a button. The div is at the beginning hidden. When the button is pressed, the callback changed the style of that div to remove "visibility" attribute. That button also trigger some operations happening on the server side, and when this operation is finished, it fills a Dash table and notify a hidden Div that the operation is finished. That hidden Div is also connected to my spinner to changed the style back to add the 'visibility': "hidden" back.
The code looks like this:

@app.callback(
    [
        dash.dependencies.Output('process-button-trigger-sync', 'n_clicks'),
        dash.dependencies.Output('process-button-trigger-fetch-messages', 'n_clicks')
    ],
    [
        dash.dependencies.Input('process-button', 'n_clicks')
    ]
)
def trigger_process_button_actions(n_clicks):
    print_log(request,f"process-button has been triggered: {n_clicks}")
    if n_clicks:
        return [ 1, 1 ]
    else:
        raise PreventUpdate()

@app.callback(
    dash.dependencies.Output('process-status-icon-div', 'style'),
    [
        dash.dependencies.Input('process-button-trigger-sync', 'n_clicks'),
        dash.dependencies.Input('finished-fetching-messages', 'n_clicks'),
    ],
    [
        dash.dependencies.State('process-status-icon-div', 'style')
    ]
)
def show_loading_state(started, finished, current_style):
    print_log(request,f"Checking loading state div visibility:")
	
    ctx = dash.callback_context

    print_log(request,f"Style: {current_style}")

    if ctx.triggered:
        for evt in ctx.triggered:
            comp = evt['prop_id'].split('.')[0]
            if comp == "process-button-trigger-sync":
                if started:
                    print_log(request,"should show loading state")
                    return loading_state_div_style
                else:
                    raise PreventUpdate()
            else:
                print_log(request,"should hide loading state")
                return {**loading_state_div_style, **{'visibility': "hidden"}}

@app.callback(
    [
        dash.dependencies.Output('msg-table', 'children'),
        dash.dependencies.Output('finished-fetching-messages', 'n_clicks')
    ],
    [
        dash.dependencies.Input("process-button-trigger-fetch-messages", "n_clicks")
    ]
)
def get_messages(n_clicks):
    [... do stuffs ...]

    return [ [table_content], 1 ]

It works the first time I click on the button , but any subsequent click generate the proper table content for and triggers the callback (I can see it in the logs), but the
does't get its style changed.

The logs shows:

2020-01-09 17:50:05.174789] [10.50.46.15] process-button has been triggered: 1
[2020-01-09 17:50:05.212320] [10.50.46.15] Checking loading state div visibility:
[2020-01-09 17:50:05.212419] [10.50.46.15] Style: {'display': 'inline-block', 'padding': '0% 6% 0% 1%', 'vertical-align': 'middle', 'visibility': 'hidden'}
[2020-01-09 17:50:05.212520] [10.50.46.15] should show loading state
[2020-01-09 17:50:05.349895] [10.50.46.15] Getting messages...
[2020-01-09 17:50:06.155847] [10.50.46.15] Checking loading state div visibility:
[2020-01-09 17:50:06.155985] [10.50.46.15] Style: {'display': 'inline-block', 'padding': '0% 6% 0% 1%', 'vertical-align': 'middle'}
[2020-01-09 17:50:06.156095] [10.50.46.15] should hide loading state
[2020-01-09 17:50:23.486135] [10.50.46.15] process-button has been triggered: 2
[2020-01-09 17:50:23.533177] [10.50.46.15] Checking loading state div visibility:
[2020-01-09 17:50:23.533278] [10.50.46.15] Style: {'display': 'inline-block', 'padding': '0% 6% 0% 1%', 'vertical-align': 'middle', 'visibility': 'hidden'}
[2020-01-09 17:50:23.533379] [10.50.46.15] should show loading state
[2020-01-09 17:50:23.664761] [10.50.46.15] Getting messages...
[2020-01-09 17:50:24.436566] [10.50.46.15] Checking loading state div visibility:
[2020-01-09 17:50:24.436679] [10.50.46.15] Style: {'display': 'inline-block', 'padding': '0% 6% 0% 1%', 'vertical-align': 'middle', 'visibility': 'hidden'}
[2020-01-09 17:50:24.436779] [10.50.46.15] should hide loading state

As you can see (hopefully), The first time the button is pressed, the current style of the Div has the "hidden" attribute. When the operation is over ("Getting messages" message), the callback is triggered again, and the current style doesn't have the hidden attribute (which is expected).

The second time around, the callback is triggered properly again, but the spinner never showed. Indeed, one can see that after the operation completes, and the current style of the div is printed, it has the "hidden" attribute while it should have been removed previously.

Expected behavior

When the callback is triggered and executed, the return value should have an effect.

I hope this is clear enough. This is taken from a big piece of code so hopefully I managed to make it readable.

@zupalex
Copy link
Author

zupalex commented Jan 9, 2020

Using what I posted here as a minimal example, I can get it to work properly by removing the intermediate Div and and . Instead, if I use the directly as an input for both callbacks, and use children as an input for the second callback instead of , it updates just fine.

However I would expect this to work, especially since the callback fires properly, just that the return value is not registered.

The reason why I split it like that at the first place is that in the full code, I was using as an input for multiple callbacks (5 total), and some would not fire at all or fire randomly when the button was pressed. Using it as an input for only one callback and using several Div to propagate the callback made the appropriate callbacks fire, but then the return values are "ignored" as described in the first post.

@mjclawar
Copy link
Contributor

Running into (I think) a similar issue with callbacks being ignored with Dash releases post 1.6.1. The callback does execute (and I can even see it correct in the network requests in the console), but does not have the intended effect on the layout.

Does your example work when you use dash==1.6.1 and break when you use dash==1.7.0?

@mjclawar
Copy link
Contributor

mjclawar commented Feb 15, 2020

It may have something to do with this change (I would assume it's in the dash-renderer JS since it's making it to the client correctly):

v1.6.1...v1.7.0#diff-15692ba768eeab56e95aa409ab7c19b7R370-R392

/**
* Determine the id of all components used as input or state in the callbacks
* triggered by the props change.
*
* Wait for all components associated to these ids to be ready before initiating
* the callbacks.
*/
const deps = queuedObservers.map(output =>
  dependenciesRequest.content.find(
    dependency => dependency.output === output
  )
);

const ids = uniq(
  flatten(
    deps.map(dep => [
      dep.inputs.map(input => input.id),
      dep.state.map(state => state.id),
    ])
  )
);

await isAppReady(layout, paths, ids);

Also potentially related to #1071 and #1053

@alexcjohnson
Copy link
Collaborator

I suspect this will be fixed by #1103 (which includes tests for #1053 and #1071) but it's hard to tell for sure without a complete reproduction case. If one of you would be able to post a full app showing the bug I'd be happy to try it and include a test for it - or, you can try the 475-wildcards branch on your own, but beware if you pull that branch down you need to rebuild the renderer bundle before running the app.

@matsujju
Copy link

matsujju commented Jan 12, 2021

Hey @alexcjohnson
I am having similar issue but my Output is single. The result of callback is printing in console but not showing in Dashboard. I have also posted in community channel. You can look here.
Here is the code of mine:

import dash
import dash_core_components as dcc
import dash_html_components as html
import dash_bootstrap_components as dbc
from dash.dependencies import Input, Output, State
from dash.exceptions import PreventUpdate
import pandas as pd
import numpy as np
import joblib
import re
import nltk
from bs4 import BeautifulSoup
import unicodedata
import spacy

nlp = spacy.load("en_core_web_sm")

from nltk.tokenize import word_tokenize
from nltk.tokenize.toktok import ToktokTokenizer

tokenizer = ToktokTokenizer()
stopword_list = nltk.corpus.stopwords.words("english")

import pickle
from sklearn.feature_extraction.text import TfidfVectorizer

################################################################################
def data_clean(text):
    text = text.lower()  # lowering of characters
    text = text.strip()  # removeing extra spaces before and after the sentence
    # text = TextBlob(text).correct() # spelling mistakes correction  (very slow) (maybe try directly on dataframe column)
    return text


# remove accented characters (Sómě Áccěntěd těxt)
def remove_accented_chars(text):
    text = (
        unicodedata.normalize("NFKD", text)
        .encode("ascii", "ignore")
        .decode("utf-8", "ignore")
    )
    return text


def strip_html_tags(text):  # function for removing html tags
    soup = BeautifulSoup(text, "html.parser")
    stripped_text = soup.get_text()
    return stripped_text


def remove_special_characters(
    text, remove_digits=False
):  # function for removing punctuations , before using this remove any contractions(like "I'll" --> "I will") in text data
    pattern = r"[^a-zA-z0-9\s\-\+\#]" if not remove_digits else r"[^a-zA-z\s\-\+\#]"
    text = re.sub(pattern, "", text)
    return text


def remove_links_emojis(text):
    pattern = re.compile(
        r"http[s]?://(?:[a-zA-Z]|[0-9]|[$-_@.&+]|[!*\(\),]|(?:%[0-9a-fA-F][0-9a-fA-F]))+"
    )  # removing https:www.examples.com
    text = pattern.sub("", text)

    emoji = re.compile(
        "["
        "\U0001F600-\U0001FFFF"  # emoticons
        "\U0001F300-\U0001F5FF"  # symbols & pictographs
        "\U0001F680-\U0001F6FF"  # transport & map symbols
        "\U0001F1E0-\U0001F1FF"  # flags (iOS)
        "\U00002702-\U000027B0"
        "\U000024C2-\U0001F251"
        "]+",
        flags=re.UNICODE,
    )
    text = emoji.sub(r"", text)
    return text


def contractions(text):  # contraction of text i.e remove_apostrophes
    text = re.sub("ain't", "is not", str(text))
    text = re.sub("aren't", "are not", str(text))
    text = re.sub("can't", "cannot", str(text))
    text = re.sub("can't've", "cannot have", str(text))
    text = re.sub("'cause", "because", str(text))
    text = re.sub("could've", "could have", str(text))
    text = re.sub("couldn't", "could not", str(text))
    text = re.sub("couldn't've", "could not have", str(text))
    text = re.sub("didn't", "did not", str(text))
    text = re.sub("doesn't", "does not", str(text))
    text = re.sub("don't", "do not", str(text))
    text = re.sub("hadn't", "had not", str(text))
    text = re.sub("hadn't've", "had not have", str(text))
    text = re.sub("hasn't", "has not", str(text))
    text = re.sub("haven't", "have not", str(text))
    text = re.sub("he'd", "he would", str(text))
    text = re.sub("he'd've", "he would have", str(text))
    text = re.sub("he'll", "he will", str(text))
    text = re.sub("he'll've", "he he will have", str(text))
    text = re.sub("he's", "he is", str(text))
    text = re.sub("how'd", "how did", str(text))
    text = re.sub("how'd'y", "how do you", str(text))
    text = re.sub("how'll", "how will", str(text))
    text = re.sub("how's", "how is", str(text))
    text = re.sub("I'd", "I would", str(text))
    text = re.sub("I'd've", "I would have", str(text))
    text = re.sub("I'll", "I will", str(text))
    text = re.sub("I'll've", "I will have", str(text))
    text = re.sub("I'm", "I am", str(text))
    text = re.sub("I've", "I have", str(text))
    text = re.sub("i'd", "i would", str(text))
    text = re.sub("i'd've", "i would have", str(text))
    text = re.sub("i'll", "i will", str(text))
    text = re.sub("i'll've", "i will have", str(text))
    text = re.sub("i'm", "i am", str(text))
    text = re.sub("i've", "i have", str(text))
    text = re.sub("isn't", "is not", str(text))
    text = re.sub("it'd", "it would", str(text))
    text = re.sub("it'd've", "it would have", str(text))
    text = re.sub("it'll", "it will", str(text))
    text = re.sub("it'll've", "it will have", str(text))
    text = re.sub("it's", "it is", str(text))
    text = re.sub("let's", "let us", str(text))
    text = re.sub("ma'am", "madam", str(text))
    text = re.sub("mayn't", "may not", str(text))
    text = re.sub("might've", "might have", str(text))
    text = re.sub("mightn't", "might not", str(text))
    text = re.sub("mightn't've", "might not have", str(text))
    text = re.sub("must've", "must have", str(text))
    text = re.sub("mustn't", "must not", str(text))
    text = re.sub("mustn't've", "must not have", str(text))
    text = re.sub("needn't", "need not", str(text))
    text = re.sub("needn't've", "need not have", str(text))
    text = re.sub("o'clock", "of the clock", str(text))
    text = re.sub("oughtn't", "ought not", str(text))
    text = re.sub("oughtn't've", "ought not have", str(text))
    text = re.sub("shan't", "shall not", str(text))
    text = re.sub("sha'n't", "shall not", str(text))
    text = re.sub("shan't've", "shall not have", str(text))
    text = re.sub("she'd", "she would", str(text))
    text = re.sub("she'd've", "she would have", str(text))
    text = re.sub("she'll", "she will", str(text))
    text = re.sub("she'll've", "she will have", str(text))
    text = re.sub("she's", "she is", str(text))
    text = re.sub("should've", "should have", str(text))
    text = re.sub("shouldn't", "should not", str(text))
    text = re.sub("shouldn't've", "should not have", str(text))
    text = re.sub("so've", "so have", str(text))
    text = re.sub("so's", "so as", str(text))
    text = re.sub("that'd", "that would", str(text))
    text = re.sub("that'd've", "that would have", str(text))
    text = re.sub("that's", "that is", str(text))
    text = re.sub("there'd", "there would", str(text))
    text = re.sub("there'd've", "there would have", str(text))
    text = re.sub("there's", "there is", str(text))
    text = re.sub("they'd", "they would", str(text))
    text = re.sub("they'd've", "they would have", str(text))
    text = re.sub("they'll", "they will", str(text))
    text = re.sub("they'll've", "they will have", str(text))
    text = re.sub("they're", "they are", str(text))
    text = re.sub("they've", "they have", str(text))
    text = re.sub("to've", "to have", str(text))
    text = re.sub("wasn't", "was not", str(text))
    text = re.sub("we'd", "we would", str(text))
    text = re.sub("we'd've", "we would have", str(text))
    text = re.sub("we'll", "we will", str(text))
    text = re.sub("we'll've", "we will have", str(text))
    text = re.sub("we're", "we are", str(text))
    text = re.sub("we've", "we have", str(text))
    text = re.sub("weren't", "were not", str(text))
    text = re.sub("what'll", "what will", str(text))
    text = re.sub("what'll've", "what will have", str(text))
    text = re.sub("what're", "what are", str(text))
    text = re.sub("what's", "what is", str(text))
    text = re.sub("what've", "what have", str(text))
    text = re.sub("when's", "when is", str(text))
    text = re.sub("when've", "when have", str(text))
    text = re.sub("where'd", "where did", str(text))
    text = re.sub("where's", "where is", str(text))
    text = re.sub("where've", "where have", str(text))
    text = re.sub("who'll", "who will", str(text))
    text = re.sub("who'll've", "who will have", str(text))
    text = re.sub("who's", "who is", str(text))
    text = re.sub("who've", "who have", str(text))
    text = re.sub("why's", "why is", str(text))
    text = re.sub("why've", "why have", str(text))
    text = re.sub("will've", "will have", str(text))
    text = re.sub("won't", "will not", str(text))
    text = re.sub("won't've", "will not have", str(text))
    text = re.sub("would've", "would have", str(text))
    text = re.sub("wouldn't", "would not", str(text))
    text = re.sub("wouldn't've", "would not have", str(text))
    text = re.sub("y'all", "you all", str(text))
    text = re.sub("y'all'd", "you all would", str(text))
    text = re.sub("y'all'd've", "you all would have", str(text))
    text = re.sub("y'all're", "you all are", str(text))
    text = re.sub("y'all've", "you all have", str(text))
    text = re.sub("you'd", "you would", str(text))
    text = re.sub("you'd've", "you would have", str(text))
    text = re.sub("you'll", "you will", str(text))
    text = re.sub("you'll've", "you will have", str(text))
    text = re.sub("you're", "you are", str(text))
    text = re.sub("you've", "you have", str(text))
    return text


def lemmatize_text(text):  # function for lemmetization of text
    text = nlp(text)
    text = str(
        " ".join(
            [word.lemma_ if word.lemma_ != "-PRON-" else word.text for word in text]
        )
    )
    return text


def remove_stopwords(text):
    tokens = tokenizer.tokenize(text)
    tokens = [token.strip() for token in tokens]

    filtered_tokens = [token for token in tokens if token.lower() not in stopword_list]
    filtered_text = " ".join(filtered_tokens)
    return filtered_text


def preprocess(
    text,
    clean_data=True,
    contraction_expansion=True,
    accented_char_removal=True,
    text_lemmatization=False,
    link_emoji=True,
    strip_html_tag=True,
    special_characters=True,
    remove_digits=False,
    stop_words=False,
):

    # cleaned data (lower & strip whitespaces & spelling mistake)
    if clean_data:
        text = data_clean(text)
    # strip html
    if strip_html_tag:
        text = strip_html_tags(text)
    # accented char removal
    if accented_char_removal:
        text = remove_accented_chars(text)

    # exapand contraction
    if contraction_expansion:
        text = contractions(text)
    if stop_words:
        text = remove_stopwords(text)
    # lemmetization
    if text_lemmatization:
        text = lemmatize_text(text)
    # punctuations and digits
    if special_characters:
        text = remove_special_characters(text, remove_digits=remove_digits)

    # remove extra whitespace
    text = re.sub(" +", " ", text)

    return text


classifier = joblib.load("logreg_multi_label.joblib")
multilabel = joblib.load("multilabel_binarizer.pkl")

tfidf_vocab = pickle.load(open("tfidf_file_0.8.pkl", "rb"))
tfidf = TfidfVectorizer(
    max_features=50000,
    ngram_range=(1, 3),
    stop_words="english",
    vocabulary=tfidf_vocab.vocabulary_,
)


def get_tags(predicted_list, threshold, labels):
    mlb = [(i1, c1) for i1, c1 in enumerate(multilabel.classes_)]
    temp_list = sorted(
        [(i, c) for i, c in enumerate(list(predicted_list))],
        key=lambda x: x[1],
        reverse=True,
    )
    tag_list = [item1 for item1 in temp_list if item1[1] >= threshold]
    tags = [
        item[1] for item2 in tag_list[:labels] for item in mlb if item2[0] == item[0]
    ]
    return tags


app = dash.Dash(
    __name__, title="Multi Tag Prediction", external_stylesheets=[dbc.themes.BOOTSTRAP]
)
server = app.server
app.layout = html.Div(
    [
        html.H2(
            ["Multi Tag Prediction"],
            style={
                "text-align": "left",
                "margin-left": "4.5%",
                "margin-right": "2%",
                "margin-bottom": "2%",
            },
        ),
        dbc.Row(
            [
                dbc.Col(
                    html.Div(
                        [
                            dbc.FormGroup(
                                [
                                    dbc.Label(
                                        "Filter by Preprocessing Functions to Apply:"
                                    ),
                                    dbc.RadioItems(
                                        options=[
                                            {"label": "All", "value": "all"},
                                            {"label": "Customized", "value": "custom"},
                                        ],
                                        value="all",
                                        id="radio-button",
                                        inline=True,
                                        style={"padding": "10px"},
                                    ),
                                ]
                            ),
                            html.Div(
                                [
                                    dcc.Dropdown(
                                        id="drop-down",
                                        options=[
                                            {
                                                "label": "remove_digits",
                                                "value": "remove_digits",
                                            },
                                            {
                                                "label": "remove_stopwords",
                                                "value": "stop_words",
                                            },
                                            {
                                                "label": "text_lemmatization",
                                                "value": "text_lemmatization",
                                            },
                                        ],
                                        value=[
                                            "remove_digits",
                                            "remove_stopwords",
                                            "text_lemmatization",
                                        ],
                                        multi=True,
                                        placeholder="Select the preprocessing functions",
                                        searchable=True,
                                    )
                                ],
                                style={"padding": "5px", "margin-bottom": "10px",},
                            ),
                            html.Div(
                                [
                                    html.H6(
                                        "Filter by threshold (probabilty of labels to include):"
                                    ),
                                    dcc.Slider(
                                        id="slider",
                                        min=0.1,
                                        max=0.9,
                                        step=0.1,
                                        value=0.5,
                                        marks={
                                            0.1: "0.1",
                                            0.2: "0.2",
                                            0.3: "0.3",
                                            0.4: "0.4",
                                            0.5: "0.5",
                                            0.6: "0.6",
                                            0.7: "0.7",
                                            0.8: "0.8",
                                            0.9: "0.9",
                                        },
                                        tooltip={"placement": "top"},
                                        included=False,
                                    ),
                                ],
                                style={"margin-bottom": "10px"},
                            ),
                            html.Div(
                                [
                                    html.H6(
                                        "Filter by label count (how many labels to include if there are more):"
                                    ),
                                    dcc.Slider(
                                        id="slider-2",
                                        min=5,
                                        max=10,
                                        step=1,
                                        value=5,
                                        marks={
                                            5: "5",
                                            6: "6",
                                            7: "7",
                                            8: "8",
                                            9: "9",
                                            10: "10",
                                        },
                                        tooltip={"placement": "top"},
                                        included=False,
                                    ),
                                ],
                                style={"margin-top": "10px"},
                            ),
                        ],
                        style={
                            "width": "100%",
                            "box-shadow": "5px 5px 5px  #cacfd2",
                            "padding": "35px",
                            "background-color": "#f9f9f9",
                        },
                    ),
                    width={"size": 4, "order": "first", "offset": 0},
                ),
                dbc.Col(
                    html.Div(
                        [
                            dbc.Textarea(
                                id="input_text",
                                value="",
                                style={
                                    "height": 300,
                                    "width": "100%",
                                    "box-shadow": "5px 5px 5px  #cacfd2",
                                    # "margin-left": "-20px",
                                    "background-color": "#f9f9f9",
                                },
                                maxLength=10000,
                                placeholder="Type the text or copy-paste from somewhere else",
                            ),
                            dbc.Button(
                                "Submit",
                                id="submit-button",
                                n_clicks=0,
                                style={"margin-left": "532px", "margin-top": "15px",},
                                outline=True,
                                color="primary",
                            ),
                            html.Div(
                                [
                                    dbc.Spinner(
                                        [
                                            dbc.Toast(
                                                id="output-state",
                                                header="Predicted Labels:",
                                                icon="info",
                                                is_open=True,
                                                duration=1000000,
                                                dismissable=False,
                                                style={
                                                    "position": "fixed",
                                                    "height": "15%",
                                                    "width": "400%",
                                                    "margin-top": "20px",
                                                },
                                                children=[],
                                            )
                                        ],
                                        type="grow",
                                        color="info",
                                        fullscreen=False,
                                    ),
                                ]
                            ),
                        ]
                    ),
                    width={"size": 6, "order": "second"},
                    # align="end",
                ),
            ],
            justify="around",
        ),
    ],

)


@app.callback(Output("drop-down", "value"), [Input("radio-button", "value")])
def display_status(selector):
    if selector == "all":
        return ["remove_digits", "text_lemmatization", "remove_stopwords"]
    else:
        return []


@app.callback(
    Output("output-state", "is_open"),
    [Input("submit-button", "n_clicks")],
    [
        State("input_text", "value"),
        State("slider", "value"),
        State("drop-down", "value"),
        State("slider-2", "value"),
    ],
    prevent_initial_call=True,
)
def label_prediction(num_clicks, text, threshold_value, preprocess_func, label_value):
    if text is None or num_clicks is None:
        raise PreventUpdate
    else:

        params = ["remove_digits", "remove_stopwords", "text_lemmatization"]
        dict_params = [param in preprocess_func for param in params]
        preprocess_text = preprocess(text, *dict_params)
        transformed_text = tfidf.fit_transform([preprocess_text])
        prediction = classifier.predict_proba(transformed_text)
        # print("This is the result to show")
        result = get_tags(prediction[0], threshold_value, label_value)
        print(str(result))
        return result


if __name__ == "__main__":
    app.run_server(debug=True)

@alexcjohnson
Copy link
Collaborator

I continued the discussion with @matsujju on the community post. The rest of this most likely was handled by #1103 but if there's anything else along these lines please open a new issue.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants