-
Notifications
You must be signed in to change notification settings - Fork 1.4k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Namespaces Support #503
Namespaces Support #503
Conversation
All executable tags (py-script, py-button, py-repl, etc) can now take a 'pys-namespace' attribute, which will cause the code to run in a namespace identified the attribute's value. Tags without this attribute run in the default (global) namespace; other namespaces are stored in a dictionary at pyodide.globals["pyscript_namespaces"]. When a new namespace is created, the global dictionary is copied to that namespace. This preserves access to the standard library, Pyodide, pyscript, etc. Currently, all namespaces are instantiated on page-load. It would be better if there was an option to create them at execution time. Refactor namespaces into new getNamespace function Fix linting errors with const v let Run format
for more information, see https://pre-commit.ci
For what it's worth, I'm not married to namespaces having their own bespoke example - it was useful for testing and demonstration, but it may be too niche to merit it's own entire example. |
Great! Finally back from travels, conferences, etc.. and will get on this today. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@JeffersGlass thanks for the PR! Overall it looks good but I think we should change the attribute name (and probably support it in other PyScript components, but this can come in follow up PRs). What do you think?
@rth Do you mind a quick review to see if it's the best way to manage pyodide namespaces? |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks @JeffersGlass ! Overall this approach is reasonable I think. There was a previous discussion about this in pyodide/pyodide#703 and we have added
- pyodide.state._save_state
- pyodide.state._restore_state
which also records JS packages loaded to the global scope pyodide/pyodide#1349 but you probably don't need that here.
The caveat is that sys.modules
and any changes to modules (e.g. extra path in sys.path
) will be shared between namespaces. I think it should be OK, but hard to say what side effects this could have until someone starts using it extensively.
IPython's %reset magic command also does something similar (cf code) which is encouraging.
Maybe @antocuni would also have an opinion on this.
Thanks @rth! Am I right in thinking that pyodide.state._save_state and pyodide.state._restore_state specifically restore the pyodide globals dict, and wouldn't be able to essentially copy a dict for use in a separate namespace? Right now, this PR just makes a copy of the globals dict immediately after its initialized, which feels just a little clunky, but functional. I see what you're saying about sys.modules being shared between namespaces. As you say, not sure if that'd be an issue, but curious to see how that manifests in projects. |
I think that in order to answer that, we need to decide what do we want from namespaces:
(1) is very hard to implement. In addition to The good news is that I don't think we want (1) :). That said, I think it would be also nice to add a way to access other namespaces. We need:
An example showing (1), in which all the named namespaces are automatically available <py-script namespace="aaa">
x = 42
</py-script>
<py-script>
y = aaa.x + 1
</py-script> But (2) becomes more difficult: if I am in a <py-script namespace="aaa">
x = 42
</py-script>
<py-script>
y = aaa.x + 1
</py-script>
<py-script namespace="bbb">
print(__main__.y)
# which one of the following should work? Or both?
print(__main__.aaa.x)
print(aaa.x)
</py-script> However, my favorite solution is slightly different: Python has already a well known construct to separate the code into multiple namespaces and a way to reference them from each other: it's called modules! So, what about the following: <py-script module="aaa">
x = 42
</py-script>
<py-script>
import aaa
y = aaa.x + 1
</py-script>
<py-script module="bbb">
import __main__
import aaa
print(__main__.y)
print(aaa.x)
</py-script> Advantages of this solution:
If we decide to go with this approach, there are still open questions though. In particular:
|
Adds utils.htmlDecode arround innerHTML to read the code for <py-repl> and <py-script>, which preserves all newlines, tabs, and < & > characters. Fix bug pyscript#480
for more information, see https://pre-commit.ci
@antocuni thank you for this! Still digesting all of those very good thoughts (and similarly want to hear from @fpliger) - in the meantime, I've added some commits to address the original comments from @fpliger, namely:
Additionally, I've removed an issue where a namespace was being created/recreated for every tag with a namespace attribute, even if it had already been created by a previous tag with the same namespace. |
@JeffersGlass thanks for addressing the comments (doh, I totally overlooked the attribute being manage in the base classes :) ) @antocuni thanks for the nice overview, a lot of good food for thought. In general, I agree that in PyScript we want to first I also agree that explicit is better than implicit and modules are a good wait to think about a first implementation for that. With that said, there are a few things to consider though:
|
I agree that module semantics are a slick and appraochable way of handling this issue, but I think by allowing multiple py-script tags to exist within the same module, there's a conflict created with the top-to-bottom-of-the-webpage execution of code. For example, what should the following code output? <py-script module="aaa">
x = 1
</py-script>
<py-script> # module == '__main__'
import aaa
print(aaa.x)
</py-script>
<py-script module="aaa">
x = 2
</py-script>
<py-script> # module == '__main__'
print(x)
</py-script> If scripts are executed top-to-bottom, we would presumably get This also probably has ramifications for working with REPLs, or with dynamically-generated code, if we want it to be possible for those to work with namespaces. If a Doing some tinkering, it seems possible to mess with a module's dict in such a way that this is possible, but I'm not entirely sure. We'd also want to investigate whether that caching of loaded modules messes with an approach like this - in the example above, even if Tl;dr It feels like "top-to-bottom-of-the-page-script execution" and "namespaces as modules" are somewhat at odds with each other. Maybe there's a way of "appending" to modules that I'm missing? Calling the default namespace As an alternative to standard Python This feels a bit lit reinventing the wheel as far as import goes, but it clarifies that it would work differently than |
Yeah, agree that there's a lot to think about. Probably the best option is to open a discussion issue and work on a proposal. |
Wow, lot's of things to discuss. Let's start to answer @fpliger remarks:
I see your point here, and I agree that in a hypothetical world it would be nice to use the "namespace", but I I politely disagree with your conclusions. The main point of my idea is to be able to do About non-technical users: if they are non-technical, they don't know what is a namespace anyway :).
this is basically impossible to do in Python inside the same process. There have been attempts in the past to offer sandboxed/restricted python execution but they all ultimately failed (e.g. prior to Python 2.3 the stdlib included E.g. PyPy offers secure sandboxed execution by running the code inside a special subprocess where all the syscalls are intercepted and implemented by an outside "controller" process.
Answering @JeffersGlass
It would be executed top-to-bottom and prints import sys
import types
class PyScriptModule(types.ModuleType):
pass
def eval_pyscript_block(src, modname='__main__'):
mod = sys.modules.get(modname)
if mod:
if not isinstance(mod, PyScriptModule):
raise Exception(f'Cannot mix <py-script> with regular modules; {modname} already exists')
else:
mod = PyScriptModule(modname)
sys.modules[modname] = mod
exec(src, mod.__dict__)
### example of usage
eval_pyscript_block('x = 42', modname='hello')
import hello
print(hello.x)
if implemented with the logic above, reloading would not be allowed nor possible: >>> eval_pyscript_block('x = 42', modname='hello')
>>> import hello
>>> import importlib
>>> importlib.reload(hello)
Traceback (most recent call last):
File "/usr/lib/python3.8/code.py", line 90, in runcode
exec(code, self.locals)
File "<stdin>", line 1, in <module>
File "/usr/lib/python3.8/importlib/__init__.py", line 168, in reload
raise ModuleNotFoundError(f"spec not found for the module {name!r}", name=name)
ModuleNotFoundError: spec not found for the module 'hello' The semantics of module "caching" is very simple in Python: when you do
I don't understand what you mean. A normal |
@antocuni thank you for your thorough explanation, and especially the example code on the how a PyScriptModule might be implemented. I think we agree on 95% of the semantics and purposes here, and we just differ somewhat on whether What I'm getting getting hung up on is the following situation, where (1) a module imported from an earlier <py-script module="module_a">
print("This is printing from module A")
x = 42
</py-script>
<py-script>
print("The ultimate answer is:")
import module_a
print(module_a.x)
</py-script>
But if we move the first module to an external file: ### module_a.py
print("This is printing from module A")
x = 42
### index.html
<py-env>
-paths:
- ./module_a.py
</py-env>
<py-script>
print("The ultimate answer is:")
import module_a
print(module_a.x)
</py-script>
Which is to say - it seems to me that the
While the side-effects of this tiny example are trivial, one could imagine a situation where they could be significant Perhaps this is a minor distinction, but the fact this this behavior is different from typical Python behavior makes me pause. Personally, I think that adding a new method of namespace access is preferable to overriding an existing term with an additional meaning in a non-explicit way - that is, I think we should have difference nomenclature for referencing the namespace created by a Hopefully I've done a better job explaining this time around - and as always, happy to be disagreed with. |
After sleeping on it, I'm wondering if I'm making a mountain out of a molehill - probably using @fpliger Interested to know if this suits your use case, but I'm happy to revise this PR (or open a new one?) with a version of namespaces based on modules, along the lines of @antocuni's method above. I know there's some outstanding questions about the name of the default namespace itself and the tag attribute that sets the namespace/module, that seems like things we can continue a discussion on? |
[cut] ah ok, I see what you mean and it's an interesting point. I don't agree that they have different semantics; from my POV, executing a <py-script module="aaa">
print('aaa')
</py-script>
<py-script>
print('main')
import aaa
</py-script> From this point of view, using <py-script namespace="aaa">
print('aaa')
</py-script>
<py-script>
print('main')
import aaa
</py-script> |
@antocuni @JeffersGlass thanks , there's a lot of good stuff in this thread! :)
I've been trying to understand where we disagree/diverge and I think it's on the level of where namespace operate. You are proposing to look them just as a module (yes, one of [the main] Python constructs that manages a namespace) while I'm seeing them as top level context where your single script is running. In fact, a Pyodide namespace lives in the Typescript (runtime) level, unlike a module. One could argue that dicts are also namespaces (and that's what a Pyodide namespace really is) and that almost "everything in Python is a dict" and there's a lot of flexibility of choices. Picking modules to serve as namespaces implementation implies picking a lower level concept that exposes an implementation detail that is much more opinionated. Additionally, PyScript is a framework on top of Pyodide and a lot of what it is, is providing APIs/Accessibility to things that would otherwise be more complicated. Hopefully Pyodide will not be the only runtime and Python will not be the only language. How would interop between these lang/runtimes/namespaces be? Just having dicts that we pass around seem more flexible, easier to maintain and simpler to me.
😅 very good point. In that I agree with you, just disagree that it can be an excuse to not be thinking about making it more accessible and easier to use anyway.
(It'd probably be good to have @rth opinion on a few of these actually... this one included) I think the issue here is what I mentioned earlier. We are seeing things at different levels. If we use modules as implementation, I agree with you, if we use a mapping/dict at the Typescript/JS level we can decide what to pass and what to expose to the interpreter. There's more opportunity to control things. (@rth , please keep me honest here, I may be wrong :) ). Ultimately, since you can also access JS from Python you could still hack things but it's less "easy" (and I think we can do more). In addition to the above the |
I'm confused. In the original @JeffersGlass's implementation, namespaces are python dictionaries, not JS dictionaries.
100% agree. My proposal started because I wanted to find a good way to access a namespace from another one, and
another thing to consider is that doing too many JS->Python->JS->Python->... jumps can cause problems, because if I understand correctly how |
https://github.com/pyscript/pyscript/pull/503/files#diff-e5b66d49faff67e7097dcdde889c4da7fc9f6b3803eba6eec4581d3bbfedcf04R299-R302 for instance. The namespaces are created on TS side and passed explicitly to the runtime when running
Yeah, that's a concern and would love to hear @rth thoughts here. I'm not sure ho can we avoid jumps whenever we have an explicit namespace though (at least for Pyodide). |
yes but the actual namespace is a python dictionary which is created here, if I'm not mistaken:
I don't think this is actually possible: storing them in a DB would mean that you have to e.g. pickle/unpickle (or any other kind of serialization) the objects, which is not something that you can do generally and transparently in Python. But again, now I start to think that I might be missing the whole point of the excercise. I thought that pyscript namespaces were mostly a way to avoid cluttering the global namespace; if you have other use cases in mind please tell me :). |
Some clarificiation - in the PR, namespaces are stored as Python dicts, within a Python dict called In more detail: the code at the end of loadInterpreter() creates a python dict called It's worth pointing out that the reason that the reason all namespace dicts are created immediately after Pyodide loads is so we can copy pyodide.globals into each namespace dict at that point to preserve access to globals and installed libraries. This is a hack around the fact that runPython/Async only takes a globals parameter, and has no concept of global vs local scope. So we have to copy all the modules/functions/builtins we want access to into every namespace. I think if we end up managing namespaces as a TS mapping or similar, this kind of copying-builtins-and-libraries-hackery is somewhat inevitable, but: if namespaces are handled as some kind of Python dictionary, there's a better option: Making use of python's object lookup process (allowing built-ins and modules). Using pyodide.eval_code, which accepts both This approach should work whether we use flat At least, that's true in my conception of what 'namespaces' are, but as @antocuni says, maybe we should clarify our goals and purposes a bit first. |
Also, I think there are two different topics at hand, and some of the confusion arises because sometimes we are thinking of one or the other:
The implementation is -- well -- an implementation detail. But the API is what we have to get right so let's focus on this for now. Another "obvious" alternative which comes to my mind is to have a sort of global object which represents the "page" or the "document" and which is accessible from all namespaces. This is similar to the <py-script>
a = 1
</py-script>
<py-script namespace="xxx">
print(pyscript.__main__.a) # 1
b = 2
</py-script>
<py-script namespace="yyy">
print(pyscript.xxx.b) # 2
</py-script> This is probably less controversial than modules. The question is:
|
In answer to the second question - "how do you access a namespace from another?" To be honest @antocuni - when I first read @fpliger's conception of namespaces, I didn't imagine there being any way of importing objects between then. Since the original issue #166 referenced So perhaps it's worth taking even a step further back and asking - should namespaces provide a (reasonable) way to access each other? (Being in the same runtime, the real answer is that there's probably always a way to access other namespaces, via the pyodide or js global scope or somesuch, as you discussed previously. So perhaps the question is 'should they be able to do so easily/as a documented feature?'). Semi-relatedly, there's been some back-and-forth about a proposal for a Whether adopting the idea or not, it feels like figuring out what is desirable in terms of code organization may be separate from what's desirable in terms of code isolation, if that makes any sense. I recognize the two could be quite intertwined, but I wonder if thinking about those concepts separately may help shape the scope of what's desirable here. On a personal note, I'm leaving for a long-delayed honeymoon about 12 hours after posting this, and so will have to drop out of the conversation for a couple weeks. But I look forward to seeing where it's gone in the meantime, or to picking up the conversation down the road! |
So... this thread has been really good/helpful. Lots of food for thought. I agree with @JeffersGlass on the fact that maybe it's worth taking a step (or two) back. After all, in general it's usually a better design to have smaller/more modular features that can be integrated together than a "one size fits all" larger monolith that does everything. I'm +10 on starting small and not thinking of namespaces as a way to interop between languages (and I first hinted at) and +0 on even keeping this first PR leaner and not adding a way for namespaces to interact with each other right away... and add that feature with a little more thought/design. This also helps to think about security. In that sense, I agree that there's probably going to always be a hack users can do to access other namespaces anyway but at least we can think of a nice API to make things easy, explicit, etc... Also, good point on code organization and overall. :) @JeffersGlass CONGRATS on your honeymoon! I hope you enjoy and have the most wonderful time! I'm bummed (and personally apologize) for the slow progress/conversation on this very good PR... but if you are ok with it we can do the final work to bring it to the finish line while you enjoy your honeymoon and have a wonderful time not thinking about PyScript for a split second 😉 . Just let us know since you did a lot of great work here 🙏 |
Thanks @fpliger! I haven't felt it was slow progress at all - it just turned out to be a bigger bite of the apple than I could have imagined, and well worth taking the time to knock around options and ramifications. Absolutely feel free to take this PR to the finish line while I'm away - whatever route you take, I feel great having been part of this excellent discussion. Thank you and @antocuni for all your brainpower on on this - I'm excited to see the results when I get back, and to see what comes next for Pyscript as well. |
Replying to @JeffersGlass
right, I think this is the correct question to ask. I assumed that the answer would be "yes", because in my mind a namespace is merely a way to organize names, not a sandbox/isolation/security feature.
Agreed. Even if we decide that we don't want "namespaces" to be able to see each other, we need to be super explicit to say that this is not a secure sandbox.
woooo, congrats! I hope you are enjoying it without thinking of pyscript 🎉 replying to @fpliger
agreed, let's keep this discussion python-only
I'm -0 on this. I see your point, but as you are saying people would find undocumented ways to achieve the goal anyway. If we don't design the inter-namespace-communication now, we will surely break someone's code when we will refactor it "properly" later. |
For the sake of moving this ahead, I'd like to propose that this PR implement the most basic form of code/variable isolation, in ways that allow the possibilities we've discussed to become future improvements:
On the implementation end, I'd like to do three things:
This leaves the hardest question to last: the naming of this concept and the associated HTML attribute. To revisit and propose some possible names:
|
A couple updates:
# This code runs in the global namespace:
pyodide.pyodide_py.eval_code(`
from js import console, document
from pyodide import create_proxy, to_js
my_local_dict = {'x': 42} #Create a rudimentary namespace dict
`,
pyodide.globals); #globals argument to eval_code
#This code will use my_local_dict as its local dictionary
pyodide.pyodide_py.eval_code(`
console.log(f"Hey, the value of x at the start of this block is {x}") #Outputs 42
def x_value( _ ):
console.log(f"The value of x is {x}") # Should be 42... but instead raises NameError when executed by event
document.getElementById("btn-x").addEventListener('click', create_proxy(x_value))
`,
pyodide.globals, #globals argument to eval_code
pyodide.globals.get('my_next_dict')); #locals argument to eval_code This makes a certain amount of sense from a JavaScript perspective, but it feels like a sharp corner from a Python perspective. It's possible to work around this, I suppose, by dynamically recreating the appropriate local variables when the function is called, but that feels a bit sloppy... another option would be a big warning in the documentation that says Proxied Functions Always Execute with Global Scope, but again, not ideal. I've reached out on the Pyodide community gitter to see if there are other workarounds. One current workaround is: def x_value(_):
x = globals()['my_next_dict']['x']
console.log(f"The value of x is {x}") # Really does output 42 One could imagine a function like Edit: Another idea - since this function receives the PointerEvent from the object that triggers it, it should be possible to get the |
@JeffersGlass first of all, sorry for the long delay. Both I and @fpliger had many things going on, including the Anaconda homecoming and the participation to SciPy, but now we are back. And I hope you had an amazing honeymoon :). That said, let's go back to the namespaces. We have many things to discuss, so I'll try to keep the discussion ordered. The ToC is:
What is the expected semantics
I agree. Since it's clear that there is no obvious agreement upon what a namespace should do, let's keep it simple, do the bare minimum for now and re-think about if and how to communicate between namespaces later.
Note that if we don't provide a way to communicate between namespaces, the name of the default one is irrelevant.
How to call them
Thanks for the nice summary!
How to implement them part 1: why locals() is a bad idea
This is actually the correct/expected behavior even from the point of view of Python. In short, every python function is associated to its global scope, which is "the dictionary where to search for names which are not local variables". So, when you do a name lookup it first checks the So basically, what are you doing is equivalent to the following pure python code (no pyodide needed): myglobals = {'x': 'inside myglobals'}
#myglobals = {}
mylocals = {'x': 'inside mylocals'}
exec("""
print(f"[1] at global level: x = {x}")
def x_value():
print(f"[2] inside x_value(): x = {x}")
""",
myglobals,
mylocals)
x_value = mylocals['x_value']
x_value()
print(x_value.__globals__ is myglobals) Which has the following output:
At point Long story short: we don't want to use How to implement them part 2I think that a lot of the complexity of this PR comes from the fact that you try hard to copy I tried the proposed solution in a simple HTML and it seems to just work: <script type="text/javascript">
async function main(){
console.log('start!')
let pyodide = await loadPyodide();
pyodide.runPython(`
print("Default global namespace", __name__)
print("id(globals()) =", id(globals()))
for key in sorted(globals().keys()):
print(" ", key)
`);
console.log("---");
let my_namespace = pyodide.globals.get("dict")();
my_namespace.set('x', 42);
pyodide.runPython(`
print("Custom global namespace", __name__)
print("id(globals()) =", id(globals()))
for key in sorted(globals().keys()):
print(" ", key)
def x_value( _ ):
print("click!")
print(f"The value of x is {x}")
import js
from pyodide import create_proxy
js.document.getElementById("btn-x").addEventListener('click', create_proxy(x_value))
`, { globals: my_namespace });
}
main();
</script> If I run it (and click on the button), I get the following output:
So, I suggest to just use this solution to implement namespaces. As a side effect, we also get the bonus point that we can get rid of |
Thank you @antocuni! I think we're getting quite close on this. And thank you for the very thorough explanation of the functionality of locals - now that you've explained it to clearly, I would agree that the functionality we want is for each namespace to have its own globals and to leave locals out of it. And similarly, I think referring to the concept as Namespaces and using the attribute Regardless of the solution, I do think Implementation: Objects from Pyscript.pyTo clarify the implementation issue: the issue I'm having is not one of wanting to copy pyodide.globals into each namespace, but of wanting access to the objects that Pyscript.py defines and initializes when Pyscript.js runs, and make them available in every namespace. ( Since on startup/pyodide load we immediately run I had hoped that it would be possible to use a ChainMap or similar for the globals dict, to allow for chained lookup in both the unique "globals" dictionary of a namespace and the original global namespace, but from what I understand only a true Further Possible SolutionsI suppose one solution would be: whenever a new namespace needs to be created (i.e. evaluate() is called with a new namespace attribute), we could do Another might be adding those classes/functions to Another might be (and would want you to weigh in @fpliger, since this very much stretches the boundaries of this PR) that Pyscript.py doesn't get run unless imported by the user in a particular namespace. The user would have to use, for example, Does this all make sense? I feel I've done an imperfect job of laying out the problem I've been trying to solve - and perhaps there is a cleaner or more Pythonic way to do this. |
ah ok, I understand what is the problem now, and I understand why you wanted to fix it iwth
yes, I think that the current way of evaluating But here you are facing a similar problem, so I start to think that we should solve the problem once for all: we should evaluate the content of the source file into a proper import pyscript from './pyscript.py';
function loadInterpreter() {
await pyodide.runPythonAsync(`
def _create_module(src):
import sys
import types
mod = types.ModuleType('pyscript')
exec(src, mod.__dict__)
sys.modules['pyscript'] = mod
def new_namespace():
import pyscript
ns = {}
ns.update(pyscript.__dict__)
return ns
`)
let _create_module = pyodide.globals.get('_create_module');
_create_module(pyscript);
}
You push an open door here. I also think that pyscript puts too many things in the global namespace and that we should do less. I think a good starting point could be:
Anyway, this is probably OT with this PR and warrants its own discussion.
It makes a lot of sense, thank you again for all the efforts and useful insights. |
Thanks @JeffersGlass and @antocuni for moving the conversation ahead. This is really important and forces us to start taking some design decisions that, honestly, are needed asap anyway. Focusing on one of the major source of problems, Imho, the exercise we should do here is to sit down, think what's the layout of this higher level API and make a concrete proposal. With that down, the work on the namespaces design/implementation will be easier. I think @antocuni proposal above is in the right direction. |
Strangely enough we agree here :).
I bet the disagreement will come when we will have to decide what to include in (3). I propose to start from nothing, and then adds things when and only when we find an example use case which hugely benefits from it. E.g.,
I'm unsure about this. If we keep the list at point (3) small enough, we probably don't need it.
If you agree with the plan above, I will start a PR |
Yeah, agree with the above (and that 3 is probably where we need to open a beer to ease the conversation 😆 ) I'd be +1 on starting this and only propose to start with exposing I think you also proposed a better modularization of what Opened #659 to track this work/discussion |
Thank you both very much - agreed, with some definitive answers around what is and isn't included in scope by default, the implementation and interface of namespaces is should become easier to specify. I have my own opinions on the [approachable for novices] vs [cleanliness of scope] spectrum, but I'll leave that to the discussion in #659 . Since it's possible that the new namespace implementation looks nothing like what's in this PR, I'll close this, in preference to centering the discussion in the other issue. |
I would love to hear your thoughts about it.
thank you for pushing this conversation. I think it helped a lot to clarify what we want, what we don't and what are the various tradeoffs. |
Thank you @JeffersGlass , looking forward to your feedback in #659 !
lol, totally agree! |
I know this is closed and #659 is the new home. But I wanted to argue about something left behind. 😀 I'm approaching this from a premise: Python code should look and run like Python code. I worry that we're creating a Python-inspired thing called PyScript. Thus, I like the idea of magic globals disappearing, and you have to import stuff to access it. No more red-squiggles in my editor. This means that I also like the discarded idea of modules. IMO, we should try harder to stick to module semantics, and possibly implementation. The more we can move from the JS side to the Python side, the better. PyScript code should "run on the server" as well as in the browser. Sharing state might be a different thing. But web frameworks have thought about this forever due to threading. I think we'll ultimately come up with a channel/state system that is well above both modules and namespaces. #642 by @antocuni matches some of my experiments and notes. Last point: let's keep an open mind about "module" and "package". Wheels, zipapps, even importing from database files. I watched this PyCon talk and the last sentence in that writeup was pretty mind-blowing. But it still stayed inside Python semantics. I will re-re-read this conversation as I realize there are a TON of gotchas that @JeffersGlass and @antocuni have been considering, so I'm likely naive. |
Addressing #166, this adds support for namespaces for any tag with an eval() or evaluate() method (py-script, py-repl, py-button, etc.) These tags can have a new attribute pys-namespace to specify which namespace they execute in. This replaces PR #407.
An very bare-bones example:
There is a new
namespaces.html
that demonstrates the basic namespace functionality.Under the hood: After pyodide is initialized, the new
initNamespaces()
initializer makes a copy ofpyodide.globals
for every distinct value of pys-namespace found in the document. This is so the new namespaces will have access to the standard lib, PyScript, etc.These copies are stored in a new dictionary in the pyodide.globals space called "pyscript_namespaces". Each element has a namespace attribute that stores the name of its namespace (as a string), which are the keys to the
pyscript_namespaces
dict.When a pyscript element is
evaluate()
'd oreval()
'd, that element uses its pys-namespace value as a key into the pyscript_namespaces dict and uses that as its__globals__
namespace. If the element has no pys-namespace value, the usual pyodide.globals dict is used.One thought for future improvement: right now, all the namespaces are created when pyodide is initialized. It might be nice if there were the option to initialize them on demand if they don't exist yet, so that pages could dynamically add new namespaces to elements. This is probably easiest if (2) is implemented, since the usual pyodide.globals namespace would only ever contain the standard pyodide startup modules, standard lib, and whatever PyScript initializes at init time.