Jurigged lets you update your code while it runs.

Overview

jurigged

Jurigged lets you update your code while it runs. Using it is trivial:

  1. python -m jurigged your_script.py
  2. Change some function or method with your favorite editor and save the file
  3. Jurigged will hot patch the new function into the running script

Jurigged updates live code smartly: changing a function or method will fudge code pointers so that all existing instances are simultaneously modified to implement the new behavior. When modifying a module, only changed lines will be re-run.

Install

pip install jurigged

Command line

The simplest way to use jurigged is to add -m jurigged to your script invocation, or to use jurigged instead of python. You can use -v to get feedback about what files are watched and what happens when you change a file.

python -m jurigged -v script.py

OR

jurigged -v script.py

With no arguments given, it will start a live REPL:

python -m jurigged

Full help:

usage: jurigged [-h] [--verbose] [--watch PATH] [-m MODULE] [PATH] ...

Run a Python script so that it is live-editable.

positional arguments:
  PATH                  Path to the script to run
  ...                   Script arguments

optional arguments:
  -h, --help            show this help message and exit
  --verbose, -v         Show watched files and changes as they happen
  --watch PATH, -w PATH
                        Wildcard path/directory for which files to watch
  -m MODULE             Module or module:function to run

Troubleshooting

First, if there's a problem, use the verbose flag (jurigged -v) to get more information. It will output a Watch statement for every file that it watches and Update/Add/Delete statements when you update, add or delete a function in the original file and then save it.

The file is not being watched.

By default, scripts are watched in the current working directory. Try jurigged -w to watch a specific file, or jurigged -w / to watch all files.

The file is watched, but nothing happens when I change the function.

It's possibly because you are using an editor that saves into a temporary swap file and moves it into place (vi does this). The watchdog library that Jurigged uses loses track of the file when that happens. Pending a better solution, you can try to configure your editor so that it writes to the file directly. For example, in vi, :set nowritebackup seems to do the trick (either put it in your .vimrc or execute it before you save for the first time).

Jurigged said it updated the function but it's still running the old code.

If you are editing the body of a for loop inside a function that's currently running, the changes will only be in effect the next time that function is called. A workaround is to extract the body of the for loop into its own helper function, which you can then edit. Alternatively, you can use reloading alongside Jurigged.

Similarly, updating a generator or async function will not change the behavior of generators or async functions that are already running.

I can update some functions but not others.

There may be issues updating some functions when they are decorated or stashed in some data structure that Jurigged does not understand. Jurigged does have to find them to update them, unfortunately.

What does "failed update" mean?

Jurigged does not allow changing a function's decorators.

API

You can call jurigged.watch() to programmatically start watching for changes. This should also work within IPython or Jupyter as an alternative to the %autoreload magic.

import jurigged
jurigged.watch()

By default all files in the current directory will be watched, but you can use jurigged.watch("script.py") to only watch a single file, or jurigged.watch("/") to watch all modules.

Recoders

Functions can be programmatically changed using a Recoder. Make one with jurigged.make_recoder. This can be used to implement hot patching or mocking. The changes can also be written back to the filesystem.

from jurigged import make_recoder

def f(x):
    return x * x

assert f(2) == 4

# Change the behavior of the function, but not in the original file
recoder = make_recoder(f)
recoder.patch("def f(x): return x * x * x")
assert f(2) == 8

# Revert changes
recoder.revert()
assert f(2) == 4

# OR: write the patch to the original file itself
recoder.commit()

revert will only revert up to the last commit, or to the original contents if there was no commit.

A recoder also allows you to add imports, helper functions and the like to a patch, but you have to use recoder.patch_module(...) in that case.

Caveats

Jurigged works in a surprisingly large number of situations, but there are several cases where it won't work, or where problems may arise:

  • Functions that are already running will keep running with the existing code. Only the next invocations will use the new code.
    • When debugging with a breakpoint, functions currently on the stack can't be changed.
    • A running generator or async function won't change.
    • You can use reloading in addition to Jurigged if you want to be able to modify a running for loop.
  • Changing initializers or attribute names may cause errors on existing instances.
    • Jurigged modifies all existing instances of a class, but it will not re-run __init__ or rename attributes on existing instances, so you can easily end up with broken objects (new methods, but old data).
  • Existing closures can't be changed.
    • Unlike top-level functions where all existing pointers will automagically use the new code, existing closures will keep using their old code.
    • Here, by "closure" I mean a function defined inside another function, and by "existing" I mean closure instances that were already returned by their enclosing function or stored in a data structure, so they're basically in the wild and jurigged can't see them.
  • Decorators cannot be changed.
    • Most wrapped/decorated functions can be changed, but that's because jurigged plows through closures to find the original functions and changes them in place (usually that does the trick). So even though it works on many decorated functions, jurigged does not re-run decorators, and because of this it will refuse to update if the decorators have changed.
    • Workaround: you can delete a function, save, paste it back, save again (in short: Ctrl+X, Ctrl+S, Ctrl+V, Ctrl+S). Jurigged will forget about the function once it's deleted and then it will count as an addition rather than a change. In that case it will run the new decorators. However, existing pointers to the old function won't be updated.
  • Decorators that look at/tweak function code will probably not update properly.
    • Wrappers that try to compile/JIT Python code won't know about jurigged and won't be able to redo their work for the new code.
    • They can be made to work if they set the (jurigged-specific) __conform__ attribute on the old function. __conform__ takes a reference to the function that should replace it.

How it works

In a nutshell, jurigged works as follows:

  1. Insert an import hook that collects and watches source files.
  2. Parse a source file into a set of definitions.
  3. Crawl through a module to find function objects and match them to definitions.
    • It will go through class members, follow functions' __wrapped__ and __closure__ pointers, and so on.
  4. When a file is modified, re-parse it into a set of definitions and match them against the original, yielding a set of changes, additions and deletions.
  5. For a change, exec the new code (with the decorators stripped out), then take the resulting function's internal __code__ pointer and shove it into the old one (this may not work on closures, but jurigged ignores closures).
  6. New additions are run in the module namespace.

Comparison

The two most comparable implementations of Jurigged's feature set that I could find (but it can be a bit difficult to find everything comparable) are %autoreload in IPython and limeade. Here are the key differences:

  • They both re-execute the entire module when its code is changed. Jurigged, by contrast, surgically extracts changed functions from the parse tree and only replaces these. It only executes new or changed statements in a module.

    Which is better is somewhat situation-dependent: on one hand, re-executing the module will pick up more changes. On the other hand, it will reinitialize module variables and state, so certain things might break. Jurigged's approach is more conservative and will only pick up on modified functions, but it will not touch anything else, so I believe it is less likely to have unintended side effects. It also tells you what it is doing :)

  • They will re-execute decorators, whereas Jurigged will instead dig into them and update the functions it finds inside.

    Again, there's no objectively superior approach. %autoreload will properly re-execute changed decorators, but these decorators will return new objects, so if a module imports an already decorated function, it won't update to the new version. If you only modify the function's code and not the decorators, however, Jurigged will usually be able to change it inside the decorator, so all the old instances will use the new behavior.

  • They rely on synchronization points, whereas Jurigged can be run in its own thread.

    This is a double-edged sword, because even though Jurigged can add live updates to existing scripts with zero lines of additional code, it is not thread safe at all (code could be executed in the middle of an update, which is possibly an inconsistent state).

Other similar efforts:

  • reloading can wrap an iterator to make modifiable for loops. Jurigged cannot do that, but you can use both packages at the same time.
Comments
  • Idea for mapping between codes and functions

    Idea for mapping between codes and functions

    Here's an idea for how you might be able to find the correct code and function objects. I'm not 100% sure if this would actually fit in the way you're doing things but I thought it might help. I also don't actually know how reliable gc is for this, it might be, but I haven't really investigated.

    import gc
    import types
    
    
    def find_subcodes(code):
        """
        Yields all code objects descended from this code object.
        e.g. given a module code object, will yield all codes defined in that module.
        """
        for const in code.co_consts:
            if isinstance(const, types.CodeType):
                yield const
                yield from find_subcodes(const)
    
    
    def get_functions(code):
        """
        Returns functions that use the given code.
        """
        return [
            ref
            for ref in gc.get_referrers(code)
            if isinstance(ref, types.FunctionType)
               and ref.__code__ == code
        ]
    
    
    module_code = compile("""
    def foo():
        def bar():
            pass
        return bar
    """, "<string>", "exec")
    
    codes = list(find_subcodes(module_code))
    
    exec(module_code)
    
    bars = [foo(), foo()]
    
    code_to_functions = {code: set(get_functions(code)) for code in codes}
    
    print(code_to_functions)
    
    assert code_to_functions == {foo.__code__: {foo}, bars[0].__code__: set(bars)}
    
    opened by alexmojaki 9
  • Working great on Ubuntu, but not on Windows

    Working great on Ubuntu, but not on Windows

    First of all - I love this code. Thank you so much for creating it. I'm using it to create a live-coding environment to make music with SuperCollider.

    I had no problems running jurigged on Ubuntu, but I can't get it to work on Windows. I tried running it the same way: python3 -m jurigged -v livecoding.py but on Windows it never registers any change in the file when I save. I'm using Sublime on Windows to save. My python version is 3.9.7 and Windows version is 10. Is there a known issue with Windows?

    opened by schollz 5
  • UnicodeDecodeError: 'gbk' codec can't decode byte 0xaf in position 594: illegal multibyte sequence

    UnicodeDecodeError: 'gbk' codec can't decode byte 0xaf in position 594: illegal multibyte sequence

    Traceback (most recent call last):
      File "C:\Users\InPRTx\AppData\Local\Programs\Python\Python38\lib\runpy.py", line 194, in _run_module_as_main
        return _run_code(code, main_globals, None,
      File "C:\Users\InPRTx\AppData\Local\Programs\Python\Python38\lib\runpy.py", line 87, in _run_code
        exec(code, run_globals)
      File "C:\Users\InPRTx\AppData\Local\Programs\Python\Python38\lib\site-packages\jurigged\__main__.py", line 4, in <module>
        cli()
      File "C:\Users\InPRTx\AppData\Local\Programs\Python\Python38\lib\site-packages\jurigged\live.py", line 258, in cli
        mod, run = find_runner(opts, pattern)
      File "C:\Users\InPRTx\AppData\Local\Programs\Python\Python38\lib\site-packages\jurigged\live.py", line 183, in find_runner
        registry.prepare("__main__", path)
      File "C:\Users\InPRTx\AppData\Local\Programs\Python\Python38\lib\site-packages\jurigged\register.py", line 60, in prepare
        f.read(),
    UnicodeDecodeError: 'gbk' codec can't decode byte 0xaf in position 594: illegal multibyte sequence
    

    jurigged 0.3.3

    It seems that it is not set to open the file with utf8 encoding by default

    https://github.com/breuleux/jurigged/blob/75dc1a599a8c6c2807cb114e66f9b59f986b8913/jurigged/register.py#L57

    opened by InPRTx 3
  • Support for pre- and post-update hooks

    Support for pre- and post-update hooks

    Essentially, I was hoping that it were possible to add support for custom hooks to run before and after updating the code in memory so that you can run pre- and post-update tasks.

    This is a fairly niche use case, but a Discord bot framework I'm working on (https://github.com/NAFTeam/NAFF) is looking to implement jurigged for fast-paced development of Discord bots, and re-registering command with Discord whenever files are changed. However, it's not really feasible, as doing so requires tracking everything both before and after the change, which currently isn't possible (that I'm aware of)

    opened by zevaryx 2
  • Files not reloaded with PyCharm with Windows

    Files not reloaded with PyCharm with Windows

    Description

    On Windows, jurigged does not seem to reload a file it's running after I edit it with PyCharm. I have read this may be an issue with how WatchDog monitors for file updates, but I didn't see JetBrains IDEs mentioned specifically so I'm not sure if it's expected that this would work on them.

    It may be nice to include a table of editors which are known to be compatible with jurigged, and any notes for making them work if necessary.

    Steps to reproduce

    1. Create a new project in PyCharm
    2. Write the script
    from time import sleep
    
    def do_something():
        print("Hello")
    
    def main():
        for _ in range(100):
            do_something()
            sleep(1)
    
    if __name__ == '__main__':
        main()
    
    1. Run the script with jurigged -v main.py in your terminal
    2. As the script is printing "Hello" repeatedly, change print("Hello") to print("Hi") and click out of the file so it saves.

    Expected behavior

    The script would go from printing "Hello" to "Hi"

    Actual behavior

    The script continues to print "Hello"

    Platform

    PyCharm version

    PyCharm 2021.2.2 (Professional Edition) Build #PY-212.5284.44, built on September 14, 2021

    Python version

    Python 3.10.0

    Dependency versions

    ansicon==1.89.0
    blessed==1.19.0
    codefind==0.1.2
    jinxed==1.1.0
    jurigged==0.3.4
    ovld==0.3.2
    six==1.16.0
    watchdog==1.0.2
    wcwidth==0.2.5
    

    Device specifications

    Processor	Intel(R) Core(TM) i9-9900K CPU @ 3.60GHz   3.60 GHz
    Installed RAM	32.0 GB (31.9 GB usable)
    System type	64-bit operating system, x64-based processor
    

    Windows specifications

    Edition	Windows 11 Education Insider Preview
    Version	Dev
    Installed on	‎10/‎19/‎2021
    OS build	22478.1012
    Experience	Windows Feature Experience Pack 1000.22478.1012.0
    
    opened by nottheswimmer 2
  • No matching distribution found for jurigged

    No matching distribution found for jurigged

    ERROR: Could not find a version that satisfies the requirement jurigged (from versions: none)
    ERROR: No matching distribution found for jurigged
    

    Pip 21.2.2 Python 3.7.11

    Any idea on how to fix that, please?

    opened by desprit 2
  • File change not detected

    File change not detected

    I have the following snippet:

    import time
    
    import jurigged
    
    
    def experiment(*args, **kwargs):
        def decorator(func):
    
            while True:
                time.sleep(0.1)
                res = func(*args, **kwargs)
                print(repr(res), end="\r")
    
        return decorator
    
    
    if __name__ == "__main__":
    
        @experiment()
        def main():
            a = 2
            return 1
    

    If I run:

    jurigged -v essai.py 
    Watch /tmp/essai.py
    

    I do get "1", but if I modify the code, jurigged doesn't "Update main.main" as usual. I suppose the decorator does something to it.

    opened by ksamuel 2
  • Add pre- and post-run callback support

    Add pre- and post-run callback support

    As per #18 , this adds (basic) callback support for pre- and post-refresh. It doesn't pass in anything into the callbacks, but is meant to provide a basic means of adding this support.

    If you have any suggestions for edits, please let me know and I'll get them made.

    opened by zevaryx 1
  • Can't pass arguments that start with -- to module

    Can't pass arguments that start with -- to module

    If you attempt to do jurigged -m module --argument 123, argparse inside jurigged will get angry and throw unrecognized arguments.

    As a workaround you can pass "" after -m module.

    opened by OctoNezd 1
  • watchdog version is incompatible

    watchdog version is incompatible

    When I install jurigged, by default it requires watchdog version <2.0.0, >=1.0.2 and it automatically install watch 1.0.2. But mkdocs 1.2.2 is also a dependency of jurigged which requires watchdog >=2.0. The wathcdog 1.0.2 which install by default is incompatible with it. How to solve this dependency issue?

    image image

    opened by yesdeepakmittal 1
  • Errorr using jurigged on docker

    Errorr using jurigged on docker

    I am getting an error when i am using with docker:

    Traceback (most recent call last):
      File "/usr/local/bin/jurigged", line 8, in <module>
        sys.exit(cli())
      File "/usr/local/lib/python3.9/site-packages/jurigged/live.py", line 268, in cli
        run()
      File "/usr/local/lib/python3.9/site-packages/jurigged/live.py", line 188, in run
        runpy.run_path(path, module_object=mod)
      File "/usr/local/lib/python3.9/site-packages/jurigged/runpy.py", line 279, in run_path
        code, fname = _get_code_from_file(run_name, path_name)
      File "/usr/local/lib/python3.9/site-packages/jurigged/runpy.py", line 252, in _get_code_from_file
        with io.open_code(decoded_path) as f:
    FileNotFoundError: [Errno 2] No such file or directory: '/srv/www/s'
    

    Dockerfile

    
    FROM python:3.9-slim
    
    RUN mkdir /srv/www/
    
    ADD ./ /srv/www/
    
    WORKDIR /srv/www/
    
    ENV GRPC_PYTHON_VERSION 1.39.0
    RUN python -m pip install --upgrade pip
    RUN pip install grpcio==${GRPC_PYTHON_VERSION} grpcio-tools==${GRPC_PYTHON_VERSION} grpcio-reflection==${GRPC_PYTHON_VERSION} grpcio-health-checking==${GRPC_PYTHON_VERSION} grpcio-testing==${GRPC_PYTHON_VERSION}
    
    RUN pip install -r requirements.txt
    
    RUN pip install jurigged
    ENTRYPOINT ["jurigged"]
    CMD ["main.py"]
    
    

    Python file main.py

    import time
    import datetime
    
    def main():
        while True:
            print("Executed %s" % datetime.datetime.now())
            time.sleep(5)
    
    
    main()
    
    
    opened by luhfilho 1
  • Workaround for Geany IDE: Detect other types of file modifications: file move, file close, file overwritten

    Workaround for Geany IDE: Detect other types of file modifications: file move, file close, file overwritten

    This is related to how Geany handles files by default: It creates a temporary file copy, writes the modifications to it and them copies the modifications to the current file. These steps are made to prevent file corruption when there is no space left in the device. Unfortunately this is not detected as a modification by jurigged and the file is not hot-reloaded

    these are the events I logged by using on_any_event inside the class JuriggedHandler(FileSystemEventHandler):

    event type: created path src : app_path/test_flask_hotreloading/.goutputstream-6PJ0X1 event type: modified path src : app_path/test_flask_hotreloading event type: modified path src : app_path/test_flask_hotreloading/.goutputstream-6PJ0X1 event type: closed path src : app_path/test_flask_hotreloading/test_flask_hello.py event type: modified path src : app_path/test_flask_hotreloading event type: modified path src : app_path/test_flask_hotreloading/.goutputstream-6PJ0X1 event type: moved path src : app_path/test_flask_hotreloading/.goutputstream-6PJ0X1 event type: modified path src : app_path/test_flask_hotreloading event type: closed path src : app_path/test_flask_hotreloading/test_flask_hello.py event type: modified path src : app_path/test_flask_hotreloading

    I propose the following temporary workaround:

        on_closed = on_modified
    

    however, the best way can be to use only an on_any_event event and them, inside it handle all possible situations:

    def on_any_event(self, event):
        print(f'event type: {event.event_type}  path src : {event.src_path}', flush = True)
    

    a more aggressive approach:

    def on_any_event(self, event):
        print(f'event type: {event.event_type}  path src : {event.src_path}', flush = True)
        if ".py" in event.src_path:
            #do reload procedure
    

    I've not made a pull request because it is open to discussion, but I'm using the temporary workaround.

    opened by animaone 0
  • jurigged -v <script.py> executes and exits

    jurigged -v executes and exits

    hi.py:

     print('hi')                                              
    
    jurigged -v hi.py      
    Watch /path/hi.py
    hi
    

    The process simply exits after executing the script once. How do I get the reloading to work on a M1 mac?

    opened by mereck 0
  • jurigged -w starts python interactive mode

    jurigged -w starts python interactive mode

    jurigged -w should start jurigged watching only the specified file.

    But: jurigged -w script.py shows this: image

    If I try: jurigged -w script.py script.py the script starts but It watch for the entire current directory instead of just the specified file.

    I'd just want to start jurigged and watch for only one file, am I doing it wrongly?

    opened by mopitithomne 5
  • Maybe a file name not supported by win was used

    Maybe a file name not supported by win was used

    PS C:\Users\InPRTx\Desktop\git> git clone https://github.com/breuleux/jurigged.git Cloning into 'jurigged'... remote: Enumerating objects: 878, done. remote: Counting objects: 100% (878/878), done. remote: Compressing objects: 100% (457/457), done. Receiving objects: 94% (826/878)used 618 (delta 393), pack-reused 0 Receiving objects: 100% (878/878), 203.28 KiB | 1.83 MiB/s, done. Resolving deltas: 100% (612/612), done. error: invalid path 'tests/snippets/ballon:main.py' fatal: unable to checkout working tree warning: Clone succeeded, but checkout failed. You can inspect what was checked out with 'git status' and retry with 'git restore --source=HEAD :/'

    opened by InPRTx 0
Owner
Olivier Breuleux
Olivier Breuleux
The best way to have DRY Django forms. The app provides a tag and filter that lets you quickly render forms in a div format while providing an enormous amount of capability to configure and control the rendered HTML.

django-crispy-forms The best way to have Django DRY forms. Build programmatic reusable layouts out of components, having full control of the rendered

null 4.6k Dec 31, 2022
The best way to have DRY Django forms. The app provides a tag and filter that lets you quickly render forms in a div format while providing an enormous amount of capability to configure and control the rendered HTML.

django-crispy-forms The best way to have Django DRY forms. Build programmatic reusable layouts out of components, having full control of the rendered

null 4.6k Jan 7, 2023
🎓Automatically Update CV Papers Daily using Github Actions (Update at 12:00 UTC Every Day)

??Automatically Update CV Papers Daily using Github Actions (Update at 12:00 UTC Every Day)

Realcat 270 Jan 7, 2023
Python function to extract all the rows from a SQLite database file while iterating over its bytes, such as while downloading it

Python function to extract all the rows from a SQLite database file while iterating over its bytes, such as while downloading it

Department for International Trade 16 Nov 9, 2022
A tool for study using pomodoro methodology, while study mode spotify or any other .exe app is opened and while resting is closed.

Pomodoro-Timer-With-Spotify-Connection A tool for study using pomodoro methodology, while study mode spotify or any other .exe app is opened and while

null 2 Oct 23, 2022
MultiPy lets you conveniently keep track of your python scripts for personal use or showcase by loading and grouping them into categories. It allows you to either run each script individually or together with just one click.

MultiPy About MultiPy is a graphical user interface built using Dear PyGui Python GUI Framework that lets you conveniently keep track of your python s

null 56 Oct 29, 2022
A very minimalistic python module that lets you track the time your code snippets take to run.

Clock Keeper A very minimalistic python module that lets you track the time your code snippets take to run. This package is available on PyPI! Run the

Rajdeep Biswas 1 Jan 19, 2022
This python application let you check for new announcements from MMLS, take attendance while your lecturer is sharing QR Code on the screen.

This python application let you check for new announcements from MMLS, take attendance while your lecturer is sharing QR Code on the screen.

wyhong3103 5 Jul 17, 2022
A HTML-code compiler-thing that lets you reuse HTML code.

RHTML RHTML stands for Reusable-Hyper-Text-Markup-Language, and is pronounced "Rech-tee-em-el" despite how its abbreviation is. As the name stands, RH

Duckie 4 Nov 15, 2021
🐸 Identify anything. pyWhat easily lets you identify emails, IP addresses, and more. Feed it a .pcap file or some text and it'll tell you what it is! 🧙‍♀️

?? Identify anything. pyWhat easily lets you identify emails, IP addresses, and more. Feed it a .pcap file or some text and it'll tell you what it is! ??‍♀️

Brandon 5.6k Jan 3, 2023
aws-lambda-scheduler lets you call any existing AWS Lambda Function you have in a future time.

aws-lambda-scheduler aws-lambda-scheduler lets you call any existing AWS Lambda Function you have in the future. This functionality is achieved by dyn

Oğuzhan Yılmaz 57 Dec 17, 2022
A simple tool that lets you know when you are out of Lost Ark's queues

Overview A simple tool that lets you know when you are out of Lost Ark's queues. You can be notified via: Sound: the app will play a sound Discord web

Nelson 3 Feb 15, 2022
uMap lets you create maps with OpenStreetMap layers in a minute and embed them in your site.

uMap project About uMap lets you create maps with OpenStreetMap layers in a minute and embed them in your site. Because we think that the more OSM wil

null 771 Dec 29, 2022
simpleT5 is built on top of PyTorch-lightning⚡️ and Transformers🤗 that lets you quickly train your T5 models.

Quickly train T5 models in just 3 lines of code + ONNX support simpleT5 is built on top of PyTorch-lightning ⚡️ and Transformers ?? that lets you quic

Shivanand Roy 220 Dec 30, 2022
This utility lets you draw using your laptop's touchpad on Linux.

FingerPaint This utility lets you draw using your laptop's touchpad on Linux. Pressing any key or clicking the touchpad will finish the drawing

Wazzaps 95 Dec 17, 2022
This Web App lets you convert your Normal Image to a SKETCHED one within a minute

This Web App lets you convert your Normal Image to a SKETCHED one within a minute

Avinash M 25 Nov 10, 2022
a python package that lets you add custom colors and text formatting to your scripts in a very easy way!

colormate Python script text formatting package What is colormate? colormate is a python library that lets you add text formatting to your scripts, it

Rodrigo 2 Dec 14, 2022
An addin for Autodesk Fusion 360 that lets you view your design in a Looking Glass Portrait 3D display

An addin for Autodesk Fusion 360 that lets you view your design in a Looking Glass Portrait 3D display

Brian Peiris 12 Nov 2, 2022
Python Program that lets you write in your handwriting!

Handwriting with Python Python Program that lets you write in your handwriting! Inspired by: thaisribeiro.in How to run? Install Unidecode and Pillow

Amanda Rodrigues Vieira 2 Oct 25, 2021
A program that lets you use your tablet's tilting to emulate an actual joystick on a Linux computer.

Tablet Tilt Joystick A program that lets you use your tablet's tilting to emulate an actual joystick on a Linux computer. It's called tablet tilt joys

null 1 Feb 7, 2022