Python Automation Software; Decorators as Entry-Points.
Project description
Pybiosis
What could empower you more than a symbiotic relationship with Python?
Pybiosis is an automation software that focuses on making python functions more accessible by providing versatile entry-points to functions. This project makes heavy use of decorators, which define the entry-points. Currently, there are existing implementations for services like StreamDeck, Google Assistant, and Windows Task Scheduler. Pybiosis also provides a CLI, a GUI CLI (using gooey), and a GUI (using streamlit) to access these functions. For example, even if you don't have a StreamDeck you can still access an analogous interface using the GUI!
How It Works
Example:
Wrap your functions in decorators to add entry points from other devices and services:
from pybiosis import *
@Device(title='Spire', description="Launch Slay the Spire.")
@Google(voice=multi_phrase(['open', 'play', 'place'], ['spire', 'fire', 'buyer']))
@Deck(location="Games/3,1", image='spire.jpg')
@Scheduler(trigger="Daily", start="today-5:01pm")
def spire():
# Launch the game shortcut using cmd.
path_to_game = R"C:\Games\Slay the Spire.url"
os.system(Rf'cmd.exe /C START "" "{path_to_game}"')
In this example, I can launch the specified game from: a Graphical User Interface (GUI), a Command-Line Interface (CLI), Voice Commands (through Google Assistant), the StreamDeck Hardware, and on a schedule.
Let's unpack all of that!
First, the function itself spire()
launches a game called Slay the Spire from my installation folder for games.
Then, there are the decorators:
-
@Device(title='Spire', description="Launch Slay the Spire.")
- The
Device
decorator simply provides metadata to the function.
- The
-
@Google(voice=multi_phrase(['open', 'play', 'place'], ['spire', 'fire', 'buyer']))
- The
Google
decorator provides an entry point through Google Assistant. - With the appropriate setup, the user would trigger the function with "Hey Google, on pc, play spire".
- The
multi_phrase
function uses synonyms to accommodate phrases that may be misunderstood by the voice recognition.
- The
-
@Deck(location="Games/3,1", image='spire.jpg')
- The
StreamDeck
is a device with programmable buttons and this decorator places a button in a specific location (with an iconspire.jpg
).
- The
-
@Scheduler(trigger="Daily", start="today-5:01pm")
- The
Scheduler
decorator executes the provided function regularly, in this case right on time to unwind!
- The
Decorators:
Each decorator is powered by a compiler that connects that function to a given service or device. Here are some basic examples. See Compilers below for more details.
-
Create a simple function.
import webbrowser def func(): webbrowser.open("www.google.com")
-
Provide a title and description to the function.
@Device(title='My First Function', description="This is a demonstration.") def func(): pass
-
Attach it to a service, like the Google Assistant (see Compiler Examples below).
@Device(title='My First Function', description="This can be called from the Assistant.") @Assistant(phrase="first") def func(): pass
-
Note that the
Device
is optional (when included though, it should always be the highest decorator).@Assistant(phrase="first") def func(): pass
-
Control what happens to the command window that opens when a function is triggered.
@Device(show=True, pause=True) # Make a terminal window appear, and pause when execution is finished. @StreamDeck(phrase="settings") def func(): pass
-
Easily apply a list of decorators (this may be useful for a list comprehension).
@apply_list([Scheduler(trigger='daily', start="2022/05/14-08:30"), Scheduler(trigger='daily', start="2022/05/14-05:00")]) def func(): pass
Class syntax vs Function syntax
The number of functions can accumulate quickly, so to improve organization the class syntax
uses nested classes
to mimic a folder structure, as syntatic sugar.
@register(globals())
class Monitor:
MAX_BRIGHTNESS: int = 100
class Brightness:
@StreamDeck(location='Monitors/Display\nSettings/1,0')
def brightness_up(): # Note that "up()" isn't used since it would clash with Contrast.
pass
@StreamDeck(location='Monitors/Display\nSettings/2,0')
def brightness_down():
pass
class Contrast:
@StreamDeck(location='Monitors/Display\nSettings/3,0')
def contrast_up():
pass
@StreamDeck(location='Monitors/Display\nSettings/4,0')
def contrast_down():
pass
To understand class syntax, we need to know a little bit about how Pybiosis works under the hood.
Each function must be accessible at the module level. This means that Pybiosis expects to be able to call something like import monitors; monitors.brightness_up()
. The class syntax is nice because it allows us to organize the functions logically, but it complicates the namespace.
This is what the register
decorator resolves. It decomposes the nested classes and puts the methods back into the global namespace (this is why globals()
is used, and why the methods don't take self
). So, even with the nesting, we can call monitors.brightness_up()
directly. Note that class attributes do need to be fully qualified: i.e. Monitor.MAX_BRIGHTNESS
not just MAX_BRIGHTNESS
.
Furthermore, some decorators (eg: streamdeck, scheduler) save an execution string (python ...
) in a .vbs
and .bat
file. Those allow us to control whether a debug window pops up, for example. This accessible format allows the functions to be called from other programs that can't easily access python directly. For example, the StreamDeck doesn't easily support executing the right python command, but it easily supports running those script files.
Compilers
Pybiosis comes with some built-in device compilers. You may also define your own if you have a novel device.
StreamDeck
Requires a StreamDeck.
- Specify a location for the button to be placed specifying a folder, row, and column. See Limitations about needing to manually create folders through the StreamDeck GUI.
@StreamDeck(location='Folder/On/Stream/Deck/3,2') def func() pass
- You can also specify multiple locations.
@StreamDeck(location=['Path1/3,2', 'Path2/3,2']) def func() pass
- And an image. Images should be placed in
PYBIOSIS_USER_PATH/Images
. See Installation for more details.@StreamDeck(location='Path/3,2', image="my_image.png") def func() pass
If you use multiple StreamDeck profiles, you can set the Environment Variable PYBIOSIS_PROFILE_ID
to the desired identifier (without .sdProfile
). The identifiers can be found in AppData\Roaming\Elgato\StreamDeck\ProfilesV2
.
Google Assistant
Requires a Push2Run installation and access to a Google Assistant device. Push2Run has been tested with the Dropbox method and the key phrase "on pc", although the API has historically been depreciated and possibly restored, so please check the link for the current status.
- The simpliest way to register a command is with a single word.
@Assistant(phrase="Hello") def greet(): pass
- Sometimes a single word can be mis-heard or you may want more freedom, a list can provide synonyms.
@Assistant(phrase=["loop", "lamp", "new", "blue"]) def loop(): pass
- To register a phrase, use the
multi_phrase
function. Note that each word is wrapped in a list.@Assistant(phrase=multi_phrase(['slay'], ['the'], ['spire'])) def slay_the_spire(): pass
- To register a phrase with synonyms, provide them in the list.
@Assistant(phrase=multi_phrase(['mode', 'mod'], ['the'], ['spire', 'fire', 'buyer'])) def mod_the_spire(): os.chdir(R"C:\Program Files (x86)\Steam\steamapps\common\SlayTheSpire") os.system(R'jre\bin\java.exe -jar mts-launcher.jar')
Scheduler
Requires a Windows machine. No installation is required since it uses the Windows Task Scheduler. See schtasks.exe
for more details on usage.
-
Schedule a function to run now (in the next minute).
@Scheduler(trigger='once', start='now') def hourly_beep(): import winsound # imports can be local winsound.Beep(1000, 200)
-
Schedule a function to run daily at 8am.
@Scheduler(trigger='daily', start=f"2022/05/14-08:00") def hourly_beep(): import winsound # imports can be local winsound.Beep(1000, 200)
-
Schedule a function to run multiple times.
@Scheduler(trigger='daily', start=f"2022/05/14-05:00") @Scheduler(trigger='daily', start=f"2022/05/14-08:00") def hourly_beep(): import winsound winsound.Beep(1000, 200)
-
Schedule a function to run at multiple times within an interval using the
apply_list
decorator.@apply_list([Scheduler(trigger='daily', start=f"2022/05/14-1{i}:00") for i in range(0, 7+1)]) def hourly_beep(): import winsound winsound.Beep(1000, 200)
CLI
You can access the full CLI, including any functions that are decorated (using run
).
python -m pybiosis --help # Get CLI usage information.
python -m pybiosis config --set user_path /Path/To/User/Path/ # Set the user path.
python -m pybiosis config --list # List config variables.
python -m pybiosis compile # Compile all decorated functions.
python -m pybiosis user # Launch the user driver.py file (could be a CLI or main module).
python -m pybiosis run monitors.to_70 # Run a specific user function.
python -m pybiosis gui # Launch the GUI to access functions graphically.
python -m pybiosis # Launch the CLI as a simple GUI.
Please note that two aliases are also registered: pybiosis
and bb
, so you can run:
python -m pybiosis # Launch the CLI as a simple GUI.
pybiosis # Use a short form
bb # Even shorter
See usage for more.
GUI
Each compiler may implement a GUI (using streamlit
) to provide more tailored access to their functions. This improves ease of use over the GUI CLI, but it requires additional development time to create.
See usage for more.
Custom
You can also create your own compiler just by inheriting from Device
(or a subclass). Check out the existing implementations for ideas.
Installation
- Install
Pybiosis
through pip withpip install pybiosis
. For the latest version, simply use Githuib Desktop (or Git) to clone this repository and usepip install -r requirements.txt -e .
in the directory withsetup.py
. - Create a directory to hold your custom functions and run
python -m pybiosis config --set user_path /Path/To/My/User/Path
. - Add a file called
driver.py
to that directory and have it contain this code:import pybiosis import winsound @pybiosis.Device() # Add additional decorators here. def beep(): winsound.Beep(1000, 200)
You can also decorate functions in any python files within your user path (eg: user_path/games.py), and they will also get registered.
Usage
A CLI, a GUI wrapper for that CLI, and a GUI provide general access to these functions. Otherwise, they are accessible through the attached device/service.
- Run
python -m pybiosis --help
to learn about the CLI, which includesconfig
,compile
,run
,gui
, anduser
commands. All decorated functions are accessible through the CLI.
- Run
python -m pybiosis
to launch the CLI as a GUI (thanks to gooey). This GUI opens by default if no command is specified when invokingpybiosis
.
- It is highly recommended to provide a CLI for your driver.py file:
from pybiosis.compilers.cli import CommandFramework
from rich import print # Optional `pip install rich`
class Commands(CommandFramework):
def add_videos(self, setup, args):
if setup:
pass # Use setup.add_arguments(...) to add parameters.
return
print(f"📺 Running the [green]Youtube[/green] command.")
webbrowser.open("https://www.youtube.com")
This provides the ability for the user to define a CLI for custom commands (in addition to being able to access the decorated functions). You can access a command (eg: videos) with python -m pybiosis user videos
, or access the GUI CLI with python -m pybiosis user
.
- The command
python -m pybiosis gui
will launch a GUI for you to access the device functions.
- Finally, you can access your functions through the respective device (eg: google assistant, streamdeck, waiting for the scheduler).
Limitations
- Most of this functionality is tested on Windows.
- If you get a password prompt from Push2Run, simply recompile until it stops.
- The streamdeck compiler supports v5.0.1.14252 of the StreamDeck software. Compatibility with the latest version needs to be checked (v6.5.2.19936 at time of writing).
- It seems like the CLI fails to forward command line arguments correctly. Possibly only when using the entry points
pybiosis
orbb
.
Future Work
- Stream Deck folders cannot be generated programmatically yet, nor is deleting supported.
- Make the PYBIOSIS_USER_PATH variable a config variable, rather than an environment one.
- Fill out the GUI. Each device subclass should implement their own widget. For now, only the
StreamDeck
does. - Add tools like monitor control, audio controls, usb devices, games, GUI automation, dashboard.
- Add keyboard and mouse decorators, eg:
@Click(region=(...))
,@Press(combo=['SHIFT', 'A'])
. Add new cli commandmonitor
/watch
. Likely to use pip librarieskeyboard
andmouse
. - Provide "packages" (aka plugins/systems/presets) for stream deck, eg:
--install monitors /games/monitors
would generate the button layout in that folder. - Add a
@Voice
decorator andrun
command for listening. See/experimental/voice.py
for a WIP.
Questions?
Email me at nawar@palfore.com, or create a pull request!
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
File details
Details for the file pybiosis-0.1.0.tar.gz
.
File metadata
- Download URL: pybiosis-0.1.0.tar.gz
- Upload date:
- Size: 22.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/5.1.1 CPython/3.12.2
File hashes
Algorithm | Hash digest | |
---|---|---|
SHA256 | cf50553926316c57f6fbea7dfd18ba26b66cf6ac74565026ba8deca5b1bc917b |
|
MD5 | 3b1de840c019f26245c743dc607bc6a1 |
|
BLAKE2b-256 | eb5520c5aef8bfd36ece6e90d1c7824b9bdf4857a25c6481685fff58f5eda67e |