How to create a webapp with Python?

Christina
Christina Registered Posts: 9 ✭✭✭✭

Hello,

I want to build a webapp that calls a model i have trained and displays the result on the HTML fornt end. I have semi-built the HTML and Python components but I am unsure how to connect these two via JavaScript.

Any help would be much appreciate.

Answers

  • lpkronek
    lpkronek Dataiker Posts: 13 Dataiker

    Hello Christina,

    We have an introductory tutorial here on the topic here: https://academy.dataiku.com/latest/tutorial/webapps/standard.html#load-data-in-the-python-backend.

    Do not hesitate to come back with additional questions if needed.

  • Christina
    Christina Registered Posts: 9 ✭✭✭✭

    Hello

    Thank you very much for your reply. I have been reading the tutorial but still my code does not work
    What I am trying to do is to have a simple input text where the user types some text and they press the button, the text is sent to the Python code which calls a model to get a prediction on that text. I know how to call the model using Dataiku API (I am ok with Python) and do the HTML but i struggle to link these two via JavaScript.
    Here is my HTML code:
    <link rel="stylesheet" href="/static/public/styles/1.0.0/dku-styles.css" />
    <h1>Prediction demo</h1>
    <div class="fetch-dataset-container">
    <section class="fetch-dataset-form">
    <form id="form-prediction" novalidate>
    <div class="field">
    <label for="description">Item's description</i></label>
    <input type="text" name="description" placeholder="this is a test" id="description">
    </div>
    <button type="button" class="main-blue-button" id="predict-button">Predict</button>
    </form>
    </section>
    </div>
    <div>
    <section class="fetch-prediction-results">
    <span id="message">Results will appear here.</span>
    </section>
    </div>
    Here is the JavaScript code:
    let predictButton = document.getElementById('predict-button');
    let descriptionText = document.getElementById('description');
    let messageContainer = document.getElementById('message');
    let selectedDescription = {};

    function displayMessage(messageText, messageClassname) {
    messageContainer.innerHTML = messageText;
    messageContainer.className = '';
    if (messageClassname && messageClassname.length > 0) {
    messageContainer.className = messageClassname;
    }
    }

    function clearMessage() {
    displayMessage('');
    }

    function displayFailure() {
    displayMessage('Error occured', 'error-message');
    }

    function displayPrediction(dataFrame) {
    let columnsNames = dataFrame.getColumnNames();
    let line = '------------------------------';
    let text = selectedDescription + '\n'
    + line + '\n'
    + dataFrame.getNbRows() + ' Rows\n'
    + columnsNames.length + ' Columns\n'
    + '\n' + line + '\n'
    + 'Columns names: \n';
    columnsNames.forEach(function(columnName) {
    text += columnName + ', ';
    });
    displayMessage(text);
    }

    predictButton.addEventListener('click', function(event) {
    $.getJSON(getWebAppBackendUrl('/first_api_call'), function(data) {
    clearMessage();
    selectedDescription.name = document.getElementById('description').value;
    dataiku.fetch(selectedDescription.name, function(dataFrame) {
    selectedDescription.dataFrame = dataFrame;
    displayPrediction(dataFrame);
    }, function() {
    displayFailure();
    });
    const output = $('<pre />').text('Backend reply: ' + JSON.stringify(data));
    $('body').append(output)
    };
    return false;
    });
    And this is my Python code, it is only a test at the moment just trying to call a dataset which I have enabled from the Settings:
    @app.route('/First_api_call')
    def first_api_call():
    max_rows = request.args.get('max_rows') if 'max_rows' in request.args else 500
    mydataset = dataiku.Dataset("test_dataset")
    mydataset_df = mydataset.get_dataframe(sampling='head', limit=max_rows)
    # Pandas dataFrames are not directly JSON serializable, use to_json()
    data = mydataset_df.to_json()
    return json.dumps({"status": "ok", "data": data})
    Please any help would be much appreicated.
    Many thanks
    Christina
  • lpkronek
    lpkronek Dataiker Posts: 13 Dataiker
    edited July 17

    Hi Christina,

    I quickly tested your code and I think I have spotted 2 small typos in your JS code :

    • The parameter of the getWebBackendUrl function needs to match the parameter @app.route instruction in the python code
    • You have a missing ")" in your addEventListener (before the return false)

    Here is what I get :

    predictButton.addEventListener('click', function(event) {
        $.getJSON(getWebAppBackendUrl('/First_api_call'), function(data) {
            clearMessage();
            selectedDescription.name = document.getElementById('description').value;
            dataiku.fetch(selectedDescription.name, function(dataFrame) {
                selectedDescription.dataFrame = dataFrame;
                displayPrediction(dataFrame);
            }, function() {
                displayFailure();
            });
            const output = $('<pre />').text('Backend reply: ' + JSON.stringify(data));
           $('body').append(output)
        });
        return false;
    });

    In order to debug your javascript tool, you can use the developer tools of your browser where you will be able to see the error message in the console and much more.

  • Christina
    Christina Registered Posts: 9 ✭✭✭✭
    Hi lpkronek
    Thank you very much for your reply. I made the 2 changes you mention but my code still does not work
    Any help would be very much appreciated.
    Many thanks
    Christina
  • lpkronek
    lpkronek Dataiker Posts: 13 Dataiker

    Can you share more about the error that you see ?

  • Christina
    Christina Registered Posts: 9 ✭✭✭✭

    So I changed my Python code to the dollowing:

    @app.route('/First_api_call')
    def first_api_call():
    max_rows = request.args.get('max_rows') if 'max_rows' in request.args else 500

    data = {'First Column Name': ['First value', 'Second value'],
    'Second Column Name': ['First value', 'Second value']
    }
    mydataset_df = pd.DataFrame (data, columns = ['First Column Name','Second Column Name'])
    data = mydataset_df.to_json()
    return json.dumps({"status": "ok", "data": data})

    The method creates and returns a simple dataframe which I would like to see it displayed on the front end when i click the Predict button. However when i click the button i get the following error:

    405 (Request method 'POST' not supported)

    please see error.png for more details

    And when i click on this error i get what you see on error2.png

    am i doing something wrong?

  • lpkronek
    lpkronek Dataiker Posts: 13 Dataiker
    edited July 17

    The error that you are seeing is coming from the JS code.

    it contains a call to :

     dataiku.fetch(selectedDescription.name, function(dataFrame) {
                selectedDescription.dataFrame = dataFrame;
                displayPrediction(dataFrame);
            }, function() {
                displayFailure();
            });

    This call is trying to get the data from the dataset that has been inputted in the text box.

    You probably want to remove this call has your goal seems to render the data return by the python code.

    Also, when using datasets in a webapp, you need to grant access to them. You will find a security button on the settings of the webapp.

  • Christina
    Christina Registered Posts: 9 ✭✭✭✭

    Thank you lpkronek,

    I have removed these lines as you suggested.

    Now when i click on the Predict button, although i dont see any error on the Console, i dont see the dataframe displayed on the page. The python code is executed though as i see the following on the Console tab:

    2020-02-21 15:08:26,863 INFO 127.0.0.1 - - [21/Feb/2020 15:08:26] "GET /First_api_call HTTP/1.1" 200 -

    I attach my code again. Your help is much appreciated

    HTML

    <div class="fetch-dataset-container">
    <section class="fetch-dataset-form">
    <form id="form-dataset" novalidate>
    <div class="field">
    <label for="description">Item's description</i></label>
    <input type="text" name="description" placeholder="i.e. Testing of new circuits as per specification" id="description">
    </div>
    <button type="button" class="main-blue-button" id="predict-button">Predict</button>
    </form>
    </section>
    </div>
    <div>
    <section class="fetch-dataset-results">
    <span id="message">Results will appear here.</span>
    </section>
    </div>

    JS

    let predictButton = document.getElementById('predict-button');
    let descriptionText = document.getElementById('description');
    let messageContainer = document.getElementById('message');
    let selectedDescription = {};

    function displayMessage(messageText, messageClassname) {
    messageContainer.innerHTML = messageText;
    messageContainer.className = '';
    if (messageClassname && messageClassname.length > 0) {
    messageContainer.className = messageClassname;
    }
    }

    function clearMessage() {
    displayMessage('');
    }

    function displayFailure() {
    displayMessage('Error occured', 'error-message');
    }

    function displayPrediction(dataFrame) {
    let columnsNames = dataFrame.getColumnNames();
    let line = '------------------------------';
    let text = selectedDescription + '\n'
    + line + '\n'
    + dataFrame.getNbRows() + ' Rows\n'
    + columnsNames.length + ' Columns\n'
    + '\n' + line + '\n'
    + 'Columns names: \n';
    columnsNames.forEach(function(columnName) {
    text += columnName + ', ';
    });
    displayMessage(text);
    }

    predictButton.addEventListener('click', function(event) {
    $.getJSON(getWebAppBackendUrl('/First_api_call'), function(data) {
    clearMessage();
    displayPrediction(data);
    });
    return false;
    });

    Python

    @app.route('/First_api_call')
    def first_api_call():
    max_rows = request.args.get('max_rows') if 'max_rows' in request.args else 500data = {'First Column Name': ['First value', 'Second value'],
    'Second Column Name': ['First value', 'Second value']
    }
    mydataset_df = pd.DataFrame (data, columns = ['First Column Name','Second Column Name'])
    data = mydataset_df.to_json()
    print (mydataset_df)
    return json.dumps({"status": "ok", "data": data})

  • Christina
    Christina Registered Posts: 9 ✭✭✭✭

    An update....

    I am calling the displayPrediction() method from the predictButton.addEventListener() and the error i get says:

    dataFrame.getColumnNames is not a function at displayPrediction

    Is it something wrong with Dataiku API?

  • lpkronek
    lpkronek Dataiker Posts: 13 Dataiker

    Hello Christina,

    The data that you receives in your event listener is the json document that your python code return from first_api_call() and not a dataiku object. You need to adapt the displayPrediction function to process the json document.

  • Christina
    Christina Registered Posts: 9 ✭✭✭✭

    Hello,

    I managed to display what i get from Python

    Do you know how can i pass a value from the front end to my Python code via Javascript?

    Many thanks

    Christina

  • Christina
    Christina Registered Posts: 9 ✭✭✭✭

    Hello @lpkronek

    Apologies for asking so many questions. I read the Dataiku literature and i have modified my code as per below

    JS

    let descriptionText = document.getElementById('description');

    function displayMessage(messageText, messageClassname) {
    messageContainer.innerHTML = messageText;
    messageContainer.className = '';
    if (messageClassname && messageClassname.length > 0) {
    messageContainer.className = messageClassname;
    }
    }

    function clearMessage() {
    displayMessage('');
    }

    predictButton.addEventListener('click', function(event) {
    $.getJSON(getWebAppBackendUrl('First_api_call'), {description:descriptionText})
    .done(
    function(data) {
    clearMessage();
    displayMessage(data.description);
    }
    );
    return false;
    });

    Python

    from flask import request

    @app.route('/First_api_call')
    def first_api_call():
    userdescription = str(request.args.get("description"))
    max_rows = request.args.get('max_rows') if 'max_rows' in request.args else 500return json.dumps({"status":"ok", "description":userdescription})

    And in my HTML page i have got an input with id='description'

    When i click the predict button i get the following error:

    Uncaught RangeError: Maximum call stack size exceeded as per photo attached

    Any help is much appreciated

    Kind regards,

    Christina

  • lpkronek
    lpkronek Dataiker Posts: 13 Dataiker
    edited July 17

    Hello,

    It looks like a JS problem. Do not hesitate to look at dedicated resources on the topic to get more accurate support.

    I'm under the impression that in your variable descriptionText you have the whole element and not the text value. In addition, you need to add quotes around "description". Your call to your python backend should look something like this :

    $.getJSON(getWebAppBackendUrl('First_api_call'), {"description" : descriptionText.value})

    $.getJSON(getWebAppBackendUrl('First_api_call'), {"description" : descriptionText.value})

  • Christina
    Christina Registered Posts: 9 ✭✭✭✭

    Hi @lpkronek
    ,

    That was the probelm, thank you so much for your help.

    I think i can take it from here.

    again, thank you very much for your support!

    Kind regards,

    Christina

Setup Info
    Tags
      Help me…