added PreventUpdate exception and attached handler to Flask server - #190

Merged
chriddyp merged 5 commits into
plotly:masterfrom
ned2:add-prevent-update-exception
Jan 19, 2018
Merged

added PreventUpdate exception and attached handler to Flask server#190
chriddyp merged 5 commits into
plotly:masterfrom
ned2:add-prevent-update-exception

Conversation

@ned2

@ned2ned2 commented Jan 5, 2018

Copy link
Copy Markdown
Contributor

create a new exception PreventUpdate for use when a callback needs to be aborted without updating the state of the client.

This exception is registered with the Flask server using an errorhandler such that on catching this exception Flask will print any message included in the exception to standard error and then return an empty 204 NO CONTENT response.

@ned2

ned2 commented Jan 5, 2018

Copy link
Copy Markdown
ContributorAuthor

Oops, I forgot about Python 2. I hope importing the print function from future is ok. Without this I don't really know of an clean way to print to stderr. But now I think about it, it's not clear this message should be going to stderr. thoughts?

Also, tests are failing because of #189.

@chriddyp

Copy link
Copy Markdown
Member

This looks good to me. I think we should add an integration test in here to lock down the behaviour. These tests would go in here: https://github.com/plotly/dash/blob/308579660b8791296f437bdcd060dc89a42f2b07/tests/test_integration.py. We basically want to check that raising this exception inside a callback will indeed:
1 - Not update the output
2 - Not trigger the output's dependencies

Let me know if you need some help with this!

I hope importing the print function from __future__ is ok

From https://stackoverflow.com/questions/5574702/how-to-print-to-stderr-in-python, it seems OK. In any case, if we add an integration test then we'll know for sure 🙂

@chriddyp

Copy link
Copy Markdown
Member

Also #189 has been merged, so we can rebase this branch off master and the baseline tests should be fixed

@ned2
ned2force-pushed the add-prevent-update-exception branch from 8a46164 to edd2a8cCompareJanuary 13, 2018 01:53
@ned2

ned2 commented Jan 13, 2018

Copy link
Copy Markdown
ContributorAuthor

I rebased off master and tests are passing again :)

I also added an integration test that hopefully addresses the criteria you described.

One thing that was unexpected is that apparently callback2, which is the one that is dependant on the one that is aborted is never triggered, even on the initial page load. Is this expected? My intuitions are that callback2 should have been run once on the initial page load given that output1 is present in the layout on page load. May there's also an argument for the current behaviour though...

@chriddyp

Copy link
Copy Markdown
Member

Is this expected? My intuitions are that callback2 should have been run once on the initial page load given that output1 is present in the layout on page load.

Yes, I think that this is expected. The argument being that the value that was in the app.layout would, normally, be immediately over written by the callbacks, and therefore aren't strictly necessary in the app.layout.

This looks good to me, thanks for writing the integration test 😄 . Let me know if you are happy with it and then I'll merge and make a new release.

Thanks again for contributing!

@ned2

ned2 commented Jan 19, 2018

Copy link
Copy Markdown
ContributorAuthor

Yep, am happy, put it in!

@chriddyp
chriddyp merged commit a79e7b2 into plotly:masterJan 19, 2018
@rquintino

Copy link
Copy Markdown

Hi @chriddyp@ned2 , I was testing this with a live dcc.Interval and a table component.

My issue, although the table values are not updated, as expected when raising the new exception, it seems to reset/mess up with table selected indexes/sort at each refresh.

Main scenario here was table live update, but refreshing only when data changes.

thx!

import datetime
import sys
import pandas as pd

app = dash.Dash()

DF_SIMPLE = pd.DataFrame({
'x': ['A', 'B', 'C', 'D', 'E', 'F'],
'y': [4, 3, 1, 2, 3, 6],
'z': ['a', 'b', 'c', 'a', 'b', 'c']
})

app.layout = html.Div([
html.H4('DataTable Sample',id="app_title"),
dt.DataTable(
rows=DF_SIMPLE.to_dict('records'),id='datatable-simple'
),
dcc.Interval(id='my-interval', interval=5000),
], className="container")

app.css.append_css({
'external_url': 'https://codepen.io/chriddyp/pen/bWLwgP.css'
})

@app.callback(
Output('datatable-simple', 'rows'),
[Input('my-interval', 'n_intervals')])
def update_table(n):
print("Checking...")
raise dash.exceptions.PreventUpdate("No data changed!")

HammadTheOne added a commit that referenced this pull request Jul 23, 2021
* Updated vignettes and dash-info.yaml
* Update CHANGELOG
* Updated changelog description
@ned2
ned2 deleted the add-prevent-update-exception branch December 11, 2022 05:41
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@ned2@chriddyp@rquintino
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

added PreventUpdate exception and attached handler to Flask server - #190

Merged
chriddyp merged 5 commits into
plotly:masterfrom
ned2:add-prevent-update-exception
Jan 19, 2018
Merged

added PreventUpdate exception and attached handler to Flask server#190
chriddyp merged 5 commits into
plotly:masterfrom
ned2:add-prevent-update-exception

Conversation

@ned2

@ned2ned2 commented Jan 5, 2018

Copy link
Copy Markdown
Contributor

create a new exception PreventUpdate for use when a callback needs to be aborted without updating the state of the client.

This exception is registered with the Flask server using an errorhandler such that on catching this exception Flask will print any message included in the exception to standard error and then return an empty 204 NO CONTENT response.

@ned2

ned2 commented Jan 5, 2018

Copy link
Copy Markdown
ContributorAuthor

Oops, I forgot about Python 2. I hope importing the print function from future is ok. Without this I don't really know of an clean way to print to stderr. But now I think about it, it's not clear this message should be going to stderr. thoughts?

Also, tests are failing because of #189.

@chriddyp

Copy link
Copy Markdown
Member

This looks good to me. I think we should add an integration test in here to lock down the behaviour. These tests would go in here: https://github.com/plotly/dash/blob/308579660b8791296f437bdcd060dc89a42f2b07/tests/test_integration.py. We basically want to check that raising this exception inside a callback will indeed:
1 - Not update the output
2 - Not trigger the output's dependencies

Let me know if you need some help with this!

I hope importing the print function from __future__ is ok

From https://stackoverflow.com/questions/5574702/how-to-print-to-stderr-in-python, it seems OK. In any case, if we add an integration test then we'll know for sure 🙂

@chriddyp

Copy link
Copy Markdown
Member

Also #189 has been merged, so we can rebase this branch off master and the baseline tests should be fixed

@ned2
ned2force-pushed the add-prevent-update-exception branch from 8a46164 to edd2a8cCompareJanuary 13, 2018 01:53
@ned2

ned2 commented Jan 13, 2018

Copy link
Copy Markdown
ContributorAuthor

I rebased off master and tests are passing again :)

I also added an integration test that hopefully addresses the criteria you described.

One thing that was unexpected is that apparently callback2, which is the one that is dependant on the one that is aborted is never triggered, even on the initial page load. Is this expected? My intuitions are that callback2 should have been run once on the initial page load given that output1 is present in the layout on page load. May there's also an argument for the current behaviour though...

@chriddyp

Copy link
Copy Markdown
Member

Is this expected? My intuitions are that callback2 should have been run once on the initial page load given that output1 is present in the layout on page load.

Yes, I think that this is expected. The argument being that the value that was in the app.layout would, normally, be immediately over written by the callbacks, and therefore aren't strictly necessary in the app.layout.

This looks good to me, thanks for writing the integration test 😄 . Let me know if you are happy with it and then I'll merge and make a new release.

Thanks again for contributing!

@ned2

ned2 commented Jan 19, 2018

Copy link
Copy Markdown
ContributorAuthor

Yep, am happy, put it in!

@chriddyp
chriddyp merged commit a79e7b2 into plotly:masterJan 19, 2018
@rquintino

Copy link
Copy Markdown

Hi @chriddyp@ned2 , I was testing this with a live dcc.Interval and a table component.

My issue, although the table values are not updated, as expected when raising the new exception, it seems to reset/mess up with table selected indexes/sort at each refresh.

Main scenario here was table live update, but refreshing only when data changes.

thx!

import datetime
import sys
import pandas as pd

app = dash.Dash()

DF_SIMPLE = pd.DataFrame({
'x': ['A', 'B', 'C', 'D', 'E', 'F'],
'y': [4, 3, 1, 2, 3, 6],
'z': ['a', 'b', 'c', 'a', 'b', 'c']
})

app.layout = html.Div([
html.H4('DataTable Sample',id="app_title"),
dt.DataTable(
rows=DF_SIMPLE.to_dict('records'),id='datatable-simple'
),
dcc.Interval(id='my-interval', interval=5000),
], className="container")

app.css.append_css({
'external_url': 'https://codepen.io/chriddyp/pen/bWLwgP.css'
})

@app.callback(
Output('datatable-simple', 'rows'),
[Input('my-interval', 'n_intervals')])
def update_table(n):
print("Checking...")
raise dash.exceptions.PreventUpdate("No data changed!")

HammadTheOne added a commit that referenced this pull request Jul 23, 2021
* Updated vignettes and dash-info.yaml
* Update CHANGELOG
* Updated changelog description
@ned2
ned2 deleted the add-prevent-update-exception branch December 11, 2022 05:41
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@ned2@chriddyp@rquintino
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

added PreventUpdate exception and attached handler to Flask server - #190

Merged
chriddyp merged 5 commits into
plotly:masterfrom
ned2:add-prevent-update-exception
Jan 19, 2018
Merged

added PreventUpdate exception and attached handler to Flask server#190
chriddyp merged 5 commits into
plotly:masterfrom
ned2:add-prevent-update-exception

Conversation

@ned2

@ned2ned2 commented Jan 5, 2018

Copy link
Copy Markdown
Contributor

create a new exception PreventUpdate for use when a callback needs to be aborted without updating the state of the client.

This exception is registered with the Flask server using an errorhandler such that on catching this exception Flask will print any message included in the exception to standard error and then return an empty 204 NO CONTENT response.

@ned2

ned2 commented Jan 5, 2018

Copy link
Copy Markdown
ContributorAuthor

Oops, I forgot about Python 2. I hope importing the print function from future is ok. Without this I don't really know of an clean way to print to stderr. But now I think about it, it's not clear this message should be going to stderr. thoughts?

Also, tests are failing because of #189.

@chriddyp

Copy link
Copy Markdown
Member

This looks good to me. I think we should add an integration test in here to lock down the behaviour. These tests would go in here: https://github.com/plotly/dash/blob/308579660b8791296f437bdcd060dc89a42f2b07/tests/test_integration.py. We basically want to check that raising this exception inside a callback will indeed:
1 - Not update the output
2 - Not trigger the output's dependencies

Let me know if you need some help with this!

I hope importing the print function from __future__ is ok

From https://stackoverflow.com/questions/5574702/how-to-print-to-stderr-in-python, it seems OK. In any case, if we add an integration test then we'll know for sure 🙂

@chriddyp

Copy link
Copy Markdown
Member

Also #189 has been merged, so we can rebase this branch off master and the baseline tests should be fixed

@ned2
ned2force-pushed the add-prevent-update-exception branch from 8a46164 to edd2a8cCompareJanuary 13, 2018 01:53
@ned2

ned2 commented Jan 13, 2018

Copy link
Copy Markdown
ContributorAuthor

I rebased off master and tests are passing again :)

I also added an integration test that hopefully addresses the criteria you described.

One thing that was unexpected is that apparently callback2, which is the one that is dependant on the one that is aborted is never triggered, even on the initial page load. Is this expected? My intuitions are that callback2 should have been run once on the initial page load given that output1 is present in the layout on page load. May there's also an argument for the current behaviour though...

@chriddyp

Copy link
Copy Markdown
Member

Is this expected? My intuitions are that callback2 should have been run once on the initial page load given that output1 is present in the layout on page load.

Yes, I think that this is expected. The argument being that the value that was in the app.layout would, normally, be immediately over written by the callbacks, and therefore aren't strictly necessary in the app.layout.

This looks good to me, thanks for writing the integration test 😄 . Let me know if you are happy with it and then I'll merge and make a new release.

Thanks again for contributing!

@ned2

ned2 commented Jan 19, 2018

Copy link
Copy Markdown
ContributorAuthor

Yep, am happy, put it in!

@chriddyp
chriddyp merged commit a79e7b2 into plotly:masterJan 19, 2018
@rquintino

Copy link
Copy Markdown

Hi @chriddyp@ned2 , I was testing this with a live dcc.Interval and a table component.

My issue, although the table values are not updated, as expected when raising the new exception, it seems to reset/mess up with table selected indexes/sort at each refresh.

Main scenario here was table live update, but refreshing only when data changes.

thx!

import datetime
import sys
import pandas as pd

app = dash.Dash()

DF_SIMPLE = pd.DataFrame({
'x': ['A', 'B', 'C', 'D', 'E', 'F'],
'y': [4, 3, 1, 2, 3, 6],
'z': ['a', 'b', 'c', 'a', 'b', 'c']
})

app.layout = html.Div([
html.H4('DataTable Sample',id="app_title"),
dt.DataTable(
rows=DF_SIMPLE.to_dict('records'),id='datatable-simple'
),
dcc.Interval(id='my-interval', interval=5000),
], className="container")

app.css.append_css({
'external_url': 'https://codepen.io/chriddyp/pen/bWLwgP.css'
})

@app.callback(
Output('datatable-simple', 'rows'),
[Input('my-interval', 'n_intervals')])
def update_table(n):
print("Checking...")
raise dash.exceptions.PreventUpdate("No data changed!")

HammadTheOne added a commit that referenced this pull request Jul 23, 2021
* Updated vignettes and dash-info.yaml
* Update CHANGELOG
* Updated changelog description
@ned2
ned2 deleted the add-prevent-update-exception branch December 11, 2022 05:41
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@ned2@chriddyp@rquintino
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

added PreventUpdate exception and attached handler to Flask server - #190

Merged
chriddyp merged 5 commits into
plotly:masterfrom
ned2:add-prevent-update-exception
Jan 19, 2018
Merged

added PreventUpdate exception and attached handler to Flask server#190
chriddyp merged 5 commits into
plotly:masterfrom
ned2:add-prevent-update-exception

Conversation

@ned2

@ned2ned2 commented Jan 5, 2018

Copy link
Copy Markdown
Contributor

create a new exception PreventUpdate for use when a callback needs to be aborted without updating the state of the client.

This exception is registered with the Flask server using an errorhandler such that on catching this exception Flask will print any message included in the exception to standard error and then return an empty 204 NO CONTENT response.

@ned2

ned2 commented Jan 5, 2018

Copy link
Copy Markdown
ContributorAuthor

Oops, I forgot about Python 2. I hope importing the print function from future is ok. Without this I don't really know of an clean way to print to stderr. But now I think about it, it's not clear this message should be going to stderr. thoughts?

Also, tests are failing because of #189.

@chriddyp

Copy link
Copy Markdown
Member

This looks good to me. I think we should add an integration test in here to lock down the behaviour. These tests would go in here: https://github.com/plotly/dash/blob/308579660b8791296f437bdcd060dc89a42f2b07/tests/test_integration.py. We basically want to check that raising this exception inside a callback will indeed:
1 - Not update the output
2 - Not trigger the output's dependencies

Let me know if you need some help with this!

I hope importing the print function from __future__ is ok

From https://stackoverflow.com/questions/5574702/how-to-print-to-stderr-in-python, it seems OK. In any case, if we add an integration test then we'll know for sure 🙂

@chriddyp

Copy link
Copy Markdown
Member

Also #189 has been merged, so we can rebase this branch off master and the baseline tests should be fixed

@ned2
ned2force-pushed the add-prevent-update-exception branch from 8a46164 to edd2a8cCompareJanuary 13, 2018 01:53
@ned2

ned2 commented Jan 13, 2018

Copy link
Copy Markdown
ContributorAuthor

I rebased off master and tests are passing again :)

I also added an integration test that hopefully addresses the criteria you described.

One thing that was unexpected is that apparently callback2, which is the one that is dependant on the one that is aborted is never triggered, even on the initial page load. Is this expected? My intuitions are that callback2 should have been run once on the initial page load given that output1 is present in the layout on page load. May there's also an argument for the current behaviour though...

@chriddyp

Copy link
Copy Markdown
Member

Is this expected? My intuitions are that callback2 should have been run once on the initial page load given that output1 is present in the layout on page load.

Yes, I think that this is expected. The argument being that the value that was in the app.layout would, normally, be immediately over written by the callbacks, and therefore aren't strictly necessary in the app.layout.

This looks good to me, thanks for writing the integration test 😄 . Let me know if you are happy with it and then I'll merge and make a new release.

Thanks again for contributing!

@ned2

ned2 commented Jan 19, 2018

Copy link
Copy Markdown
ContributorAuthor

Yep, am happy, put it in!

@chriddyp
chriddyp merged commit a79e7b2 into plotly:masterJan 19, 2018
@rquintino

Copy link
Copy Markdown

Hi @chriddyp@ned2 , I was testing this with a live dcc.Interval and a table component.

My issue, although the table values are not updated, as expected when raising the new exception, it seems to reset/mess up with table selected indexes/sort at each refresh.

Main scenario here was table live update, but refreshing only when data changes.

thx!

import datetime
import sys
import pandas as pd

app = dash.Dash()

DF_SIMPLE = pd.DataFrame({
'x': ['A', 'B', 'C', 'D', 'E', 'F'],
'y': [4, 3, 1, 2, 3, 6],
'z': ['a', 'b', 'c', 'a', 'b', 'c']
})

app.layout = html.Div([
html.H4('DataTable Sample',id="app_title"),
dt.DataTable(
rows=DF_SIMPLE.to_dict('records'),id='datatable-simple'
),
dcc.Interval(id='my-interval', interval=5000),
], className="container")

app.css.append_css({
'external_url': 'https://codepen.io/chriddyp/pen/bWLwgP.css'
})

@app.callback(
Output('datatable-simple', 'rows'),
[Input('my-interval', 'n_intervals')])
def update_table(n):
print("Checking...")
raise dash.exceptions.PreventUpdate("No data changed!")

HammadTheOne added a commit that referenced this pull request Jul 23, 2021
* Updated vignettes and dash-info.yaml
* Update CHANGELOG
* Updated changelog description
@ned2
ned2 deleted the add-prevent-update-exception branch December 11, 2022 05:41
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@ned2@chriddyp@rquintino
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

added PreventUpdate exception and attached handler to Flask server - #190

Merged
chriddyp merged 5 commits into
plotly:masterfrom
ned2:add-prevent-update-exception
Jan 19, 2018
Merged

added PreventUpdate exception and attached handler to Flask server#190
chriddyp merged 5 commits into
plotly:masterfrom
ned2:add-prevent-update-exception

Conversation

@ned2

@ned2ned2 commented Jan 5, 2018

Copy link
Copy Markdown
Contributor

create a new exception PreventUpdate for use when a callback needs to be aborted without updating the state of the client.

This exception is registered with the Flask server using an errorhandler such that on catching this exception Flask will print any message included in the exception to standard error and then return an empty 204 NO CONTENT response.

@ned2

ned2 commented Jan 5, 2018

Copy link
Copy Markdown
ContributorAuthor

Oops, I forgot about Python 2. I hope importing the print function from future is ok. Without this I don't really know of an clean way to print to stderr. But now I think about it, it's not clear this message should be going to stderr. thoughts?

Also, tests are failing because of #189.

@chriddyp

Copy link
Copy Markdown
Member

This looks good to me. I think we should add an integration test in here to lock down the behaviour. These tests would go in here: https://github.com/plotly/dash/blob/308579660b8791296f437bdcd060dc89a42f2b07/tests/test_integration.py. We basically want to check that raising this exception inside a callback will indeed:
1 - Not update the output
2 - Not trigger the output's dependencies

Let me know if you need some help with this!

I hope importing the print function from __future__ is ok

From https://stackoverflow.com/questions/5574702/how-to-print-to-stderr-in-python, it seems OK. In any case, if we add an integration test then we'll know for sure 🙂

@chriddyp

Copy link
Copy Markdown
Member

Also #189 has been merged, so we can rebase this branch off master and the baseline tests should be fixed

@ned2
ned2force-pushed the add-prevent-update-exception branch from 8a46164 to edd2a8cCompareJanuary 13, 2018 01:53
@ned2

ned2 commented Jan 13, 2018

Copy link
Copy Markdown
ContributorAuthor

I rebased off master and tests are passing again :)

I also added an integration test that hopefully addresses the criteria you described.

One thing that was unexpected is that apparently callback2, which is the one that is dependant on the one that is aborted is never triggered, even on the initial page load. Is this expected? My intuitions are that callback2 should have been run once on the initial page load given that output1 is present in the layout on page load. May there's also an argument for the current behaviour though...

@chriddyp

Copy link
Copy Markdown
Member

Is this expected? My intuitions are that callback2 should have been run once on the initial page load given that output1 is present in the layout on page load.

Yes, I think that this is expected. The argument being that the value that was in the app.layout would, normally, be immediately over written by the callbacks, and therefore aren't strictly necessary in the app.layout.

This looks good to me, thanks for writing the integration test 😄 . Let me know if you are happy with it and then I'll merge and make a new release.

Thanks again for contributing!

@ned2

ned2 commented Jan 19, 2018

Copy link
Copy Markdown
ContributorAuthor

Yep, am happy, put it in!

@chriddyp
chriddyp merged commit a79e7b2 into plotly:masterJan 19, 2018
@rquintino

Copy link
Copy Markdown

Hi @chriddyp@ned2 , I was testing this with a live dcc.Interval and a table component.

My issue, although the table values are not updated, as expected when raising the new exception, it seems to reset/mess up with table selected indexes/sort at each refresh.

Main scenario here was table live update, but refreshing only when data changes.

thx!

import datetime
import sys
import pandas as pd

app = dash.Dash()

DF_SIMPLE = pd.DataFrame({
'x': ['A', 'B', 'C', 'D', 'E', 'F'],
'y': [4, 3, 1, 2, 3, 6],
'z': ['a', 'b', 'c', 'a', 'b', 'c']
})

app.layout = html.Div([
html.H4('DataTable Sample',id="app_title"),
dt.DataTable(
rows=DF_SIMPLE.to_dict('records'),id='datatable-simple'
),
dcc.Interval(id='my-interval', interval=5000),
], className="container")

app.css.append_css({
'external_url': 'https://codepen.io/chriddyp/pen/bWLwgP.css'
})

@app.callback(
Output('datatable-simple', 'rows'),
[Input('my-interval', 'n_intervals')])
def update_table(n):
print("Checking...")
raise dash.exceptions.PreventUpdate("No data changed!")

HammadTheOne added a commit that referenced this pull request Jul 23, 2021
* Updated vignettes and dash-info.yaml
* Update CHANGELOG
* Updated changelog description
@ned2
ned2 deleted the add-prevent-update-exception branch December 11, 2022 05:41
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@ned2@chriddyp@rquintino
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

added PreventUpdate exception and attached handler to Flask server - #190

Merged
chriddyp merged 5 commits into
plotly:masterfrom
ned2:add-prevent-update-exception
Jan 19, 2018
Merged

added PreventUpdate exception and attached handler to Flask server#190
chriddyp merged 5 commits into
plotly:masterfrom
ned2:add-prevent-update-exception

Conversation

@ned2

@ned2ned2 commented Jan 5, 2018

Copy link
Copy Markdown
Contributor

create a new exception PreventUpdate for use when a callback needs to be aborted without updating the state of the client.

This exception is registered with the Flask server using an errorhandler such that on catching this exception Flask will print any message included in the exception to standard error and then return an empty 204 NO CONTENT response.

@ned2

ned2 commented Jan 5, 2018

Copy link
Copy Markdown
ContributorAuthor

Oops, I forgot about Python 2. I hope importing the print function from future is ok. Without this I don't really know of an clean way to print to stderr. But now I think about it, it's not clear this message should be going to stderr. thoughts?

Also, tests are failing because of #189.

@chriddyp

Copy link
Copy Markdown
Member

This looks good to me. I think we should add an integration test in here to lock down the behaviour. These tests would go in here: https://github.com/plotly/dash/blob/308579660b8791296f437bdcd060dc89a42f2b07/tests/test_integration.py. We basically want to check that raising this exception inside a callback will indeed:
1 - Not update the output
2 - Not trigger the output's dependencies

Let me know if you need some help with this!

I hope importing the print function from __future__ is ok

From https://stackoverflow.com/questions/5574702/how-to-print-to-stderr-in-python, it seems OK. In any case, if we add an integration test then we'll know for sure 🙂

@chriddyp

Copy link
Copy Markdown
Member

Also #189 has been merged, so we can rebase this branch off master and the baseline tests should be fixed

@ned2
ned2force-pushed the add-prevent-update-exception branch from 8a46164 to edd2a8cCompareJanuary 13, 2018 01:53
@ned2

ned2 commented Jan 13, 2018

Copy link
Copy Markdown
ContributorAuthor

I rebased off master and tests are passing again :)

I also added an integration test that hopefully addresses the criteria you described.

One thing that was unexpected is that apparently callback2, which is the one that is dependant on the one that is aborted is never triggered, even on the initial page load. Is this expected? My intuitions are that callback2 should have been run once on the initial page load given that output1 is present in the layout on page load. May there's also an argument for the current behaviour though...

@chriddyp

Copy link
Copy Markdown
Member

Is this expected? My intuitions are that callback2 should have been run once on the initial page load given that output1 is present in the layout on page load.

Yes, I think that this is expected. The argument being that the value that was in the app.layout would, normally, be immediately over written by the callbacks, and therefore aren't strictly necessary in the app.layout.

This looks good to me, thanks for writing the integration test 😄 . Let me know if you are happy with it and then I'll merge and make a new release.

Thanks again for contributing!

@ned2

ned2 commented Jan 19, 2018

Copy link
Copy Markdown
ContributorAuthor

Yep, am happy, put it in!

@chriddyp
chriddyp merged commit a79e7b2 into plotly:masterJan 19, 2018
@rquintino

Copy link
Copy Markdown

Hi @chriddyp@ned2 , I was testing this with a live dcc.Interval and a table component.

My issue, although the table values are not updated, as expected when raising the new exception, it seems to reset/mess up with table selected indexes/sort at each refresh.

Main scenario here was table live update, but refreshing only when data changes.

thx!

import datetime
import sys
import pandas as pd

app = dash.Dash()

DF_SIMPLE = pd.DataFrame({
'x': ['A', 'B', 'C', 'D', 'E', 'F'],
'y': [4, 3, 1, 2, 3, 6],
'z': ['a', 'b', 'c', 'a', 'b', 'c']
})

app.layout = html.Div([
html.H4('DataTable Sample',id="app_title"),
dt.DataTable(
rows=DF_SIMPLE.to_dict('records'),id='datatable-simple'
),
dcc.Interval(id='my-interval', interval=5000),
], className="container")

app.css.append_css({
'external_url': 'https://codepen.io/chriddyp/pen/bWLwgP.css'
})

@app.callback(
Output('datatable-simple', 'rows'),
[Input('my-interval', 'n_intervals')])
def update_table(n):
print("Checking...")
raise dash.exceptions.PreventUpdate("No data changed!")

HammadTheOne added a commit that referenced this pull request Jul 23, 2021
* Updated vignettes and dash-info.yaml
* Update CHANGELOG
* Updated changelog description
@ned2
ned2 deleted the add-prevent-update-exception branch December 11, 2022 05:41
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@ned2@chriddyp@rquintino
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

added PreventUpdate exception and attached handler to Flask server - #190

Merged
chriddyp merged 5 commits into
plotly:masterfrom
ned2:add-prevent-update-exception
Jan 19, 2018
Merged

added PreventUpdate exception and attached handler to Flask server#190
chriddyp merged 5 commits into
plotly:masterfrom
ned2:add-prevent-update-exception

Conversation

@ned2

@ned2ned2 commented Jan 5, 2018

Copy link
Copy Markdown
Contributor

create a new exception PreventUpdate for use when a callback needs to be aborted without updating the state of the client.

This exception is registered with the Flask server using an errorhandler such that on catching this exception Flask will print any message included in the exception to standard error and then return an empty 204 NO CONTENT response.

@ned2

ned2 commented Jan 5, 2018

Copy link
Copy Markdown
ContributorAuthor

Oops, I forgot about Python 2. I hope importing the print function from future is ok. Without this I don't really know of an clean way to print to stderr. But now I think about it, it's not clear this message should be going to stderr. thoughts?

Also, tests are failing because of #189.

@chriddyp

Copy link
Copy Markdown
Member

This looks good to me. I think we should add an integration test in here to lock down the behaviour. These tests would go in here: https://github.com/plotly/dash/blob/308579660b8791296f437bdcd060dc89a42f2b07/tests/test_integration.py. We basically want to check that raising this exception inside a callback will indeed:
1 - Not update the output
2 - Not trigger the output's dependencies

Let me know if you need some help with this!

I hope importing the print function from __future__ is ok

From https://stackoverflow.com/questions/5574702/how-to-print-to-stderr-in-python, it seems OK. In any case, if we add an integration test then we'll know for sure 🙂

@chriddyp

Copy link
Copy Markdown
Member

Also #189 has been merged, so we can rebase this branch off master and the baseline tests should be fixed

@ned2
ned2force-pushed the add-prevent-update-exception branch from 8a46164 to edd2a8cCompareJanuary 13, 2018 01:53
@ned2

ned2 commented Jan 13, 2018

Copy link
Copy Markdown
ContributorAuthor

I rebased off master and tests are passing again :)

I also added an integration test that hopefully addresses the criteria you described.

One thing that was unexpected is that apparently callback2, which is the one that is dependant on the one that is aborted is never triggered, even on the initial page load. Is this expected? My intuitions are that callback2 should have been run once on the initial page load given that output1 is present in the layout on page load. May there's also an argument for the current behaviour though...

@chriddyp

Copy link
Copy Markdown
Member

Is this expected? My intuitions are that callback2 should have been run once on the initial page load given that output1 is present in the layout on page load.

Yes, I think that this is expected. The argument being that the value that was in the app.layout would, normally, be immediately over written by the callbacks, and therefore aren't strictly necessary in the app.layout.

This looks good to me, thanks for writing the integration test 😄 . Let me know if you are happy with it and then I'll merge and make a new release.

Thanks again for contributing!

@ned2

ned2 commented Jan 19, 2018

Copy link
Copy Markdown
ContributorAuthor

Yep, am happy, put it in!

@chriddyp
chriddyp merged commit a79e7b2 into plotly:masterJan 19, 2018
@rquintino

Copy link
Copy Markdown

Hi @chriddyp@ned2 , I was testing this with a live dcc.Interval and a table component.

My issue, although the table values are not updated, as expected when raising the new exception, it seems to reset/mess up with table selected indexes/sort at each refresh.

Main scenario here was table live update, but refreshing only when data changes.

thx!

import datetime
import sys
import pandas as pd

app = dash.Dash()

DF_SIMPLE = pd.DataFrame({
'x': ['A', 'B', 'C', 'D', 'E', 'F'],
'y': [4, 3, 1, 2, 3, 6],
'z': ['a', 'b', 'c', 'a', 'b', 'c']
})

app.layout = html.Div([
html.H4('DataTable Sample',id="app_title"),
dt.DataTable(
rows=DF_SIMPLE.to_dict('records'),id='datatable-simple'
),
dcc.Interval(id='my-interval', interval=5000),
], className="container")

app.css.append_css({
'external_url': 'https://codepen.io/chriddyp/pen/bWLwgP.css'
})

@app.callback(
Output('datatable-simple', 'rows'),
[Input('my-interval', 'n_intervals')])
def update_table(n):
print("Checking...")
raise dash.exceptions.PreventUpdate("No data changed!")

HammadTheOne added a commit that referenced this pull request Jul 23, 2021
* Updated vignettes and dash-info.yaml
* Update CHANGELOG
* Updated changelog description
@ned2
ned2 deleted the add-prevent-update-exception branch December 11, 2022 05:41
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@ned2@chriddyp@rquintino
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

added PreventUpdate exception and attached handler to Flask server - #190

Merged
chriddyp merged 5 commits into
plotly:masterfrom
ned2:add-prevent-update-exception
Jan 19, 2018
Merged

added PreventUpdate exception and attached handler to Flask server#190
chriddyp merged 5 commits into
plotly:masterfrom
ned2:add-prevent-update-exception

Conversation

@ned2

@ned2ned2 commented Jan 5, 2018

Copy link
Copy Markdown
Contributor

create a new exception PreventUpdate for use when a callback needs to be aborted without updating the state of the client.

This exception is registered with the Flask server using an errorhandler such that on catching this exception Flask will print any message included in the exception to standard error and then return an empty 204 NO CONTENT response.

@ned2

ned2 commented Jan 5, 2018

Copy link
Copy Markdown
ContributorAuthor

Oops, I forgot about Python 2. I hope importing the print function from future is ok. Without this I don't really know of an clean way to print to stderr. But now I think about it, it's not clear this message should be going to stderr. thoughts?

Also, tests are failing because of #189.

@chriddyp

Copy link
Copy Markdown
Member

This looks good to me. I think we should add an integration test in here to lock down the behaviour. These tests would go in here: https://github.com/plotly/dash/blob/308579660b8791296f437bdcd060dc89a42f2b07/tests/test_integration.py. We basically want to check that raising this exception inside a callback will indeed:
1 - Not update the output
2 - Not trigger the output's dependencies

Let me know if you need some help with this!

I hope importing the print function from __future__ is ok

From https://stackoverflow.com/questions/5574702/how-to-print-to-stderr-in-python, it seems OK. In any case, if we add an integration test then we'll know for sure 🙂

@chriddyp

Copy link
Copy Markdown
Member

Also #189 has been merged, so we can rebase this branch off master and the baseline tests should be fixed

@ned2
ned2force-pushed the add-prevent-update-exception branch from 8a46164 to edd2a8cCompareJanuary 13, 2018 01:53
@ned2

ned2 commented Jan 13, 2018

Copy link
Copy Markdown
ContributorAuthor

I rebased off master and tests are passing again :)

I also added an integration test that hopefully addresses the criteria you described.

One thing that was unexpected is that apparently callback2, which is the one that is dependant on the one that is aborted is never triggered, even on the initial page load. Is this expected? My intuitions are that callback2 should have been run once on the initial page load given that output1 is present in the layout on page load. May there's also an argument for the current behaviour though...

@chriddyp

Copy link
Copy Markdown
Member

Is this expected? My intuitions are that callback2 should have been run once on the initial page load given that output1 is present in the layout on page load.

Yes, I think that this is expected. The argument being that the value that was in the app.layout would, normally, be immediately over written by the callbacks, and therefore aren't strictly necessary in the app.layout.

This looks good to me, thanks for writing the integration test 😄 . Let me know if you are happy with it and then I'll merge and make a new release.

Thanks again for contributing!

@ned2

ned2 commented Jan 19, 2018

Copy link
Copy Markdown
ContributorAuthor

Yep, am happy, put it in!

@chriddyp
chriddyp merged commit a79e7b2 into plotly:masterJan 19, 2018
@rquintino

Copy link
Copy Markdown

Hi @chriddyp@ned2 , I was testing this with a live dcc.Interval and a table component.

My issue, although the table values are not updated, as expected when raising the new exception, it seems to reset/mess up with table selected indexes/sort at each refresh.

Main scenario here was table live update, but refreshing only when data changes.

thx!

import datetime
import sys
import pandas as pd

app = dash.Dash()

DF_SIMPLE = pd.DataFrame({
'x': ['A', 'B', 'C', 'D', 'E', 'F'],
'y': [4, 3, 1, 2, 3, 6],
'z': ['a', 'b', 'c', 'a', 'b', 'c']
})

app.layout = html.Div([
html.H4('DataTable Sample',id="app_title"),
dt.DataTable(
rows=DF_SIMPLE.to_dict('records'),id='datatable-simple'
),
dcc.Interval(id='my-interval', interval=5000),
], className="container")

app.css.append_css({
'external_url': 'https://codepen.io/chriddyp/pen/bWLwgP.css'
})

@app.callback(
Output('datatable-simple', 'rows'),
[Input('my-interval', 'n_intervals')])
def update_table(n):
print("Checking...")
raise dash.exceptions.PreventUpdate("No data changed!")

HammadTheOne added a commit that referenced this pull request Jul 23, 2021
* Updated vignettes and dash-info.yaml
* Update CHANGELOG
* Updated changelog description
@ned2
ned2 deleted the add-prevent-update-exception branch December 11, 2022 05:41
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants

@ned2@chriddyp@rquintino