Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

Import PyScript Version into Python Runtime #958

Merged
merged 11 commits into from
Nov 18, 2022

Conversation

JeffersGlass
Copy link
Member

@JeffersGlass JeffersGlass commented Nov 17, 2022

After Ted's PR earlier this week (#952), here's a small-ish PR to access that version in Python as well. It adds PyScript.__version__ and PyScript.version_info:

# namedtuple, mimics sys.version_info
>>> PyScript.version_info
version_info(year=2023, month=2, patch=1, releaselevel='dev')

# human-readable version, similar to what ipython and others do
>>> PyScript.__version__
'2023.02.1.dev'

This fixes #546 , and supercedes #833.

docs/reference/API/__version__.md Outdated Show resolved Hide resolved
version_dict["releaselevel"] = version_parts[3]
except IndexError:
version_dict["releaselevel"] = ""

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ah, this seems to contradict my previous comment. But then I don't understand how "normal" versions are supposed to like, with or without the .final?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So I figured if the "canonical" name of the version is "2022.09.1.RC2", then version_info should match that, as version_info(year=2022, month=9, minor=1, releaselevel='RC2')

But if the cannonical name of the version is "2022.09.1", then either

  • version-info should respect that the releaselevel is nothing version_info(year=2009, month=9, minor=1, releaselevel='') (this is what the PR currently does)

Or

  • since we've been using "2022.09.1" to mean the actual release of "2022.09.1", we could put a signifier in the releaselevel for this version_info(year=2009, month=9, minor=1, releaselevel='final')

The term "final" I'm not set on - could be "release" or something.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I am inclined towards final or release instead of "" but have no strong opinions to be honest.

@JeffersGlass
Copy link
Member Author

Sadly, this breaks in pyodide.test.ts in a very strange way that I'm hoping someone can help me figure out.

These lines build fine - if you open a REPL you can see the two new values:

this.run(pyscript as string);
this.run(`PyScript.set_version_info('${version}')`)

image

But pyodide.test.ts fails:

● PyodideRuntime › should check if runtime is an instance of abstract Runtime

  PythonError: Traceback (most recent call last):
    File "/lib/python3.10/site-packages/_pyodide/_base.py", line 435, in eval_code
      .run(globals, locals)
    File "/lib/python3.10/site-packages/_pyodide/_base.py", line 304, in run
      coroutine = eval(self.code, globals, locals)
    File "<exec>", line 1, in <module>
  NameError: name 'PyScript' is not defined

I can't for the life of me figure out why the second this.run seems to not be able to access the objects in the initial one, but only in jest.

Any ideas?

@JeffersGlass JeffersGlass added the help wanted Extra attention is needed label Nov 17, 2022
@FabioRosado
Copy link
Contributor

Sadly, this breaks in pyodide.test.ts in a very strange way that I'm hoping someone can help me figure out.

These lines build fine - if you open a REPL you can see the two new values:

this.run(pyscript as string);
this.run(`PyScript.set_version_info('${version}')`)

image

But pyodide.test.ts fails:

● PyodideRuntime › should check if runtime is an instance of abstract Runtime

  PythonError: Traceback (most recent call last):
    File "/lib/python3.10/site-packages/_pyodide/_base.py", line 435, in eval_code
      .run(globals, locals)
    File "/lib/python3.10/site-packages/_pyodide/_base.py", line 304, in run
      coroutine = eval(self.code, globals, locals)
    File "<exec>", line 1, in <module>
  NameError: name 'PyScript' is not defined

I can't for the life of me figure out why the second this.run seems to not be able to access the objects in the initial one, but only in jest.

Any ideas?

Apologies for the delay Jeff! The pyodide unit tests are importing pyodide and loading it dynamically, I wonder if maybe that's why it's it's complainig that PyScript is not available?

@madhur-tandon perhaps you have more context into this? 🤔

@JeffersGlass
Copy link
Member Author

Thanks @FabioRosado !

For an extra bit of weirdness, if I change that bit of loadInterpreter to:

this.run(pyscript as string);
logger.info("Dir Results: " + this.run('dir()'))

Then this shows up in the jest logs:

console.info
    [pyscript/pyodide] Dir Results: ['__annotations__', '__builtins__', '__doc__', '__loader__', '__name__', '__package__', '__spec__', 'pyversion', 'version_info']

So dir() is running fine... but with none of the symbols/globals that should be there after running pyscript.py? Very odd.

@JeffersGlass
Copy link
Member Author

JeffersGlass commented Nov 17, 2022

Ok some further oddities which make me think it's something odd about async interactions between pyodide and jest. Both of the following examples work when building for the browser, but...

This (the above example) breaks jest:

//main.ts
async afterRuntimeLoad(){
    ...
    await runtime.loadInterpreter();
    ...
    }

//pyodide.ts
async loadInterpreter(){
    ...
    this.run(pyscript as string);
    this.run(`PyScript.set_version_info('${version}')`)
    ...
    }

BUT this works in jest:

//main.ts
async afterRuntimeLoad(){
    ...
    await runtime.loadInterpreter();
    runtime.run(`PyScript.set_version_info('${version}')`)
    ...
    }

//pyodide.ts
async loadInterpreter(){
    ...
    this.run(pyscript as string);
    ....
    }

🤯

Not proposing this as a solution, it's just... odd and interesting.

@FabioRosado
Copy link
Contributor

That's interesting, the pyodide tests are doing this:

await runtime.loadInterpreter();

I wonder if this is related? 🤔 Wonder if perhaps runtime didn't finish load (not sure if this even makes sense haha)

@JeffersGlass
Copy link
Member Author

I figured it out!

jest is using a mocked version of pyscript.py for the tests:
https://github.com/JeffersGlass/pyscript/blob/803988342304d576425d48fd79ce3ebc50047226/pyscriptjs/jest.config.js#L19-L21

And that mock is.... empty!
https://github.com/JeffersGlass/pyscript/blob/803988342304d576425d48fd79ce3ebc50047226/pyscriptjs/__mocks__/fileMock.js#L12

So of course "PyScript isn't defined".

I think the solution is: change fileMock.js so that it copies and exports the contents of PyScript.py instead of "". I think this gives us the best approximation of the in-browser environment going forward. I'm just not sure the best way to do that... but I'll figure it out. Unless @madhur-tandon has suggestions?

@FabioRosado
Copy link
Contributor

Oh dang! That's a great find, awesome work!

Copying over seems like a good idea or we could copy only the needed methods if that works, what do you think?

@madhur-tandon
Copy link
Member

madhur-tandon commented Nov 18, 2022

@JeffersGlass @FabioRosado

Sorry for the late reply. Yeah, I did the mocking thing with an empty string because jest
threw errors when trying to import a .py script.

The project overall builds without failure since our rollup.config.js has

string({
include: "./src/**/*.py",
}),

aka the contents of .py imports are interpreted as raw strings. If we can somehow apply this setting to jest, that will be the best solution IMO but a JS ninja 🥷 could probably weigh in on how to do that.

Else, it seems like copying relevant stuff that is needed into the mocked string should be decently sufficient I guess.

@FabioRosado
Copy link
Contributor

@JeffersGlass @FabioRosado

Sorry for the late reply. Yeah, I did the mocking thing with an empty string because jest threw errors when trying to import a .py script.

The project overall builds without failure since our rollup.config.js has

string({
include: "./src/**/*.py",
}),

aka the contents of .py imports are interpreted as raw strings. If we can somehow apply this setting to jest, that will be the best solution IMO but a JS ninja 🥷 could probably weigh in on how to do that.

Else, it seems like copying relevant stuff that is needed into the mocked string should be decently sufficient I guess.

You can get jest to accept .py files with the config:

    moduleFileExtensions: ["js", "mjs", "cjs", "jsx", "ts", "tsx", "json", "node", "py"],

But then the tests explode because of the python imports

SyntaxError: /Users/fabiorosado/github/pyscript/pyscriptjs/src/python/pyscript.py: Unexpected token, expected "from" (2:0)

      1 | import ast
    > 2 | import asyncio

Maybe we go for the easier path and expand the mocked file 😄

@JeffersGlass
Copy link
Member Author

JeffersGlass commented Nov 18, 2022

Maybe we go for the easier path and expand the mocked file

Thanks y'all - for now, I've added __mocks__/_pyscript.js as an explicit mock of pyscript.py that jest can use at test time. It loads the contents of "pyscript.py" and exports them as a string.

const fs = require('fs')
module.exports = fs.readFileSync('./src/python/pyscript.py', 'utf8');

All other .py files are still caught by the original mapping and mocked with fileMock.js (i.e. as an empty string), but I've added a console warning there that files are being mocked as "" for the next time someone runs across this.

console.warn(`.py files that are not explicitly mocked in \
jest.config.js and /__mocks__/ are mocked as empty strings`)
module.exports = ""

@JeffersGlass JeffersGlass merged commit 687b93d into pyscript:main Nov 18, 2022
*/

const fs = require('fs');
module.exports = fs.readFileSync('./src/python/pyscript.py', 'utf8');
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Apologies a bit late to the party but... NICE! 😄

@JeffersGlass JeffersGlass removed the help wanted Extra attention is needed label Nov 18, 2022
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

PyScript should include version information
4 participants