mirror of
https://github.com/pyscript/pyscript.git
synced 2025-12-19 18:27:29 -05:00
Introduce DeprecatedGlobal and show proper warnings (#1014)
* kill the PyScript class and the weird pyscript instance; from the user point of view its functionalities are still available as pyscript.*, but pyscript is not the module, not the instance of PyScript * simplify the code in _set_version_info, while I'm at it * start to implement DeprecatedGlobal * DeprecatedGlobal.__getattr__ * don't show the same warning twice * DeprecatedGlobal.__call__ * make it possible to specify a different warning message for every global * WIP: carefully use DeprecatedGlobal to show reasonable warning messages depending on which name you are accessing to. More names to follow * deprecate more names * deprecate private names * depreacte direct usage of console and document * deprecate the PyScript class * use a better error message * fix test_pyscript.py * introduce a __repr__ for DeprecatedGlobal * add an helper to ensure that we don't show any error or warning on the page * WIP: ensure that examples don't use depreacted features. Many tests are failing * don't deprecate Element * don't use the global micropip to install packages, else we trigger a warning * use a better error message for micropip * fix test_todo_pylist to avoid using deprecated globals * fix test_webgl_raycaster * fix tests * make HTML globally available * add MIME_RENDERERS and MIME_METHODS * fix the typing of Micropip, thanks to @FabioRosado
This commit is contained in:
@@ -1,7 +1,9 @@
|
|||||||
from datetime import datetime as dt
|
from datetime import datetime as dt
|
||||||
|
|
||||||
|
import pyscript
|
||||||
|
|
||||||
class PyItem(PyItemTemplate):
|
|
||||||
|
class PyItem(pyscript.PyItemTemplate):
|
||||||
def on_click(self, evt=None):
|
def on_click(self, evt=None):
|
||||||
self.data["done"] = not self.data["done"]
|
self.data["done"] = not self.data["done"]
|
||||||
self.strike(self.data["done"])
|
self.strike(self.data["done"])
|
||||||
@@ -9,7 +11,7 @@ class PyItem(PyItemTemplate):
|
|||||||
self.select("input").element.checked = self.data["done"]
|
self.select("input").element.checked = self.data["done"]
|
||||||
|
|
||||||
|
|
||||||
class PyList(PyListTemplate):
|
class PyList(pyscript.PyListTemplate):
|
||||||
item_class = PyItem
|
item_class = PyItem
|
||||||
|
|
||||||
def add(self, item):
|
def add(self, item):
|
||||||
|
|||||||
@@ -124,16 +124,17 @@
|
|||||||
<p>pylist.py</p>
|
<p>pylist.py</p>
|
||||||
<pre class="prism-code language-python">
|
<pre class="prism-code language-python">
|
||||||
<code class="language-python">
|
<code class="language-python">
|
||||||
|
import pyscript
|
||||||
from datetime import datetime as dt
|
from datetime import datetime as dt
|
||||||
|
|
||||||
class PyItem(PyItemTemplate):
|
class PyItem(pyscript.PyItemTemplate):
|
||||||
def on_click(self, evt=None):
|
def on_click(self, evt=None):
|
||||||
self.data["done"] = not self.data["done"]
|
self.data["done"] = not self.data["done"]
|
||||||
self.strike(self.data["done"])
|
self.strike(self.data["done"])
|
||||||
|
|
||||||
self.select("input").element.checked = self.data["done"]
|
self.select("input").element.checked = self.data["done"]
|
||||||
|
|
||||||
class PyList(PyListTemplate):
|
class PyList(pyscript.PyListTemplate):
|
||||||
item_class = PyItem
|
item_class = PyItem
|
||||||
|
|
||||||
def add(self, item):
|
def add(self, item):
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ from js import Math
|
|||||||
from js import THREE
|
from js import THREE
|
||||||
from js import performance
|
from js import performance
|
||||||
from js import Object
|
from js import Object
|
||||||
|
from js import document
|
||||||
import asyncio
|
import asyncio
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -210,22 +210,21 @@ export class PyScriptApp {
|
|||||||
//Refresh the module cache so Python consistently finds pyscript module
|
//Refresh the module cache so Python consistently finds pyscript module
|
||||||
runtime.invalidate_module_path_cache()
|
runtime.invalidate_module_path_cache()
|
||||||
|
|
||||||
// inject `define_custom_element` it into the PyScript module scope
|
// inject `define_custom_element` and showWarning it into the PyScript
|
||||||
|
// module scope
|
||||||
const pyscript_module = runtime.interpreter.pyimport('pyscript');
|
const pyscript_module = runtime.interpreter.pyimport('pyscript');
|
||||||
pyscript_module.define_custom_element = define_custom_element;
|
pyscript_module.define_custom_element = define_custom_element;
|
||||||
pyscript_module.PyScript.set_version_info(version);
|
pyscript_module.showWarning = showWarning;
|
||||||
|
pyscript_module._set_version_info(version);
|
||||||
pyscript_module.destroy();
|
pyscript_module.destroy();
|
||||||
|
|
||||||
// TODO: Currently adding the imports for backwards compatibility, we should
|
// import some carefully selected names into the global namespace
|
||||||
// remove it
|
|
||||||
await runtime.run(`
|
await runtime.run(`
|
||||||
from pyscript import *
|
import js
|
||||||
`);
|
import pyscript
|
||||||
logger.warn(`DEPRECATION WARNING: 'micropip', 'Element', 'console', 'document' and several other \
|
from pyscript import Element, display, HTML
|
||||||
objects form the pyscript module (with the exception of 'display') will be \
|
pyscript._install_deprecated_globals_2022_12_1(globals())
|
||||||
be removed from the Python global namespace in the following release. \
|
`)
|
||||||
To avoid errors in future releases use import from pyscript instead. For instance: \
|
|
||||||
from pyscript import micropip, Element, console, document`);
|
|
||||||
|
|
||||||
if (this.config.packages) {
|
if (this.config.packages) {
|
||||||
logger.info('Packages to install: ', this.config.packages);
|
logger.info('Packages to install: ', this.config.packages);
|
||||||
|
|||||||
@@ -6,15 +6,14 @@ import type { Runtime } from './runtime';
|
|||||||
const logger = getLogger('pyexec');
|
const logger = getLogger('pyexec');
|
||||||
|
|
||||||
export function pyExec(runtime: Runtime, pysrc: string, outElem: HTMLElement) {
|
export function pyExec(runtime: Runtime, pysrc: string, outElem: HTMLElement) {
|
||||||
// this is the python function defined in pyscript.py
|
//This is pyscript.py
|
||||||
const set_current_display_target = runtime.globals.get('set_current_display_target');
|
const pyscript_py = runtime.interpreter.pyimport('pyscript');
|
||||||
|
|
||||||
ensureUniqueId(outElem);
|
ensureUniqueId(outElem);
|
||||||
set_current_display_target(outElem.id);
|
pyscript_py.set_current_display_target(outElem.id);
|
||||||
//This is the python function defined in pyscript.py
|
|
||||||
const usesTopLevelAwait = runtime.globals.get('uses_top_level_await');
|
|
||||||
try {
|
try {
|
||||||
try {
|
try {
|
||||||
if (usesTopLevelAwait(pysrc)) {
|
if (pyscript_py.uses_top_level_await(pysrc)) {
|
||||||
throw new UserError(
|
throw new UserError(
|
||||||
ErrorCode.TOP_LEVEL_AWAIT,
|
ErrorCode.TOP_LEVEL_AWAIT,
|
||||||
'The use of top-level "await", "async for", and ' +
|
'The use of top-level "await", "async for", and ' +
|
||||||
@@ -33,7 +32,8 @@ export function pyExec(runtime: Runtime, pysrc: string, outElem: HTMLElement) {
|
|||||||
displayPyException(err, outElem);
|
displayPyException(err, outElem);
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
set_current_display_target(undefined);
|
pyscript_py.set_current_display_target(undefined);
|
||||||
|
pyscript_py.destroy();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ declare const loadPyodide: typeof loadPyodideDeclaration;
|
|||||||
|
|
||||||
const logger = getLogger('pyscript/pyodide');
|
const logger = getLogger('pyscript/pyodide');
|
||||||
|
|
||||||
interface Micropip {
|
interface Micropip extends PyProxy {
|
||||||
install: (packageName: string | string[]) => Promise<void>;
|
install: (packageName: string | string[]) => Promise<void>;
|
||||||
destroy: () => void;
|
destroy: () => void;
|
||||||
}
|
}
|
||||||
@@ -91,7 +91,8 @@ export class PyodideRuntime extends Runtime {
|
|||||||
async installPackage(package_name: string | string[]): Promise<void> {
|
async installPackage(package_name: string | string[]): Promise<void> {
|
||||||
if (package_name.length > 0) {
|
if (package_name.length > 0) {
|
||||||
logger.info(`micropip install ${package_name.toString()}`);
|
logger.info(`micropip install ${package_name.toString()}`);
|
||||||
const micropip = this.globals.get('micropip') as Micropip;
|
|
||||||
|
const micropip = this.interpreter.pyimport('micropip') as Micropip;
|
||||||
try {
|
try {
|
||||||
await micropip.install(package_name);
|
await micropip.install(package_name);
|
||||||
micropip.destroy();
|
micropip.destroy();
|
||||||
|
|||||||
@@ -8,8 +8,7 @@ import time
|
|||||||
from collections import namedtuple
|
from collections import namedtuple
|
||||||
from textwrap import dedent
|
from textwrap import dedent
|
||||||
|
|
||||||
import micropip # noqa: F401
|
import js
|
||||||
from js import console, document
|
|
||||||
|
|
||||||
try:
|
try:
|
||||||
from pyodide import create_proxy
|
from pyodide import create_proxy
|
||||||
@@ -68,6 +67,41 @@ MIME_RENDERERS = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# these are set by _set_version_info
|
||||||
|
__version__ = None
|
||||||
|
version_info = None
|
||||||
|
|
||||||
|
|
||||||
|
def _set_version_info(version_from_runtime: str):
|
||||||
|
"""Sets the __version__ and version_info properties from provided JSON data
|
||||||
|
Args:
|
||||||
|
version_from_runtime (str): A "dotted" representation of the version:
|
||||||
|
YYYY.MM.m(m).releaselevel
|
||||||
|
Year, Month, and Minor should be integers; releaselevel can be any string
|
||||||
|
"""
|
||||||
|
global __version__
|
||||||
|
global version_info
|
||||||
|
|
||||||
|
__version__ = version_from_runtime
|
||||||
|
|
||||||
|
version_parts = version_from_runtime.split(".")
|
||||||
|
year = int(version_parts[0])
|
||||||
|
month = int(version_parts[1])
|
||||||
|
minor = int(version_parts[2])
|
||||||
|
if len(version_parts) > 3:
|
||||||
|
releaselevel = version_parts[3]
|
||||||
|
else:
|
||||||
|
releaselevel = ""
|
||||||
|
|
||||||
|
VersionInfo = namedtuple("version_info", ("year", "month", "minor", "releaselevel"))
|
||||||
|
version_info = VersionInfo(year, month, minor, releaselevel)
|
||||||
|
|
||||||
|
# we ALSO set PyScript.__version__ and version_info for backwards
|
||||||
|
# compatibility. Should be killed eventually.
|
||||||
|
PyScript.__version__ = __version__
|
||||||
|
PyScript.version_info = version_info
|
||||||
|
|
||||||
|
|
||||||
class HTML:
|
class HTML:
|
||||||
"""
|
"""
|
||||||
Wrap a string so that display() can render it as plain HTML
|
Wrap a string so that display() can render it as plain HTML
|
||||||
@@ -126,7 +160,7 @@ def format_mime(obj):
|
|||||||
break
|
break
|
||||||
if output is None:
|
if output is None:
|
||||||
if not_available:
|
if not_available:
|
||||||
console.warn(
|
js.console.warn(
|
||||||
f"Rendered object requested unavailable MIME renderers: {not_available}"
|
f"Rendered object requested unavailable MIME renderers: {not_available}"
|
||||||
)
|
)
|
||||||
output = repr(output)
|
output = repr(output)
|
||||||
@@ -138,18 +172,16 @@ def format_mime(obj):
|
|||||||
return MIME_RENDERERS[mime_type](output, meta), mime_type
|
return MIME_RENDERERS[mime_type](output, meta), mime_type
|
||||||
|
|
||||||
|
|
||||||
class PyScript:
|
@staticmethod
|
||||||
loop = loop
|
def run_until_complete(f):
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def run_until_complete(f):
|
|
||||||
_ = loop.run_until_complete(f)
|
_ = loop.run_until_complete(f)
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def write(element_id, value, append=False, exec_id=0):
|
@staticmethod
|
||||||
|
def write(element_id, value, append=False, exec_id=0):
|
||||||
"""Writes value to the element with id "element_id"""
|
"""Writes value to the element with id "element_id"""
|
||||||
Element(element_id).write(value=value, append=append)
|
Element(element_id).write(value=value, append=append)
|
||||||
console.warn(
|
js.console.warn(
|
||||||
dedent(
|
dedent(
|
||||||
"""PyScript Deprecation Warning: PyScript.write is
|
"""PyScript Deprecation Warning: PyScript.write is
|
||||||
marked as deprecated and will be removed sometime soon. Please, use
|
marked as deprecated and will be removed sometime soon. Please, use
|
||||||
@@ -157,39 +189,6 @@ class PyScript:
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def set_version_info(cls, version_from_runtime: str):
|
|
||||||
"""Sets the __version__ and version_info properties from provided JSON data
|
|
||||||
Args:
|
|
||||||
version_from_runtime (str): A "dotted" representation of the version:
|
|
||||||
YYYY.MM.m(m).releaselevel
|
|
||||||
Year, Month, and Minor should be integers; releaselevel can be any string
|
|
||||||
"""
|
|
||||||
|
|
||||||
# __version__ is the same string from runtime.ts
|
|
||||||
cls.__version__ = version_from_runtime
|
|
||||||
|
|
||||||
# version_info is namedtuple: (year, month, minor, releaselevel)
|
|
||||||
version_parts = version_from_runtime.split(".")
|
|
||||||
version_dict = {
|
|
||||||
"year": int(version_parts[0]),
|
|
||||||
"month": int(version_parts[1]),
|
|
||||||
"minor": int(version_parts[2]),
|
|
||||||
}
|
|
||||||
|
|
||||||
# If the version only has three parts (e.g. 2022.09.1), let the releaselevel be ""
|
|
||||||
try:
|
|
||||||
version_dict["releaselevel"] = version_parts[3]
|
|
||||||
except IndexError:
|
|
||||||
version_dict["releaselevel"] = ""
|
|
||||||
|
|
||||||
# Format mimics sys.version_info
|
|
||||||
_VersionInfo = namedtuple("version_info", version_dict.keys())
|
|
||||||
cls.version_info = _VersionInfo(**version_dict)
|
|
||||||
|
|
||||||
# tidy up class namespace
|
|
||||||
del cls.set_version_info
|
|
||||||
|
|
||||||
|
|
||||||
def set_current_display_target(target_id):
|
def set_current_display_target(target_id):
|
||||||
get_current_display_target._id = target_id
|
get_current_display_target._id = target_id
|
||||||
@@ -231,7 +230,7 @@ class Element:
|
|||||||
def element(self):
|
def element(self):
|
||||||
"""Return the dom element"""
|
"""Return the dom element"""
|
||||||
if not self._element:
|
if not self._element:
|
||||||
self._element = document.querySelector(f"#{self._id}")
|
self._element = js.document.querySelector(f"#{self._id}")
|
||||||
return self._element
|
return self._element
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@@ -248,7 +247,7 @@ class Element:
|
|||||||
return
|
return
|
||||||
|
|
||||||
if append:
|
if append:
|
||||||
child = document.createElement("div")
|
child = js.document.createElement("div")
|
||||||
self.element.appendChild(child)
|
self.element.appendChild(child)
|
||||||
|
|
||||||
if self.element.children:
|
if self.element.children:
|
||||||
@@ -257,7 +256,7 @@ class Element:
|
|||||||
out_element = self.element
|
out_element = self.element
|
||||||
|
|
||||||
if mime_type in ("application/javascript", "text/html"):
|
if mime_type in ("application/javascript", "text/html"):
|
||||||
script_element = document.createRange().createContextualFragment(html)
|
script_element = js.document.createRange().createContextualFragment(html)
|
||||||
out_element.appendChild(script_element)
|
out_element.appendChild(script_element)
|
||||||
else:
|
else:
|
||||||
out_element.innerHTML = html
|
out_element.innerHTML = html
|
||||||
@@ -278,7 +277,7 @@ class Element:
|
|||||||
if _el:
|
if _el:
|
||||||
return Element(_el.id, _el)
|
return Element(_el.id, _el)
|
||||||
else:
|
else:
|
||||||
console.warn(f"WARNING: can't find element matching query {query}")
|
js.console.warn(f"WARNING: can't find element matching query {query}")
|
||||||
|
|
||||||
def clone(self, new_id=None, to=None):
|
def clone(self, new_id=None, to=None):
|
||||||
if new_id is None:
|
if new_id is None:
|
||||||
@@ -318,7 +317,7 @@ def add_classes(element, class_list):
|
|||||||
|
|
||||||
|
|
||||||
def create(what, id_=None, classes=""):
|
def create(what, id_=None, classes=""):
|
||||||
element = document.createElement(what)
|
element = js.document.createElement(what)
|
||||||
if id_:
|
if id_:
|
||||||
element.id = id_
|
element.id = id_
|
||||||
add_classes(element, classes)
|
add_classes(element, classes)
|
||||||
@@ -432,7 +431,7 @@ class PyListTemplate:
|
|||||||
Element(new_id).element.onclick = foo
|
Element(new_id).element.onclick = foo
|
||||||
|
|
||||||
def connect(self):
|
def connect(self):
|
||||||
self.md = main_div = document.createElement("div")
|
self.md = main_div = js.document.createElement("div")
|
||||||
main_div.id = self._id + "-list-tasks-container"
|
main_div.id = self._id + "-list-tasks-container"
|
||||||
|
|
||||||
if self.theme:
|
if self.theme:
|
||||||
@@ -502,7 +501,7 @@ class Plugin:
|
|||||||
|
|
||||||
def register_custom_element(self, tag):
|
def register_custom_element(self, tag):
|
||||||
# TODO: Ideally would be better to use the logger.
|
# TODO: Ideally would be better to use the logger.
|
||||||
console.info(f"Defining new custom element {tag}")
|
js.console.info(f"Defining new custom element {tag}")
|
||||||
|
|
||||||
def wrapper(class_):
|
def wrapper(class_):
|
||||||
# TODO: this is very pyodide specific but will have to do
|
# TODO: this is very pyodide specific but will have to do
|
||||||
@@ -512,4 +511,158 @@ class Plugin:
|
|||||||
return create_proxy(wrapper)
|
return create_proxy(wrapper)
|
||||||
|
|
||||||
|
|
||||||
pyscript = PyScript()
|
class DeprecatedGlobal:
|
||||||
|
"""
|
||||||
|
Proxy for globals which are deprecated.
|
||||||
|
|
||||||
|
The intendend usage is as follows:
|
||||||
|
|
||||||
|
# in the global namespace
|
||||||
|
Element = pyscript.DeprecatedGlobal('Element', pyscript.Element, "...")
|
||||||
|
console = pyscript.DeprecatedGlobal('console', js.console, "...")
|
||||||
|
...
|
||||||
|
|
||||||
|
The proxy forwards __getattr__ and __call__ to the underlying object, and
|
||||||
|
emit a warning on the first usage.
|
||||||
|
|
||||||
|
This way users see a warning only if they actually access the top-level
|
||||||
|
name.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, name, obj, message):
|
||||||
|
self.__name = name
|
||||||
|
self.__obj = obj
|
||||||
|
self.__message = message
|
||||||
|
self.__warning_already_shown = False
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return f"<DeprecatedGlobal({self.__name!r})>"
|
||||||
|
|
||||||
|
def _show_warning(self, message):
|
||||||
|
"""
|
||||||
|
NOTE: this is overridden by unit tests
|
||||||
|
"""
|
||||||
|
# this showWarning is implemented in js and injected into this
|
||||||
|
# namespace by main.ts
|
||||||
|
showWarning(message, "html") # noqa: F821
|
||||||
|
|
||||||
|
def _show_warning_maybe(self):
|
||||||
|
if self.__warning_already_shown:
|
||||||
|
return
|
||||||
|
self._show_warning(self.__message)
|
||||||
|
self.__warning_already_shown = True
|
||||||
|
|
||||||
|
def __getattr__(self, attr):
|
||||||
|
self._show_warning_maybe()
|
||||||
|
return getattr(self.__obj, attr)
|
||||||
|
|
||||||
|
def __call__(self, *args, **kwargs):
|
||||||
|
self._show_warning_maybe()
|
||||||
|
return self.__obj(*args, **kwargs)
|
||||||
|
|
||||||
|
def __iter__(self):
|
||||||
|
self._show_warning_maybe()
|
||||||
|
return iter(self.__obj)
|
||||||
|
|
||||||
|
def __getitem__(self, key):
|
||||||
|
self._show_warning_maybe()
|
||||||
|
return self.__obj[key]
|
||||||
|
|
||||||
|
def __setitem__(self, key, value):
|
||||||
|
self._show_warning_maybe()
|
||||||
|
self.__obj[key] = value
|
||||||
|
|
||||||
|
|
||||||
|
class PyScript:
|
||||||
|
"""
|
||||||
|
This class is deprecated since 2022.12.1.
|
||||||
|
|
||||||
|
All its old functionalities are available as module-level functions. This
|
||||||
|
class should be killed eventually.
|
||||||
|
"""
|
||||||
|
|
||||||
|
loop = loop
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def run_until_complete(f):
|
||||||
|
run_until_complete(f)
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def write(element_id, value, append=False, exec_id=0):
|
||||||
|
write(element_id, value, append, exec_id)
|
||||||
|
|
||||||
|
|
||||||
|
def _install_deprecated_globals_2022_12_1(ns):
|
||||||
|
"""
|
||||||
|
Install into the given namespace all the globals which have been
|
||||||
|
deprecated since the 2022.12.1 release. Eventually they should be killed.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def deprecate(name, obj, instead):
|
||||||
|
message = f"Direct usage of <code>{name}</code> is deprecated. " + instead
|
||||||
|
ns[name] = DeprecatedGlobal(name, obj, message)
|
||||||
|
|
||||||
|
# function/classes defined in pyscript.py ===> pyscript.XXX
|
||||||
|
pyscript_names = [
|
||||||
|
"PyItemTemplate",
|
||||||
|
"PyListTemplate",
|
||||||
|
"PyWidgetTheme",
|
||||||
|
"add_classes",
|
||||||
|
"create",
|
||||||
|
"loop",
|
||||||
|
]
|
||||||
|
for name in pyscript_names:
|
||||||
|
deprecate(
|
||||||
|
name, globals()[name], f"Please use <code>pyscript.{name}</code> instead."
|
||||||
|
)
|
||||||
|
|
||||||
|
# stdlib modules ===> import XXX
|
||||||
|
stdlib_names = [
|
||||||
|
"asyncio",
|
||||||
|
"base64",
|
||||||
|
"io",
|
||||||
|
"sys",
|
||||||
|
"time",
|
||||||
|
"datetime",
|
||||||
|
"pyodide",
|
||||||
|
"micropip",
|
||||||
|
]
|
||||||
|
for name in stdlib_names:
|
||||||
|
obj = __import__(name)
|
||||||
|
deprecate(name, obj, f"Please use <code>import {name}</code> instead.")
|
||||||
|
|
||||||
|
# special case
|
||||||
|
deprecate(
|
||||||
|
"dedent", dedent, "Please use <code>from textwrap import dedent</code> instead."
|
||||||
|
)
|
||||||
|
|
||||||
|
# these are names that used to leak in the globals but they are just
|
||||||
|
# implementation details. People should not use them.
|
||||||
|
private_names = [
|
||||||
|
"eval_formatter",
|
||||||
|
"format_mime",
|
||||||
|
"identity",
|
||||||
|
"render_image",
|
||||||
|
"MIME_RENDERERS",
|
||||||
|
"MIME_METHODS",
|
||||||
|
]
|
||||||
|
for name in private_names:
|
||||||
|
obj = globals()[name]
|
||||||
|
message = (
|
||||||
|
f"<code>{name}</code> is deprecated. "
|
||||||
|
"This is a private implementation detail of pyscript. "
|
||||||
|
"You should not use it."
|
||||||
|
)
|
||||||
|
ns[name] = DeprecatedGlobal(name, obj, message)
|
||||||
|
|
||||||
|
# these names are available as js.XXX
|
||||||
|
for name in ["document", "console"]:
|
||||||
|
obj = getattr(js, name)
|
||||||
|
deprecate(name, obj, f"Please use <code>js.{name}</code> instead.")
|
||||||
|
|
||||||
|
# PyScript is special, use a different message
|
||||||
|
message = (
|
||||||
|
"The <code>PyScript</code> object is deprecated. "
|
||||||
|
"Please use <code>pyscript</code> instead."
|
||||||
|
)
|
||||||
|
ns["PyScript"] = DeprecatedGlobal("PyScript", PyScript, message)
|
||||||
|
|||||||
@@ -292,6 +292,17 @@ class PyScriptTest:
|
|||||||
elems = [loc.nth(i) for i in range(n)]
|
elems = [loc.nth(i) for i in range(n)]
|
||||||
return iter(elems)
|
return iter(elems)
|
||||||
|
|
||||||
|
def assert_no_banners(self):
|
||||||
|
"""
|
||||||
|
Ensure that there are no alert banners on the page, which are used for
|
||||||
|
errors and warnings. Raise AssertionError if any if found.
|
||||||
|
"""
|
||||||
|
loc = self.page.locator(".alert-banner")
|
||||||
|
n = loc.count()
|
||||||
|
if n > 0:
|
||||||
|
text = "\n".join(loc.all_inner_texts())
|
||||||
|
raise AssertionError(f"Found {n} alert banners:\n" + text)
|
||||||
|
|
||||||
|
|
||||||
# ============== Helpers and utility functions ==============
|
# ============== Helpers and utility functions ==============
|
||||||
|
|
||||||
|
|||||||
@@ -214,8 +214,8 @@ class TestBasic(PyScriptTest):
|
|||||||
"""
|
"""
|
||||||
<py-script>
|
<py-script>
|
||||||
import js
|
import js
|
||||||
js.console.log(PyScript.__version__)
|
js.console.log(pyscript.__version__)
|
||||||
js.console.log(str(PyScript.version_info))
|
js.console.log(str(pyscript.version_info))
|
||||||
</py-script>
|
</py-script>
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
@@ -232,27 +232,47 @@ class TestBasic(PyScriptTest):
|
|||||||
is not None
|
is not None
|
||||||
)
|
)
|
||||||
|
|
||||||
def test_python_modules_deprecated(self):
|
def test_assert_no_banners(self):
|
||||||
# GIVEN a py-script tag
|
"""
|
||||||
|
Test that the DOM doesn't contain error/warning banners
|
||||||
|
"""
|
||||||
self.pyscript_run(
|
self.pyscript_run(
|
||||||
"""
|
"""
|
||||||
<py-script>
|
<py-script>
|
||||||
print('hello pyscript')
|
import pyscript
|
||||||
raise Exception('this is an error')
|
pyscript.showWarning("hello")
|
||||||
|
pyscript.showWarning("world")
|
||||||
</py-script>
|
</py-script>
|
||||||
"""
|
"""
|
||||||
)
|
)
|
||||||
# TODO: Adding a quick check that the deprecation warning is logged. Not spending
|
with pytest.raises(AssertionError, match="Found 2 alert banners"):
|
||||||
# to much time to make it perfect since we'll remove this right after the
|
self.assert_no_banners()
|
||||||
# release. (Anyone wanting to improve it, please feel free to)
|
|
||||||
warning_msg = (
|
def test_deprecated_globals(self):
|
||||||
"[pyscript/main] DEPRECATION WARNING: 'micropip', 'Element', 'console', 'document' "
|
self.pyscript_run(
|
||||||
"and several other objects form the pyscript module (with the exception of 'display') "
|
"""
|
||||||
"will be be removed from the Python global namespace in the following release. "
|
<py-script>
|
||||||
"To avoid errors in future releases use import from pyscript "
|
# trigger various warnings
|
||||||
"instead. For instance: from pyscript import micropip, Element, "
|
create("div", classes="a b c")
|
||||||
"console, document"
|
assert sys.__name__ == 'sys'
|
||||||
|
dedent("")
|
||||||
|
format_mime("")
|
||||||
|
assert MIME_RENDERERS['text/html'] is not None
|
||||||
|
console.log("hello")
|
||||||
|
PyScript.loop
|
||||||
|
</py-script>
|
||||||
|
|
||||||
|
<div id="mydiv"></div>
|
||||||
|
"""
|
||||||
)
|
)
|
||||||
# we EXPECTED to find a deprecation warning about what will be removed from the Python
|
banner = self.page.locator(".py-warning")
|
||||||
# global namespace in the next releases
|
messages = banner.all_inner_texts()
|
||||||
assert warning_msg in self.console.warning.lines
|
assert messages == [
|
||||||
|
"The PyScript object is deprecated. Please use pyscript instead.",
|
||||||
|
"Direct usage of console is deprecated. Please use js.console instead.",
|
||||||
|
"MIME_RENDERERS is deprecated. This is a private implementation detail of pyscript. You should not use it.", # noqa: E501
|
||||||
|
"format_mime is deprecated. This is a private implementation detail of pyscript. You should not use it.", # noqa: E501
|
||||||
|
"Direct usage of dedent is deprecated. Please use from textwrap import dedent instead.",
|
||||||
|
"Direct usage of sys is deprecated. Please use import sys instead.",
|
||||||
|
"Direct usage of create is deprecated. Please use pyscript.create instead.",
|
||||||
|
]
|
||||||
|
|||||||
@@ -2,7 +2,8 @@ from .support import PyScriptTest
|
|||||||
|
|
||||||
# Source code of a simple plugin that creates a Custom Element for testing purposes
|
# Source code of a simple plugin that creates a Custom Element for testing purposes
|
||||||
CE_PLUGIN_CODE = """
|
CE_PLUGIN_CODE = """
|
||||||
from pyscript import Plugin, console
|
from pyscript import Plugin
|
||||||
|
from js import console
|
||||||
|
|
||||||
plugin = Plugin('py-upper')
|
plugin = Plugin('py-upper')
|
||||||
|
|
||||||
@@ -20,7 +21,8 @@ class Upper:
|
|||||||
|
|
||||||
# Source of a plugin hooks into the PyScript App lifecycle events
|
# Source of a plugin hooks into the PyScript App lifecycle events
|
||||||
HOOKS_PLUGIN_CODE = """
|
HOOKS_PLUGIN_CODE = """
|
||||||
from pyscript import Plugin, console
|
from pyscript import Plugin
|
||||||
|
from js import console
|
||||||
|
|
||||||
class TestLogger(Plugin):
|
class TestLogger(Plugin):
|
||||||
def configure(self, config):
|
def configure(self, config):
|
||||||
@@ -44,7 +46,8 @@ plugin = TestLogger()
|
|||||||
|
|
||||||
# Source of a script that doesn't call define a `plugin` attribute
|
# Source of a script that doesn't call define a `plugin` attribute
|
||||||
NO_PLUGIN_CODE = """
|
NO_PLUGIN_CODE = """
|
||||||
from pyscript import Plugin, console
|
from pyscript import Plugin
|
||||||
|
from js import console
|
||||||
|
|
||||||
class TestLogger(Plugin):
|
class TestLogger(Plugin):
|
||||||
pass
|
pass
|
||||||
@@ -52,7 +55,8 @@ class TestLogger(Plugin):
|
|||||||
|
|
||||||
# Source code of a simple plugin that creates a Custom Element for testing purposes
|
# Source code of a simple plugin that creates a Custom Element for testing purposes
|
||||||
CODE_CE_PLUGIN_BAD_RETURNS = """
|
CODE_CE_PLUGIN_BAD_RETURNS = """
|
||||||
from pyscript import Plugin, console
|
from pyscript import Plugin
|
||||||
|
from js import console
|
||||||
|
|
||||||
plugin = Plugin('py-broken')
|
plugin = Plugin('py-broken')
|
||||||
|
|
||||||
|
|||||||
@@ -60,6 +60,7 @@ class TestExamples(PyScriptTest):
|
|||||||
content = self.page.content()
|
content = self.page.content()
|
||||||
pattern = "\\d+/\\d+/\\d+, \\d+:\\d+:\\d+" # e.g. 08/09/2022 15:57:32
|
pattern = "\\d+/\\d+/\\d+, \\d+:\\d+:\\d+" # e.g. 08/09/2022 15:57:32
|
||||||
assert re.search(pattern, content)
|
assert re.search(pattern, content)
|
||||||
|
self.assert_no_banners()
|
||||||
|
|
||||||
def test_simple_clock(self):
|
def test_simple_clock(self):
|
||||||
self.goto("examples/simple_clock.html")
|
self.goto("examples/simple_clock.html")
|
||||||
@@ -77,6 +78,7 @@ class TestExamples(PyScriptTest):
|
|||||||
time.sleep(1)
|
time.sleep(1)
|
||||||
else:
|
else:
|
||||||
assert False, "Espresso time not found :("
|
assert False, "Espresso time not found :("
|
||||||
|
self.assert_no_banners()
|
||||||
|
|
||||||
def test_altair(self):
|
def test_altair(self):
|
||||||
self.goto("examples/altair.html")
|
self.goto("examples/altair.html")
|
||||||
@@ -94,6 +96,7 @@ class TestExamples(PyScriptTest):
|
|||||||
# Let's confirm that the links are visible now after clicking the menu
|
# Let's confirm that the links are visible now after clicking the menu
|
||||||
assert save_as_png_link.is_visible()
|
assert save_as_png_link.is_visible()
|
||||||
assert see_source_link.is_visible()
|
assert see_source_link.is_visible()
|
||||||
|
self.assert_no_banners()
|
||||||
|
|
||||||
def test_bokeh(self):
|
def test_bokeh(self):
|
||||||
# XXX improve this test
|
# XXX improve this test
|
||||||
@@ -101,6 +104,7 @@ class TestExamples(PyScriptTest):
|
|||||||
self.wait_for_pyscript()
|
self.wait_for_pyscript()
|
||||||
assert self.page.title() == "Bokeh Example"
|
assert self.page.title() == "Bokeh Example"
|
||||||
wait_for_render(self.page, "*", '<div.*class=\\"bk\\".*>')
|
wait_for_render(self.page, "*", '<div.*class=\\"bk\\".*>')
|
||||||
|
self.assert_no_banners()
|
||||||
|
|
||||||
def test_bokeh_interactive(self):
|
def test_bokeh_interactive(self):
|
||||||
# XXX improve this test
|
# XXX improve this test
|
||||||
@@ -108,6 +112,7 @@ class TestExamples(PyScriptTest):
|
|||||||
self.wait_for_pyscript()
|
self.wait_for_pyscript()
|
||||||
assert self.page.title() == "Bokeh Example"
|
assert self.page.title() == "Bokeh Example"
|
||||||
wait_for_render(self.page, "*", '<div.*?class=\\"bk\\".*?>')
|
wait_for_render(self.page, "*", '<div.*?class=\\"bk\\".*?>')
|
||||||
|
self.assert_no_banners()
|
||||||
|
|
||||||
@pytest.mark.skip("flaky, see issue 759")
|
@pytest.mark.skip("flaky, see issue 759")
|
||||||
def test_d3(self):
|
def test_d3(self):
|
||||||
@@ -123,6 +128,7 @@ class TestExamples(PyScriptTest):
|
|||||||
# Let's simply assert that the text of the chart is as expected which
|
# Let's simply assert that the text of the chart is as expected which
|
||||||
# means that the chart rendered successfully and with the right text
|
# means that the chart rendered successfully and with the right text
|
||||||
assert "🍊21\n🍇13\n🍏8\n🍌5\n🍐3\n🍋2\n🍎1\n🍉1" in pyscript_chart.inner_text()
|
assert "🍊21\n🍇13\n🍏8\n🍌5\n🍐3\n🍋2\n🍎1\n🍉1" in pyscript_chart.inner_text()
|
||||||
|
self.assert_no_banners()
|
||||||
|
|
||||||
def test_folium(self):
|
def test_folium(self):
|
||||||
self.goto("examples/folium.html")
|
self.goto("examples/folium.html")
|
||||||
@@ -145,6 +151,7 @@ class TestExamples(PyScriptTest):
|
|||||||
zoom_out = iframe.locator("[aria-label='Zoom out']")
|
zoom_out = iframe.locator("[aria-label='Zoom out']")
|
||||||
assert "−" in zoom_out.inner_text()
|
assert "−" in zoom_out.inner_text()
|
||||||
zoom_out.click()
|
zoom_out.click()
|
||||||
|
self.assert_no_banners()
|
||||||
|
|
||||||
def test_matplotlib(self):
|
def test_matplotlib(self):
|
||||||
self.goto("examples/matplotlib.html")
|
self.goto("examples/matplotlib.html")
|
||||||
@@ -171,6 +178,7 @@ class TestExamples(PyScriptTest):
|
|||||||
# let's confirm that they are the same
|
# let's confirm that they are the same
|
||||||
deviation = np.mean(np.abs(img_data - ref_data))
|
deviation = np.mean(np.abs(img_data - ref_data))
|
||||||
assert deviation == 0.0
|
assert deviation == 0.0
|
||||||
|
self.assert_no_banners()
|
||||||
|
|
||||||
def test_numpy_canvas_fractals(self):
|
def test_numpy_canvas_fractals(self):
|
||||||
self.goto("examples/numpy_canvas_fractals.html")
|
self.goto("examples/numpy_canvas_fractals.html")
|
||||||
@@ -217,6 +225,7 @@ class TestExamples(PyScriptTest):
|
|||||||
assert self.console.log.lines[-2] == "Computing Newton set ..."
|
assert self.console.log.lines[-2] == "Computing Newton set ..."
|
||||||
# Confirm that changing the input values, triggered a new computation
|
# Confirm that changing the input values, triggered a new computation
|
||||||
assert self.console.log.lines[-1] == "Computing Newton set ..."
|
assert self.console.log.lines[-1] == "Computing Newton set ..."
|
||||||
|
self.assert_no_banners()
|
||||||
|
|
||||||
def test_panel(self):
|
def test_panel(self):
|
||||||
self.goto("examples/panel.html")
|
self.goto("examples/panel.html")
|
||||||
@@ -234,6 +243,7 @@ class TestExamples(PyScriptTest):
|
|||||||
|
|
||||||
# Let's confirm that slider title changed
|
# Let's confirm that slider title changed
|
||||||
assert slider_title.inner_text() == "Amplitude: 5"
|
assert slider_title.inner_text() == "Amplitude: 5"
|
||||||
|
self.assert_no_banners()
|
||||||
|
|
||||||
def test_panel_deckgl(self):
|
def test_panel_deckgl(self):
|
||||||
# XXX improve this test
|
# XXX improve this test
|
||||||
@@ -241,6 +251,7 @@ class TestExamples(PyScriptTest):
|
|||||||
self.wait_for_pyscript()
|
self.wait_for_pyscript()
|
||||||
assert self.page.title() == "PyScript/Panel DeckGL Demo"
|
assert self.page.title() == "PyScript/Panel DeckGL Demo"
|
||||||
wait_for_render(self.page, "*", "<div.*?class=['\"]bk-root['\"].*?>")
|
wait_for_render(self.page, "*", "<div.*?class=['\"]bk-root['\"].*?>")
|
||||||
|
self.assert_no_banners()
|
||||||
|
|
||||||
def test_panel_kmeans(self):
|
def test_panel_kmeans(self):
|
||||||
# XXX improve this test
|
# XXX improve this test
|
||||||
@@ -248,6 +259,7 @@ class TestExamples(PyScriptTest):
|
|||||||
self.wait_for_pyscript()
|
self.wait_for_pyscript()
|
||||||
assert self.page.title() == "Pyscript/Panel KMeans Demo"
|
assert self.page.title() == "Pyscript/Panel KMeans Demo"
|
||||||
wait_for_render(self.page, "*", "<div.*?class=['\"]bk-root['\"].*?>")
|
wait_for_render(self.page, "*", "<div.*?class=['\"]bk-root['\"].*?>")
|
||||||
|
self.assert_no_banners()
|
||||||
|
|
||||||
def test_panel_stream(self):
|
def test_panel_stream(self):
|
||||||
# XXX improve this test
|
# XXX improve this test
|
||||||
@@ -255,6 +267,7 @@ class TestExamples(PyScriptTest):
|
|||||||
self.wait_for_pyscript()
|
self.wait_for_pyscript()
|
||||||
assert self.page.title() == "PyScript/Panel Streaming Demo"
|
assert self.page.title() == "PyScript/Panel Streaming Demo"
|
||||||
wait_for_render(self.page, "*", "<div.*?class=['\"]bk-root['\"].*?>")
|
wait_for_render(self.page, "*", "<div.*?class=['\"]bk-root['\"].*?>")
|
||||||
|
self.assert_no_banners()
|
||||||
|
|
||||||
def test_repl(self):
|
def test_repl(self):
|
||||||
self.goto("examples/repl.html")
|
self.goto("examples/repl.html")
|
||||||
@@ -274,6 +287,7 @@ class TestExamples(PyScriptTest):
|
|||||||
# before looking into the text_content
|
# before looking into the text_content
|
||||||
assert self.page.wait_for_selector("#my-repl-2-2", state="attached")
|
assert self.page.wait_for_selector("#my-repl-2-2", state="attached")
|
||||||
assert self.page.locator("#my-repl-2-2").text_content() == "4"
|
assert self.page.locator("#my-repl-2-2").text_content() == "4"
|
||||||
|
self.assert_no_banners()
|
||||||
|
|
||||||
def test_repl2(self):
|
def test_repl2(self):
|
||||||
self.goto("examples/repl2.html")
|
self.goto("examples/repl2.html")
|
||||||
@@ -289,6 +303,7 @@ class TestExamples(PyScriptTest):
|
|||||||
content = self.page.content()
|
content = self.page.content()
|
||||||
pattern = "\\d+/\\d+/\\d+, \\d+:\\d+:\\d+" # e.g. 08/09/2022 15:57:32
|
pattern = "\\d+/\\d+/\\d+, \\d+:\\d+:\\d+" # e.g. 08/09/2022 15:57:32
|
||||||
assert re.search(pattern, content)
|
assert re.search(pattern, content)
|
||||||
|
self.assert_no_banners()
|
||||||
|
|
||||||
def test_todo(self):
|
def test_todo(self):
|
||||||
self.goto("examples/todo.html")
|
self.goto("examples/todo.html")
|
||||||
@@ -316,6 +331,7 @@ class TestExamples(PyScriptTest):
|
|||||||
'<p class="m-0 inline line-through">Fold laundry</p>'
|
'<p class="m-0 inline line-through">Fold laundry</p>'
|
||||||
in first_task.inner_html()
|
in first_task.inner_html()
|
||||||
)
|
)
|
||||||
|
self.assert_no_banners()
|
||||||
|
|
||||||
def test_todo_pylist(self):
|
def test_todo_pylist(self):
|
||||||
# XXX improve this test
|
# XXX improve this test
|
||||||
@@ -323,6 +339,7 @@ class TestExamples(PyScriptTest):
|
|||||||
self.wait_for_pyscript()
|
self.wait_for_pyscript()
|
||||||
assert self.page.title() == "Todo App"
|
assert self.page.title() == "Todo App"
|
||||||
wait_for_render(self.page, "*", "<input.*?id=['\"]new-task-content['\"].*?>")
|
wait_for_render(self.page, "*", "<input.*?id=['\"]new-task-content['\"].*?>")
|
||||||
|
self.assert_no_banners()
|
||||||
|
|
||||||
@pytest.mark.xfail(reason="To be moved to collective and updated, see issue #686")
|
@pytest.mark.xfail(reason="To be moved to collective and updated, see issue #686")
|
||||||
def test_toga_freedom(self):
|
def test_toga_freedom(self):
|
||||||
@@ -340,6 +357,7 @@ class TestExamples(PyScriptTest):
|
|||||||
self.page.locator("button#toga_calculate").click()
|
self.page.locator("button#toga_calculate").click()
|
||||||
result = self.page.locator("#toga_c_input")
|
result = self.page.locator("#toga_c_input")
|
||||||
assert "40.555" in result.input_value()
|
assert "40.555" in result.input_value()
|
||||||
|
self.assert_no_banners()
|
||||||
|
|
||||||
def test_webgl_raycaster_index(self):
|
def test_webgl_raycaster_index(self):
|
||||||
# XXX improve this test
|
# XXX improve this test
|
||||||
@@ -347,3 +365,4 @@ class TestExamples(PyScriptTest):
|
|||||||
self.wait_for_pyscript()
|
self.wait_for_pyscript()
|
||||||
assert self.page.title() == "Raycaster"
|
assert self.page.title() == "Raycaster"
|
||||||
wait_for_render(self.page, "*", "<canvas.*?>")
|
wait_for_render(self.page, "*", "<canvas.*?>")
|
||||||
|
self.assert_no_banners()
|
||||||
|
|||||||
@@ -12,15 +12,16 @@ class TestElement:
|
|||||||
|
|
||||||
def test_element(self, monkeypatch):
|
def test_element(self, monkeypatch):
|
||||||
el = pyscript.Element("something")
|
el = pyscript.Element("something")
|
||||||
document_mock = Mock()
|
js_mock = Mock()
|
||||||
|
js_mock.document = Mock()
|
||||||
call_result = "some_result"
|
call_result = "some_result"
|
||||||
document_mock.querySelector = Mock(return_value=call_result)
|
js_mock.document.querySelector = Mock(return_value=call_result)
|
||||||
monkeypatch.setattr(pyscript, "document", document_mock)
|
monkeypatch.setattr(pyscript, "js", js_mock)
|
||||||
assert not el._element
|
assert not el._element
|
||||||
real_element = el.element
|
real_element = el.element
|
||||||
assert real_element
|
assert real_element
|
||||||
assert pyscript.document.querySelector.call_count == 1
|
assert js_mock.document.querySelector.call_count == 1
|
||||||
pyscript.document.querySelector.assert_called_with("#something")
|
js_mock.document.querySelector.assert_called_with("#something")
|
||||||
assert real_element == call_result
|
assert real_element == call_result
|
||||||
|
|
||||||
|
|
||||||
@@ -121,6 +122,86 @@ def test_uses_top_level_await():
|
|||||||
|
|
||||||
def test_set_version_info():
|
def test_set_version_info():
|
||||||
version_string = "1234.56.78.ABCD"
|
version_string = "1234.56.78.ABCD"
|
||||||
pyscript.PyScript.set_version_info(version_string)
|
pyscript._set_version_info(version_string)
|
||||||
assert pyscript.PyScript.__version__ == version_string
|
assert pyscript.__version__ == version_string
|
||||||
assert pyscript.PyScript.version_info == (1234, 56, 78, "ABCD")
|
assert pyscript.version_info == (1234, 56, 78, "ABCD")
|
||||||
|
#
|
||||||
|
# for backwards compatibility, should be killed eventually
|
||||||
|
assert pyscript.PyScript.__version__ == pyscript.__version__
|
||||||
|
assert pyscript.PyScript.version_info == pyscript.version_info
|
||||||
|
|
||||||
|
|
||||||
|
class MyDeprecatedGlobal(pyscript.DeprecatedGlobal):
|
||||||
|
"""
|
||||||
|
A subclass of DeprecatedGlobal, for tests.
|
||||||
|
|
||||||
|
Instead of showing warnings into the DOM (which we don't have inside unit
|
||||||
|
tests), we record the warnings into a field.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, *args, **kwargs):
|
||||||
|
super().__init__(*args, **kwargs)
|
||||||
|
self.warnings = []
|
||||||
|
|
||||||
|
def _show_warning(self, message):
|
||||||
|
self.warnings.append(message)
|
||||||
|
|
||||||
|
|
||||||
|
class TestDeprecatedGlobal:
|
||||||
|
def test_repr(self):
|
||||||
|
glob = MyDeprecatedGlobal("foo", None, "my message")
|
||||||
|
assert repr(glob) == "<DeprecatedGlobal('foo')>"
|
||||||
|
|
||||||
|
def test_show_warning_override(self):
|
||||||
|
"""
|
||||||
|
Test that our overriding of _show_warning actually works.
|
||||||
|
"""
|
||||||
|
glob = MyDeprecatedGlobal("foo", None, "my message")
|
||||||
|
glob._show_warning("foo")
|
||||||
|
glob._show_warning("bar")
|
||||||
|
assert glob.warnings == ["foo", "bar"]
|
||||||
|
|
||||||
|
def test_getattr(self):
|
||||||
|
class MyFakeObject:
|
||||||
|
name = "FooBar"
|
||||||
|
|
||||||
|
glob = MyDeprecatedGlobal("MyFakeObject", MyFakeObject, "this is my warning")
|
||||||
|
assert glob.name == "FooBar"
|
||||||
|
assert glob.warnings == ["this is my warning"]
|
||||||
|
|
||||||
|
def test_dont_show_warning_twice(self):
|
||||||
|
class MyFakeObject:
|
||||||
|
name = "foo"
|
||||||
|
surname = "bar"
|
||||||
|
|
||||||
|
glob = MyDeprecatedGlobal("MyFakeObject", MyFakeObject, "this is my warning")
|
||||||
|
assert glob.name == "foo"
|
||||||
|
assert glob.surname == "bar"
|
||||||
|
assert len(glob.warnings) == 1
|
||||||
|
|
||||||
|
def test_call(self):
|
||||||
|
def foo(x, y):
|
||||||
|
return x + y
|
||||||
|
|
||||||
|
glob = MyDeprecatedGlobal("foo", foo, "this is my warning")
|
||||||
|
assert glob(1, y=2) == 3
|
||||||
|
assert glob.warnings == ["this is my warning"]
|
||||||
|
|
||||||
|
def test_iter(self):
|
||||||
|
d = {"a": 1, "b": 2, "c": 3}
|
||||||
|
glob = MyDeprecatedGlobal("d", d, "this is my warning")
|
||||||
|
assert list(glob) == ["a", "b", "c"]
|
||||||
|
assert glob.warnings == ["this is my warning"]
|
||||||
|
|
||||||
|
def test_getitem(self):
|
||||||
|
d = {"a": 1, "b": 2, "c": 3}
|
||||||
|
glob = MyDeprecatedGlobal("d", d, "this is my warning")
|
||||||
|
assert glob["a"] == 1
|
||||||
|
assert glob.warnings == ["this is my warning"]
|
||||||
|
|
||||||
|
def test_setitem(self):
|
||||||
|
d = {"a": 1, "b": 2, "c": 3}
|
||||||
|
glob = MyDeprecatedGlobal("d", d, "this is my warning")
|
||||||
|
glob["a"] = 100
|
||||||
|
assert glob.warnings == ["this is my warning"]
|
||||||
|
assert glob["a"] == 100
|
||||||
|
|||||||
Reference in New Issue
Block a user