Skip to main content

Allow a single instance of a Python application to run at once

Project description

Socket_Singleton.py

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

Without lockfiles, mutexes, dependencies, or tomfoolery

Install:

pip install Socket_Singleton

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()

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

Socket_Singleton-1.0.0.tar.gz (5.5 kB view details)

Uploaded Source

Built Distribution

Socket_Singleton-1.0.0-py3-none-any.whl (6.7 kB view details)

Uploaded Python 3

File details

Details for the file Socket_Singleton-1.0.0.tar.gz.

File metadata

  • Download URL: Socket_Singleton-1.0.0.tar.gz
  • Upload date:
  • Size: 5.5 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/3.2.0 pkginfo/1.6.1 requests/2.25.0 setuptools/49.2.1 requests-toolbelt/0.9.1 tqdm/4.51.0 CPython/3.9.0

File hashes

Hashes for Socket_Singleton-1.0.0.tar.gz
Algorithm Hash digest
SHA256 1b0ebda25fd051304ab773c7e93e7a1af4dd2d54dde366fce0f4567e47d1aab8
MD5 134181c1a3e2896b9d80beedcb4d59db
BLAKE2b-256 7b6fc8e20638cd312438618b9832c50e5b251ec1b599ea3e6568545b5e42d2b5

See more details on using hashes here.

File details

Details for the file Socket_Singleton-1.0.0-py3-none-any.whl.

File metadata

  • Download URL: Socket_Singleton-1.0.0-py3-none-any.whl
  • Upload date:
  • Size: 6.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/3.2.0 pkginfo/1.6.1 requests/2.25.0 setuptools/49.2.1 requests-toolbelt/0.9.1 tqdm/4.51.0 CPython/3.9.0

File hashes

Hashes for Socket_Singleton-1.0.0-py3-none-any.whl
Algorithm Hash digest
SHA256 d0eff5a7d9b579ecafceb7bd01cb2c64971817c62ac87562914750f60f64f3ff
MD5 427b89a6a05699f4c363540dc292112e
BLAKE2b-256 619d6416fe35fae816937d6ed4b0fffc7c7d29e34ce8349db9f90c50834f3b5b

See more details on using hashes here.

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page