Skip to content

🌠 Allow a single instance of a Python application to run at once | platform agnostic

License

Notifications You must be signed in to change notification settings

emboiko/Socket_Singleton

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

9 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Socket_Singleton.py

Socket-based, single-instance Python applications with a clean interface

Without lockfiles, mutexes, dependencies, or tomfoolery

Install:

pip install Socket_Singleton -U

Import:

From Socket_Singleton import Socket_Singleton

Constructor:

Socket_Singleton(address="127.0.0.1", port=1337, timeout=0, client=True, strict=True, max_clients=0)

Usage:

Socket_Singleton()

or, keep a reference:

app = Socket_Singleton()

then attach a callback, and capture arguments from subsequent calls to your application:

def my_callback(arg):
    print(arg)

app.trace(my_callback)

See also:

Common TCP/UDP Port Numbers

It is recommended to specify a port in the constructor*

Examples:

Say we have an application, app.py, that we want to restrict to a single instance.

#app.py

from Socket_Singleton import Socket_Singleton
Socket_Singleton()
input() #Blocking call to simulate your_business_logic() 

The first time app.py is launched:

>> C:\current\working\directory λ python app.py
>> 

app.py executes normally. (Here, app.py blocks until we satisfy input(). Replace this with your own logic. The examples and basic recipes on this page contain these calls simply for demonstration purposes.)

Now, in another shell, if we try:

>> C:\current\working\directory λ python app.py
>> C:\current\working\directory λ

The interpreter exits immediately and we end up back at the prompt.


We can also get access to arguments passed from subsequent attempts to run python app.py with the arguments property, although is not intended to be accessed directly- it's most likely more convenient to use the trace() method. This allows you to register a callback, which gets called when arguments is appended (as other instances try to run).

Socket_Singleton.trace(observer, *args, **kwargs)

#app.py

from Socket_Singleton import Socket_Singleton

def callback(argument, *args, **kwargs):
    print(argument)
    #do_a_thing(argument)

def main():
    app = Socket_Singleton()
    app.trace(callback, *args, **kwargs)
    input() #Blocking call to simulate your_business_logic() 

if __name__ == "__main__":
    main()

At the terminal:

>> C:\current\working\directory λ python app.py
>> 

In another shell, subsequent attempts to python app.py now look like this:

>> C:\current\working\directory λ python app.py foo bar baz
>> C:\current\working\directory λ

Meanwhile, our output for the original python app.py shell looks like this:

>> C:\current\working\directory λ python app.py
>> foo
>> bar
>> baz

We can also detach a given observer / callback via untrace() with a similar interface.

Socket_Singleton.untrace(observer)


If you'd prefer to disconnect from the port prematurely, thus releasing the "lock", there's a close() method:

#app.py

from Socket_Singleton import Socket_Singleton

def main():
    app = Socket_Singleton()
    app.close()
    print("Running!")
    input()

if __name__ == "__main__":
    main()

At the terminal:

>> C:\current\working\directory λ python app.py
>> Running!
>> 

And in a new shell:

>> C:\current\working\directory λ python app.py
>> Running!
>> 

Context manager protocol is implemented as well:

with Socket_Singleton():
    input() #Blocking call to simulate your_business_logic()

Socket_Singleton.__enter__() returns self so we can can have access to the object if needed:

with Socket_Singleton() as ss:
    ss.trace(callback)
    input() #Blocking call to simulate your_business_logic()

  • timeout

A duration in seconds, specifying how long to hold the socket. Defaults to 0 (no timeout, keep-alive). Countdown starts at the end of initialization, immediately after the socket is bound successfully.


  • clients

An integer property describing how many client processes have connected since instantiation.


  • max_clients

A positive integer describing how many client processes to capture before internally calling close() and releasing the port. Defaults to 0 (no maximum)


  • strict=False

We can raise and capture a custom exception, MultipleSingletonsError, rather than entirely destroying the process which fails to become the singleton.

from Socket_Singleton import Socket_Singleton, MultipleSingletonsError

def callback(arg):
    print(f"callback: {arg}")

def main():
    try:
        app = Socket_Singleton(strict=False)
    except MultipleSingletonsError as err:
        print("We are not the singleton.")
        print(err)
    else:
        print("We are the singleton!")
        app.trace(callback)
        [print(arg) for arg in app.arguments]
        # print(app)
        # print(repr(app))
        # help(app)

    input()

if __name__ == "__main__":
    main()

About

🌠 Allow a single instance of a Python application to run at once | platform agnostic

Topics

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages