PyDev adventures (original) (raw)
tag:blogger.com,1999:blog-85509622026-03-18T04:40:01.106-07:00PyDev adventuresPosting about venturing (and creating) PyDev.
LINKS: PyDev.org Blog RSS Twitter RSS
Fabio Zadroznyhttp://www.blogger.com/profile/04202246218394712738noreply@blogger.comBlogger325125tag:blogger.com,1999:blog-8550962.post-82992513901009438542025-01-17T12:43:00.001-08:002025-01-21T07:33:46.625-08:00Using (or really misusing) Path.resolve() in Python
I just stumbled upon a code in Python that uses a simple `Path(...).resolve()` after receiving a path in an API and suddenly reminded myself on the many the bugs I've tripped over due to it, so, decided to share why I hope at some point this function stops being so pervasive as it's usually the wrong thing to call.. (maybe because when Python went from os.path to pathlib they added no real replacement and now you need to use both).
The main reason of bugs for that is that when you call Path.resolve() it'll actually resolve symlinks (and substs on Windows).
This means that if the user crafted some structure as
/myproject
/myproject/package.json
/myproject/src -> symlink to sources in /allmysources/temp
If you resolve `/myproject/src/myfile.py` to `/allmysources/temp/myfile.py` and actually want to get to the `package.json`, it'll never be found!
I was even looking at the python docs and found this nugget when talking about `Path.parent`:
If you want to walk an arbitrary filesystem path upwards, it is recommended to first call
Path.resolve()so as to resolve symlinks and eliminate".."components.
And that's completely why you shouldn't use it because if you do, then the parents will be completely off!
So, what should you do?
Unfortunately pathlib is not your friend here, you need to use `os.path.normpath(os.path.abspath(...))` to remove `..` occurrences from the string and make it absolute (and then after, sure, use pathlib for the remainder of your program) -- possibly you even want to get the real case in the filesystem if you're on Windows (which is very annoying as then you end up needing to do a bunch of listdir() calls to get the case stored in the filesystem).
-- but also, keep in mind you usually just need to do that on boundaries (i.e.: when you receive a relative path as an argument in the command line for instance, not when you receive an API call from another program -- chances are, the cwd of that program and your own are different and thus calls to `absolute()` or `resolve()` are actually bugs).
But isn't there any use-case for `Path.resolve()`?
As far as I know, the only case where it's valid is if you do want to create a cache of sorts where any representation of that file is considered the same (i.e.: you really want the canonical representation of the file).
Say, in an IDE you opened your `subst` in `x:` and then you have `c:\project\foo.py` and `x:\foo.py` in a debugger and you want to always show the version that's opened under your IDE, you need to build that mapping so that regardless of the version that's accessed you will open the version that's being seen in your IDE (and oh my god, I've seen so many IDEs get that wrong and the effect is usually you'll have the same file opened under different names in the IDE -- I know that in the pydevd debugger this is especially tricky because the name of the file is gotten from a .pyc file, which depends on how it was generated and the cached version may not match the version that the user is currently seeing -- the debugger goes through great lengths to have an internal canonical representation and a different version which should be what the user sees, but it's not perfect and IDEs which actually open the file afterwards don't make the due diligence to try to do the proper mapping afterwards -- even worse when they fail because the path is the same but the drive is uppercase and internally the path they have has the drive in lowercase).
Maybe even then it'd be better to use the inode of the file for a reference (gotten from `Path.stat()`)...
Anyways, to sum up, in its current format, I'd just recommend to ALMOST NEVER use `resolve()` as it's usually just the source of bugs, keep to `Path(os.path.normpath(os.path.abspath(...)))` on program boundaries as that's just saner in general (and when you want to pass files among APIs, make sure paths are already absolute and normalized and fail if they aren't).
p.s.: just as a disclaimer, I've also seen a very minor subset of users on Linux (just one so far really, but maybe there are others that want that behavior out there) that say they want the symlinks resolved as the created structure they have is always final and the symlinks are just a commodity to avoid having to cd into that structure so that they can freely cd into symlinks (and as such they don't want to affect the structure when they created symlinks), but I don't think this is the most common behavior (and if it is, then programs probably need to have a flag determining on whether to use it or not (for instance, I know vite from javascript has a resolve.preserveSymlinks setting so that the user can decide if they want it or not) -- although it's also true that most users don't even care, because they don't put their source code in a symlink or Windows subst 😊
The latest release of PyDev (12.0.0) is now available and it brings a really nice speed improvement for those who are already in Python 3.12! -- If you're a LiClipse user, it's now available in LiClipse 11.
The PyDev Debugger now uses sys.monitoring, which enables faster debugging (on my tests it can be up to 15 times faster than the version using sys.setttrace, depending on the use case -- kudos to Mark Shannon for PEP 669 😉).
It took me a while to cover the many scenarios that pydevd deals with, but the good thing is that most of the infrastructure available in pydevd didn't need changes (the debugger already had all the concepts needed as it already tried to trace only the frames which were actually needed, which definitely helped a lot as this is now pretty central in how to deal with the tracing using sys.monitoring)
Given that it's now out, I'll talk about how it works and some of the caveats when using it.
So, the first thing to note is that PEP 669 defines a bunch of callbacks which are now related to the code object (and not the frame as happened with sys.settrace) and when inside one of those callbacks the debugger can react to decide what should happen.
Some things that could happen could be pausing due to a breakpoint or a step instruction or deciding that the given code should not be traced again (by returning a DISABLE).
The reason it becomes faster than the previous approach is that the DISABLE is then considered by the Python interpreter which then bakes that DISABLE into the code when it's executing (up until sys.monitoring.restart_events() is called again). This does come with a big caveat though: if there are multiple threads running the program and one of those threads returns a DISABLE instruction then the related callback will actually be disabled for all the threads. Note that if DISABLE is not returned, the speed ends up being close to what was available with sys.settrace (which wasn't all that bad in pydevd already, but definitely a step down from what is now available).
This means that the debugger is really much faster when going for a breakpoint (because it can DISABLE many of the tracing instructions), but after a breakpoint is hit, if one thread is doing a step operation, then the speed reverts back to being close to the sys.settrace variant (the debugger can still DISABLE tracing for places it knows the user never wants to stop, but it cannot DISABLE the tracing for any code in any thread where the user may want to stop, because the thread which is stepping could be affected by a DISABLE in another thread which is not stepping, or at least the stepping must be considered for all threads even if a given thread hasn't really stopped and is not stepping).
Also, it's worth to mention that sys.monitoring has finer grained events vs. the ones available in sys.settrace, which is good as for instance, it's possible to get events from exceptions separate from events related to entering/returning from a function or lines, which helps the debugger in tracing only what's actually needed (the tracing debugger had to do lots of gymnastics to create a separate tracer just for exceptions when there would be no need to trace a given scope for lines, so, the new code ends up being both simpler and faster).
Note however that in pydevd I've done some other improvements and stepping should be more responsive even when using sys.settrace with older versions of Python!
p.s.: the attach to process is still not available for Python 3.12.
Note: back in the day other players which make use of pydevd such as JetBrains and Microsoft did step up to finance it, but at this point the only support it has is from backers in the community.
So, if you enjoy using pydevd please consider becoming a supporter... I've actually just setup sponsoring through GitHub sponsorships (https://github.com/sponsors/fabioz). Any takes on becoming one of the first backers there 😉?
It's been a while since I don't post, so, I decided to shed some details on what I'm working now (which I think is a really nice -- open source -- tool for the Python ecosystem -- in my view it's something close to a "Time travel debugger" for Python, although it's mostly labelled as automatic logging for Python 😄).
I'll start with a bit of history though: I'm working with Robocorp right now (Robocorp is a company which focuses on automation projects -- it leverages and provides many open source tools, especially in the Python ecosystem).
A few years back Robocorp approached me to do a language server for Robot Framework (which started as a testing framework done in Python and right now is also used for doing automations, which Robocorp recommends for many clients doing automations -- although many clients also prefer to just use Python directly).
Now, one of the strong points of Robot Framework is that it generates an output which provides information on everything that happened during its execution (Keyword calls -- which is how "methods" are called in Robot Framework, if statements, return values, etc), so, it's almost a "Time travel" debugger as it records and shows information on everything that happened in the execution.
Now, this brings me to what I'm working right now: a library which records what happens inside a Python process -- along with a UI which makes it possible to inspect it afterwards (its the Python log counterpart of Robot Framework).
For those curious I've created a repo with one example showing the output from Robot Framework and from the one generated by the Robocorp's Python Framework: https://github.com/fabioz/log_examples.
You can also see a live example (which solves the https://rpachallenge.com/) output of Robot Framework log.html as well as the output of Robocorp's Python Framework log.html.
The easiest way to use it right now is by using the tasks from Robocorp's Python Framework (see: https://github.com/robocorp/robo/tree/master/tasks as the log will be automatically generated for tasks run through it -- mostly, mark your entry points with @task (from robocorp.tasks import task) and run with `python -m robocorp.tasks run` and get your `log.html` in the `output` showing method calls, if statements, assigns, returns, etc that happened in your run).
One question I got a few times is how does it work... Well, after working quite a bit on pydevd and debugpy one of the things scratched right from the start was trying to use Python debugger infrastructure due to a simple fact: if the debugger infrastructure was used then no one could actually debug the code while using the logging framework.
In the end, inspiration ended up coming from PyTest. p.s.: thanks to Bruno Oliveira for a discussion back in the day about how assertions were rewritten using import hooks to provide nicer messages in assertion failures in PyTest.
robocorp.log uses the same approach as PyTest (import hooks + ast rewriting), but instead of rewriting asserts it rewrites the whole method to add callbacks on what's happening.
So, something as:
roughly becomes something as:
The real output is a bit more contrived as it needs to deal with exceptions, yields, making sure the stack is correct, but I hope you get the idea.
Then, the callbacks are converted into a kind of journal of what happens and that is then fed to the log.html (the idea is having it directly in a view in VSCode in the future so that you can see the log being created in real time -- right now the info is added to a bunch of "robolog" files and embedded into the final log.html).
Now, the approach does come with one caveat: import hooks need to be setup prior to importing a module, code imported before setting it up won't be traced -- it's one of the reasons why it's recommended to use robocorp.tasks to do the launching instead of bolting the logging manually as it makes sure things happen in the proper order.
The second caveat is related to the object __repr__. The framework is quite keen on getting the representation from objects at various times, so, if the __repr__ is too slow the execution may be much slower or even worse, if it has side-effects bad things will happen (thankfully most objects do the right thing as a __repr__ with side effects is a bug and the program would misbehave on debuggers too).
The third caveat is that right now it needs to be told what needs to be traced (by default full logging is available for all code which is considered user code and libraries need to be manually specified to be logged when it's called directly from a function which is considered user code -- this may change in the future, but it's the current state of affairs).
The final caveat is that right now it'll only trace what's happening in the main thread. Other threads won't appear in the log.
Well, I guess this post is already a bit bigger than I planned so I'll stop here. For those interested in testing or reporting enhancements/bugs see: https://github.com/robocorp/robo/.
Enjoy!
In Python asyncio land it's always a bit of a hassle when you have existing code which runs in sync mode which needs to be retrofitted to run in async, but it's usually doable -- in many cases, slapping async on the top of a bunch of definitions and adding the needed await statements where needed does the trick -- even though it's not always that easy.
Now, unfortunately a debugger has no such option. You see, a debugger needs to work on the boundaries of callbacks which are called from python (i.e.: it will usually do a busy wait from a line event from a callback registered in sys.settrace which is always called as a sync call).
Still, users still want to do some evaluation in the breakpoint context which would await... What now? Classic questions of how to go from async to sync say this is not possible.
This happens because to run something in asynchronous fashion an asyncio loop must be used to run it, but alas, the current loop is paused in the breakpoint and due to how asyncio is implemented in Python the asyncio loop is not reentrant, so, we can't just ask the loop to keep on processing at a certain point -- note that not all loops are equal, so, this is mostly an implementation detail on how CPython has implemented it, but unless we want to monkey-patch many things to make it reentrant, this would be a no-no... also, even if possible, it's not possible in asyncio to force a given coroutine to execute, rather we schedule it and asyncio decides when it'll run afterwards).
My initial naive attempt was just creating a new event loop, but again, CPython gets in the way because 2 event loops can't even coexist in the same thread. Then I thought about recreating the asyncio loop and got a bit further (up to being able to evaluate an asyncio.sleep coroutine), but after checking the asyncio AbstractEventLoop it became clear that the API is just too big to reimplement safely (it's not just about implementing the loop, it's also about implementing network I/O such as getnameinfo, create_connection, etc).
In the end the solution implemented for the debugger is that to support await constructs for evaluation, a new thread is created with a new event loop and that event loop in that new thread will execute the coroutine (with the context of the paused frame passed to that thread for the evaluation).
This is not perfect as there are some cons, for instance, evaluating the code in a thread can mean that some evaluations may not work because some frameworks such as qt consider the UI thread as special and won't work properly, checks for the current thread won't match the thread paused and probably a bunch of other things, but I guess it's a reasonable tradeoff vs not having it at all as it should work in the majority of cases.
Keep an eye open for the next release as it'll be possible to await coroutines in the debugger evaluation and watches ;)
p.s.: For VSCode users this will also be available in debugpy.
PyDev 9.3.0 is now available.
The main changes in this release are related to the debugger, with improvements such as:
- Major issue fixed issue where variable children sometimes wouldn't expand correctly.
- Fixed some case where automatic connection to subprocesses wouldn't work.
- Debugging with Pandas is much improved with the addition of some custom converters.
- Opt-in support to show paused greenlets by setting GEVENT_SHOW_PAUSED_GREENLETS=1.
- Support for newer versions of gevent.
- Information on user settings paths is cached to fix issue with slow debugging using pipenv project.
- A warning is shown if getting some attribute / getting its repr is slow.
- Interactively inspect matplotlib plots when the QtAgg backend is used.
- Support for PySide2.
- Better error messages in case Python 3.11 frozen modules are being used.
Also noteworthy is that this will be the last release supporting older versions of Python including Python 2.7 up to Python 3.5. Newer releases will only support Python 3.6 onwards.
Fabio Zadroznyhttp://www.blogger.com/profile/04202246218394712738noreply@blogger.com0tag:blogger.com,1999:blog-8550962.post-65334879965579587862021-04-18T04:53:00.000-07:002021-04-18T04:53:35.703-07:00PyDev 8.3.0 (Java 11, Flake 8 , Code-completion LRU, issue on Eclipse 4.19)PyDev 8.3.0 is now available!
Let me start with some warnings here:
First, PyDev now requires Java 11. I believe that Java 11 is pretty standard nowadays and the latest Eclipse also requires Java 11 (if you absolutely need Java 8, please keep using PyDev 8.2.0 -- or earlier -- indefinitely, otherwise, if you are still using Java 8, please upgrade to Java 11 -- or higher).
Second, Eclipse 2021-03 (4.19) is broken and cannot be used with any version of PyDev due to https://bugs.eclipse.org/bugs/show_bug.cgi?id=571990, so, if you use PyDev, please keep to Eclipse 4.18 (or get a newer if available) -- the latest version of PyDev warns about this, older versions will not complain but some features will not function properly, so, please skip on using Eclipse 4.19 if you use PyDev.
Now, on to the goodies ;)
On the linters front, the configurations for the linters can now be saved to the project or user settings and flake8 has an UI for configuration which is much more flexible, allowing to change the severity of any error.
A new option which allows all comments to be added to a single indent was added (and this is now the default).
The code-completion and quick fixes which rely on automatically adding some import will now cache the selection so that if a given token is imported that selection is saved and when asked again it'll be reused (so, for instance, if you just resolved Optional to be typing.Optional, that'll be the first choice the next time around).
Environment variables are now properly supported in .pydevproject. The expected format is: ${env_var:VAR_NAME}.
Acknowledgements
Thanks to Luis Cabral, who is now helping in the project for doing many of those improvements (and to the Patrons at https://www.patreon.com/fabioz which enabled it to happen).
Enjoy!
PyDev 8.2.0 is now available for download.
This release has many improvements for dealing with external linters.
The main ones are the inclusion of support for the Flake8 linter as well as using a single linter call for analyzing a directory, so, that should be much faster now (previously it called external linters once for each file) .
Note: to request code analysis for all the contents below a folder, right-click it and choose PyDev > Code analysis:
Another change is that comments are now added to the line indentation...
This means that some code as:
def method(): if True: pass Will become:
def method(): # if True: # pass p.s.: it's possible to revert to the old behavior by changing the preferences at PyDev > Editor > Code Style > Comments.
Also note that after some feedback, on the next release an option to format such as the code below will also be added (and will probably be made the default):
def method(): # if True: # pass Interpreter configuration also got a revamp:
Path mappings for remote debugging can now (finally) be configured from within PyDev itself, so, changing environment variables is no longer needed for that:
Note that Add path mappings template entry may be clicked multiple times to add multiple entries.
That's it... More details may be found at: http://pydev.org.
Hope you enjoy the release 😊
PyDev 8.1.0 is now available for download.
As a note, I didn't really create a post on 8.0.1, so, I'm covering some of the features in that version in this blog post too! 😊
Some nice things added:
- Python 3.9 is officially supported in PyDev -- definitely a must if you plan on using Python 3.9!
- There are quick-fixes (Ctrl+1) to convert a string into an f-string (see image below):
Note that even if there's no formatting in the string, with this it's possible to write some string as: "Value is {value}" and then use the quick fix just to add the "f" to the front of the string (which is quite handy if you weren't initially planning to create it as an f-string).
- When starting the interactive console, it's now possible to save how the interactive console should be initialized (see image below):
Note that it's also possible to change those settings in the interactive console preferences if you want to change them after the initial selection (hint: after the interactive console is created it's possible to use F2 to send a line from the editor for execution to the interactive console for notebook-like evaluation).
Besides this there are many other improvements, such as improved conda activation, rerunning pytest parametrized tests from the PyUnit view, MyPy integration improvements, support for from __future__ import annotations in code analysis and debugger improvements, among many others.. see: https://pydev.org for more details.
Enjoy!
Wow, PyDev is turning 8.0... the 8.0 version probably doesn't do much justice as it's actually being developed for 17 years already! 😊
I'm actually pretty happy on how things are working around it right now... Some interesting points:
- Many users now support PyDev through Patreon or PayPal -- so, it's been a while since I spent time to do a dedicated crowdfunding campaign (which usually demands quite a bit of time). That funding also allows having additional help (it's what currently sponsors the work of Luis Cabral in PyDev).
- It has a good amount of users (although it doesn't have as much users as it did in the past as there are many good Python IDEs available in the Python ecosystem now) -- I'm actually quite happy with this as for some time it seems like Eclipse had such a big market share that users that didn't like it had to bear with it, so, I'm pretty glad that there are alternatives now, even if it means its market share isn't as big as at was -- so, although this may mean less users it also means happier users.
- Microsoft is currently sponsoring my work on the pydevd debugger (as it's also used in the Python in Visual Studio and the Python Extension for Visual Studio Code), so, this actually translates into a high quality debugger for the whole Python ecosystem!
Ok, on to news on the PyDev 8.0 release...
As with the previous release, this release keeps on improving the support for type hinting and MyPy.
On the MyPy front, besides showing an error it will also show the related notes for a message on the tooltip (which would previously be available only in the output view) and MyPy processes are no longer launched in parallel when using the same cache folder (as this could end up making MyPy write wrong caches which required the cache folder to be manually erased).
In the type inference front there are multiple improvements to take advantage of type hints (such as support for Optional[] in code completion, handle types given as string and following type hints when presenting an option to create a new method in a class).
The debugger had a critical fix on the frame-evaluation mode (the mode which works by adding programmatic breakpoints by manipulating bytecode) which could make it skip breakpoints or even change the behavior of a program in extreme cases.
Besides this, other niceties such as code-completion inside f-strings are now available and this release had many other fixes (see: https://www.pydev.org for more details).
So, thank you to all PyDev users and here's to the next 17 years, hope you enjoy using it as much as I enjoy making it!
🥂
Fabio Zadroznyhttp://www.blogger.com/profile/04202246218394712738noreply@blogger.com0tag:blogger.com,1999:blog-8550962.post-66138672718875935142020-08-02T10:43:00.001-07:002020-08-02T10:43:29.951-07:00PyDev 7.7.0 released (mypy integration improvements, namespace packages)This release brings multiple improvements for dealing with type hints as well as improvements in the Mypy integration in PyDev:The MYPYPATH can now be set automatically to the source folders set on PyDev and the --follow-imports flag is set to silent by default (this flag is required because only one file is analyzed at a time in PyDev as failing to do so would end up showing errors for other files).
Personally, I think that with Python 3.8 it's finally possible to actually use Python type hints properly (due to the typing.Protocol -- a.k.a Duck typing -- support), so, more improvements are expected in that front on PyDev itself.
This release also brings many other improvements, such as support for pip-installed namespace packages, debugger fixes, support for parsing with the latest version of Cython and support for the latest PyTest.
There are actually many other minor improvements and bug-fixes in this release too. Check the release notes at http://www.pydev.org/ for more details!
Acknowledgements
Thanks to Luis Cabral, who is now helping in the project for doing many of those improvements (and to the Patrons at https://www.patreon.com/fabioz which enabled it to happen).
Thanks for Microsoft for sponsoring the debugger improvements, which are also available in Python in Visual Studio and the Python Extension for Visual Studio Code.
Enjoy!
--
FabioFabio Zadroznyhttp://www.blogger.com/profile/04202246218394712738noreply@blogger.com0tag:blogger.com,1999:blog-8550962.post-14815532391048418062020-06-11T05:13:00.000-07:002020-06-11T05:13:07.758-07:00PyDev 7.6.0 (Python 3.8 parsing fixes and debugger improvements)PyDev 7.6.0 is now available for download.
This release brings multiple fixes to parsing the Python 3.8 grammar (in particular, dealing with f-strings and iterable unpacking had some corner cases that weren't well supported).
Also, the debugger had a number of improvements, such as:
- Grouping variables which were previously hidden -- note that they can be hidden/shown when debugging from the variables view dropdown menu as shown in the screen below:
- Better support for PySide2;
- Show the disassembly of frames when sources aren't available:
Besides those, there were a number of other improvements... some noteworthy are support for the latest PyTest, faster PyDev Package Explorer, type inference for type comments with self attributes (i.e.: #: :type self.var: MyClass) and properly recognizing trailing commas on automatic import.
Acknowledgements
Thanks to Luis Cabral, who is now helping in the project for doing many of those improvements (and to the Patrons at https://www.patreon.com/fabioz which enabled it to happen).
Thanks for Microsoft for sponsoring the debugger improvements, which are also available in Python in Visual Studio and the Python Extension for Visual Studio Code.
Enjoy!
--
FabioFabio Zadroznyhttp://www.blogger.com/profile/04202246218394712738noreply@blogger.com4tag:blogger.com,1999:blog-8550962.post-6788435654885053912020-03-18T07:19:00.000-07:002020-03-18T07:19:00.008-07:00How is frame evaluation used in pydevd?First some background in frame evaluation:
Since Python 3.6, CPython has a mechanism which allows clients to override how it evaluates frames. This is done by changing PyThreadState.interp.eval_frame to a different C-function (the default being _PyEval_EvalFrameDefault). See: pydevd_frame_evaluator.pyx#L370 in pydevd (note that Cython is used there).
Note that this affects the Python runtime globally, whereas the regular Python tracing function -- set through sys.settrace() -- affects only the current thread (so, some of the caches for frame evaluation in pydevd are thread-local due to that).
How is this used in the debugger?
Well, the debugger doesn't really want to change how Python code is executed, but, there's another interesting side effect of the frame evaluation: it's possible to change the bytecode of the frame right before it's evaluated and CPython will interpret that bytecode instead of the original bytecode of the frame.
So, this works the following way: the frame evaluation function receives a PyFrameObject*, and at that point, the debugger checks the frame for existing breakpoints, if it has a breakpoint, it'll create a new code object which has a programmatic breakpoint (pydevd_frame_evaluator.pyx#L234) and change PyFrameObject.f_code to point to the new code object (pydevd_frame_evaluator.pyx#L358) -- when it reaches the programmatic breakpoint (pydevd_frame_tracing.py#L34), the regular (trace-based) debugger will kick in at that frame. Until that breakpoint is reached, frames are executed at full speed.
But if it runs at full speed, why is my program still running slower when using pydevd with frame evaluation?
Well, frames are executed at full speed, but, the debugger still adds some overhead at function calls (when it decides whether to add the programmatic breakpoint) and it also needs to add an almost no-op trace (pydevd_frame_evaluator.pyx#L95) function to sys.settrace -- which makes function calls slower too (this is needed because otherwise the debugger is not able to switch to the regular tracing by just changing the frame.f_trace as frame.f_trace is only checked when a tracing function is set for some thread through sys.settrace()). There are also some cases where it can't completely skip tracing for a frame even if it doesn't have a breakpoint (for instance, when it needs to break on caught exceptions or if it's stepping in the debugger).
It's interesting to note that even the regular (tracing) debugger on pydevd can run frames at full speed (it evaluates all frames and if a frame doesn't have a breakpoint the tracing for that frame will be skipped), the difference is that if a frame does have a breakpoint, that frame can run at full speed until it reaches the breakpoint in the frame eval mode, whereas in the regular mode each new line tracing event would need to be individually checked for a breakpoint.
If it just changes the bytecode, why use frame eval at all, can't you just change the bytecode of objects at a custom import hook? (which could have the benefit of avoiding the performance penalty of checking the frame on each new frame call)
There are 2 main reasons for that: the 1st is that breakpoints can change and when they change the frame evaluation would need to be completely shut down and only the tracing debugger would be valid from that point onwards (whereas right now, if breakpoints change, the tracing debugger kicks in for all the frames that were currently running but the frame evaluation debugger can still be used for new frames). The 2nd is that it can be hard to consistently do that if not just before frame evaluation (because user code can also change a method code and there are a number of corner cases to change the bytecode for live objects -- think of a function inside a function or decorated functions).
Note that this means that the debugger could probably get away with something simpler than frame evaluation and could potentially be applicable to other Python implementations (say, a different callback just before the frame is evaluated which allows to change the frame code... unfortunately it can't currently be done through the "call" event received by the trace function set by sys.settrace because at that point the frame is already being evaluated with the current code and at that point, even if it's changed, Python won't pick up that change).
That's it, hope you enjoyed pydevd using frame evaluation for debugging purposes 101 ;)
Fabio Zadroznyhttp://www.blogger.com/profile/04202246218394712738noreply@blogger.com0tag:blogger.com,1999:blog-8550962.post-32264940529533814982020-01-10T10:05:00.000-08:002020-01-10T10:05:13.643-08:00PyDev 7.5.0 Released (Python 3.8 and Cython)
This version brings some improvements to the debugger and a fix for when PyDev could not properly find pipenv which could impact some users.
See: http://pydev.org for more details. Fabio Zadroznyhttp://www.blogger.com/profile/04202246218394712738noreply@blogger.com4tag:blogger.com,1999:blog-8550962.post-52412332061306274252018-11-09T05:17:00.003-08:002018-11-09T05:17:42.260-08:00PyDev 7.0 (mypy, black, pipenv, faster debugger)
- #region / #endregion comments can now be used by the code-folding engine.
- An action to easily switch the default interpreter is now available (default binding: Ctrl+Shift+Alt+I -- note that it must be executed with an opened editor).
- It's possible to create local imports from global imports (use Ctrl+1 on the name of a given global import and select "Move import to local scope(s)" -- although note that the global import needs to be manually deleted later on).
- The interactive interpreter now has scroll-lock.
- The debugger is much more responsive!
Well, I guess going from 15 s to 10 s with just a few changes is already an improvement in my case for an integrated tests which starts up the whole app (although it could certainly be better...) and I think I'll still be able to trim some of that time doing some more imports lazily -- although that's no longer really pytest-related, so, that's it for this post ;)
Fabio Zadroznyhttp://www.blogger.com/profile/04202246218394712738noreply@blogger.com0tag:blogger.com,1999:blog-8550962.post-28957707504421070042018-07-06T04:37:00.000-07:002018-07-06T04:37:18.585-07:00PyDev 6.4.3 (code formatter standalone, debugger improvements and f-strings handling)The latest version of PyDev is now out...
Major changes in this release include:
1. Being able to use the PyDev code formatter as a standalone tool.
To use it it's possible to install it as pip install pydevf (the command line is provided as a python library which will call the actual formatter from PyDev -- see the README at https://github.com/fabioz/PyDev.Formatter for more details on how to use it).
The target of the PyDev formatter is trying to keep as close to the original structure of the code while fixing many common issues (so, it won't try to indent based on line width but will fix many common issues such as a space after a comma, space at start of comment, blank lines among methods and classes, etc).
2. Improvements to the debugger, such as:
- Thread creation is notified as threads are created instead of synchronized afterwards.
- Support for using frame evaluation disabled by default as it made the debugger much slower on some cases.
- Fixed case where breakpoint was missed if an exception was raised in a given line.
- Properly break on unhandled exceptions on threads.
- Add missing import which affected repl with IPython.
- Fix for case where breakpoints could be missed.
As a note, the debugger improvements have been sponsored by Microsoft, which is in the process of using the PyDev Debugger as the core of ptvsd, the Python debugger package used by Python in Visual Studio and the Python Extension for Visual Studio Code (note that it's still marked as experimental there as it's in the process of being integrated into ptvsd).
It's really nice to see pydevd being used in more IDEs in the Python world! 😉
Besides those, there are some bugfixes in handling f-strings and sending the contents of the current line to the console (through F2).
Also, a new major version of LiClipse (5.0) is now also available (see: http://www.liclipse.com/download.html for how to get it). It includes the latest PyDev and a new major Eclipse release (4.8 - Photon).Fabio Zadroznyhttp://www.blogger.com/profile/04202246218394712738noreply@blogger.com0tag:blogger.com,1999:blog-8550962.post-69719507114887468312018-05-12T08:39:00.000-07:002018-05-12T08:39:18.671-07:00Howto launch and debug in VSCode using the debug adapter protocol (part 2)
To launch a program, our debug adapter must treat the 'LaunchRequest' message and actually run the program (bear in mind that we'll just launch it without doing any debugging at this point).
The first point then is how to actually launch it. We provided options for the debugger to be launched with different console arguments specifying where to launch it (either just showing output to the debug console, using the integrated terminal or using an external terminal).
So, let's start with just showing output in the debug console.
Launching it should be simple: just generate a command line while treating the 'LaunchRequest' message, but then, based on the console specified some things may be different...
Let's start handling just showing the output on the debug console.
To do that we have launch the command properly redirecting the output to pipes (for python it's something as subprocess.Popen([sys.executable, '-u', file_to_run], stdout=subprocess.PIPE, stderr=subprocess.PIPE) and then create threads which will read that output to provide it back to vscode (so, when output is obtained, an OutputEvent must be given).
Also, create another thread so that when the process finishes, a TerminatedEvent is given (in python, just do a popen.wait() in a thread and when complete send the TerminatedEvent -- you may want to synchronize to make sure other threads related to output have finished before doing that).
At this point, we can run something and should be able to see anything printed both to stdout and stderr and when the process finishes, VSCode itself acknowledges that and closes the related controls.
Great! On to launching in the integrated terminal!
So, to launch in the terminal we have to first actually check if the client does support running in the terminal... in the InitializeRequest, if it is supported, we should've received in the arguments "supportsRunInTerminalRequest": True (if it doesn't, in my case I just fall back to the debug console).
This also becomes a little bit trickier because at this point we're the ones doing the request (RunInTerminalRequest) and the client should send a response (RunInTerminalResponse). So, on to it: when the client launches, create a RunInTerminalRequest with the proper kind ('internal' or 'external') and wait for the response.
As a note the 'noDebug' option is added behind the scenes by VSCode depending on whether the user has chosen to do a debug or run for the selected launch (so, it shouldn't be a part of the declared configuration in the extension).
Now, thinking a bit more about it, there's a caveat: when launching with the redirection to the debug console, we should treat sending to stdin too (we don't want to create a process he can't do any communication with later on).
To do that in 'noDebug' should be simple... when we receive an 'EvaluateRequest', we'll send it to stdin (when actually in debug mode we probably have to check the current debugger state to determine if we want to do an evaluation or send to stdin -- i.e.: if we are stopped in a breakpoint we may want to evaluate and not send to stdin).
As a note, after playing with it more I renamed the "console" option to "terminal" with options "none", "internal", "external" as I think that's a better representation of what's expected.
So, that's it for part 2: we're launching a program and redirecting the output as requested by the user (albeit without actually debugging it for now).
{ 'type': 'PyDev', 'label': 'PyDev (Python)', 'languages': ['python'], 'adapterExecutableCommand': 'pydev.start.debugger', # Note: adapterExecutableCommand will be replaced by a different API (right now still in proposal mode). # See: https://code.visualstudio.com/updates/v1\_20#\_debug-api # See: https://github.com/Microsoft/vscode/blob/7636a7d6f7d2749833f783e94fd3d48d6a1791cb/src/vs/vscode.proposed.d.ts#L388-L395 'enableBreakpointsFor': { 'languageIds': ['python', 'html'], }, 'configurationAttributes': { 'launch': { 'required': [ 'mainModule' ], 'properties': { 'mainModule': { 'type': 'string', 'description': 'The .py file that should be debugged.', }, 'args': { 'type': 'string', 'description': 'The command line arguments passed to the program.' }, "cwd": { "type": "string", "description": "The working directory of the program.", "default": "${workspaceFolder}" }, "console": { "type": "string", "enum": [ "integratedTerminal", "externalTerminal" ], "enumDescriptions": [ "VS Code integrated terminal.", "External terminal that can be configured in user settings." ], "description": "The specified console to launch the program.", "default": "integratedTerminal" }, } } }, "configurationSnippets": [ { "label": "PyDev: Launch Python Program", "description": "Add a new configuration for launching a python program with the PyDev debugger.", "body": { "type": "PyDev", "name": "PyDev Debug (Launch)", "request": "launch", "cwd": "^\"\\${workspaceFolder}\"", "console": "integratedTerminal", "mainModule": "", "args": "" } }, ] } In my case it's something as:
commands.registerCommand('pydev.start.debugger', () => { return { command: "C:/bin/python27/python.exe", // paths initially hardcoded for simplicity args: ["X:/vscode-pydev/vscode-pydev/src/debug_adapter/debugger_protocol.py"] } }); Another interesting point is that when VSCode launches the debug adapter it'll use stdin and stdout to communicate with the adapter (this makes some things a bit quirky to develop the debugger because you have to (initially) resort to printing debug information to a file to be able to check what's happening, although on the bright side, you won't have to worry about having a firewall at that point).
The first message that arrives from stdin is:
Content-Length: 312\r\n \r\n { "arguments": { "adapterID": "PyDev", "clientID": "vscode", "clientName": "Visual Studio Code", "columnsStartAt1": true, "linesStartAt1": true, "locale": "en-us", "pathFormat": "path", "supportsRunInTerminalRequest": true, "supportsVariablePaging": true, "supportsVariableType": true }, "command": "initialize", "seq": 1, "type": "request" } -- this is the InitializeRequest in the json schema.
{ "seq": 1, "request_seq": 1, "command": "initialize","body": {"supportsConfigurationDoneRequest": true,"type": "response", "success": true }"supportsConditionalBreakpoints": true},
-- this is the InitializeResponse in the json schema.
and then send and event saying that it has initialized properly:
{"type": "event", "event": "initialized", "seq": 2} -- this is the InitializedEvent in the json schema.
{ "arguments": { "__sessionId": "474aa497-0a90-4b30-8cc6-edf3bebbe703", "args": "", "console": "integratedTerminal", "cwd": "X:\\vscode_example", "name": "PyDev Debug (Launch)", "program": "X:/vscode_example/robots.py", "request": "launch", "type": "PyDev" }, "command": "launch", "seq": 2, "type": "request" } { "request_seq": 2, "command": "launch", "body": {}, "type": "response", "success": true } -- this is the LaunchResponse in the json schema.
- Niceties from PyDev when typing such as auto-adding self where needed (note that having the editor.formatOnType setting turned on is a requisite for that to work).
- Really fast code-completion, code-analysis and code-formatting engines.
- Code completion provides options to import modules, top level classes, methods and variables (python.pydev.preferredImportLocation can be used to determine the location of the import).
- Quick fix which automatically allows adding an import for unresolved symbols.
- In-file navigation to previous or next class or method through Ctrl+Shift+Up and Ctrl+Shift+Down.
The extension will provide a command which should open a few options for the user on how he wants to do the profile (with yappi, cProfile or start without profiling but connected with the live sampling view).
I went with https://code.visualstudio.com/docs/extensions/yocode to bootstrap the extension, which gives a template with a command to run then renamed a bunch of things (such as the extension name, description, command name, etc).
Next was finding a way to ask the user for the options (to ask how the profile should be started). Searching for it revealed https://tstringer.github.io/nodejs/javascript/vscode/2015/12/14/input-and-output-vscode-ext.html, so, I went with creating the needed constants and going with vscode.window.showQuickPick (experimenting, undefined is returned if the user cancel the action, so, that needs to be taken into account too).
Now, after the user chooses how to start PyVmMonitor, the idea would be making any launch actually start in the chosen profile mode (which is how it works in PyDev).
After investigating a bit, I couldn't find out how to intercept an existing launch to modify the command line to add the needed parameters for profiling with PyVmMonitor, so, this integration will be a bit more limited than the one in PyDev as it will simply create a new terminal and call PyVmMonitor asking it to profile the currently opened module...
In the other integrations, it was done as a setting where the user selected that it wanted to profile any python launch from a given point onward as a toggle and then intercepted launches changing the command line given, so, for instance, it could intercept a unittest launch too, but in this case, it seems that there's currently no way to do that -- or some ineptitude on my part finding an actual API to do it ;)
Now, searching on the VSCode Python plugin, I found a "function execInTerminal", so, I based the launching in it (but not using its settings as I don't want to add a dependency on it for now, so, I just call `python` -- if that's wrong, as it opens a shell, the user is free to cancel that and correct the command line to use the appropriate python interpreter or change it as needed later on).
Ok, wrapping up: put the initial version of the code on https://github.com/fabioz/vscode-pyvmmonitor. Following https://code.visualstudio.com/docs/extensions/publish-extension did work out, so, there's a "Profile Python with PyVmMonitor" extension now ;).
Some notes I took during the process related to things I stumbled on or found awkard:
- After publishing the first time and installing, the extension wasn't working because I wrongly put a dependency from npm in "devDependencies" and not in "dependencies" (the console in the developer tools helped in finding out that the dependency wasn't being loaded after the extension was installed).
- When a dependency is added/removed, npm install needs to be called again, it's not automatic.
- When uploading the extension I had the (common) error of not generating a token for "all" ;)
- Apparently there's a "String" and a "string" in TypeScript (or at least within the dependencies when editing VSCode).
- The whole launching on VSCode seems a bit limited/ad-hoc right now (for instance, .launch files create actual launchers which can be debugged but the python extension completely bypasses that by doing a launch in terminal for the current file -- probably because it's a bit of a pain creating that .launch file) -- I guess this reflects how young VSCode is... on the other hand, it really seems it built upon previous experience as the commands and bindings seems to have evolved directly to a good design (Eclipse painfully iterated over several designs on its command API).
- Extensions seem to be limited by design. I guess this is good and bad at the same time... good that extensions should never slow down the editor, but bad because they are not able to do something which is not in the platform itself to start with -- for instance, I really missed a good unittest UI when using it... there are actually many other things I missed from PyDev, although I guess this is probably a reflect on how young the platform is (and it does seem to be shaping up fast).


