Looper is a daemonizer library, it can help you with lifecycle of your daemon.
Project description
GenesisCoreLibs Looper Documentation
Overview
GCL Looper is a Python library designed to create daemon-like services that can run indefinitely, performing tasks at regular intervals or on demand.
Usage Examples
Basic Service
- Iterate infinitely
- There should be at least 5 seconds between start of previous and next iteration (
iter_min_period) - pause for 1 second between iterations (
iter_pause)
from gcl_looper.services import basic
class MyService(basic.BasicService):
def __init__(self, iter_min_period=5, iter_pause=1):
super(MyService, self).__init__(iter_min_period, iter_pause)
def _iteration(self):
print("Iteration", self._iteration_number)
service = MyService()
service.start()
Finite Service without any pauses in-between
from gcl_looper.services import basic
class MyFiniteService(basic.BasicService):
def __init__(self, iter_min_period=0, iter_pause=0):
super(MyFiniteService, self).__init__(iter_min_period, iter_pause)
self.countdown = 3
def _iteration(self):
if self.countdown > 1:
self.countdown -= 1
else:
self.stop()
service = MyFiniteService()
service.start()
API service with database (restalchemy)
from gcl_looper.services import bjoern_service
from gcl_looper.services import hub
from oslo_config import cfg
from restalchemy.storage.sql import engines
from restalchemy.common import config_opts as db_config_opts
from MY_PACKAGE.user_api import app
api_cli_opts = [
cfg.StrOpt(
"bind-host", default="127.0.0.1", help="The host IP to bind to"
),
cfg.IntOpt("bind-port", default=8080, help="The port to bind to"),
cfg.IntOpt(
"workers", default=1, help="How many http servers should be started"
),
]
DOMAIN = "user_api"
CONF = cfg.CONF
CONF.register_cli_opts(api_cli_opts, DOMAIN)
db_config_opts.register_posgresql_db_opts(conf=CONF)
def main():
serv_hub = hub.ProcessHubService()
for _ in range(CONF[DOMAIN].workers):
service = bjoern_service.BjoernService(
wsgi_app=app.build_wsgi_application(),
host=CONF[DOMAIN].bind_host,
port=CONF[DOMAIN].bind_port,
bjoern_kwargs=dict(reuse_port=True),
)
service.add_setup(
lambda: engines.engine_factory.configure_postgresql_factory(
conf=CONF
)
)
serv_hub.add_service(service)
serv_hub.start()
if __name__ == "__main__":
main()
Public interface:
start(): Starts the service.stop(): Stop the service._loop_iteration(): Performs one iteration of the service loop.
Implement these methods to get usable service:
_iteration(): This method must be implemented by subclasses to perform the actual work at each iteration.
Process Hub service
Process Hub allows running multiple services in separate processes. It's useful when you want to run multiple instances of a service (e.g., multiple API workers) or different services that should be isolated.
Security Feature: Privilege Downgrade
When using ProcessHubService, you can set __mp_downgrade_user__ on a child service to automatically downgrade process privileges after the fork. This is a security best practice to minimize attack surface - start as root (if needed to bind to privileged ports), then downgrade to an unprivileged user.
__mp_downgrade_user__: Class attribute set to a username (e.g.,"nobody"). When set, the child process will downgrade to this user after forking. Default isNone(no downgrade).
from gcl_looper.services import hub
from gcl_looper.services import bjoern_service
serv_hub = hub.ProcessHubService()
# BjoernService has __mp_downgrade_user__ = "nobody" by default
for _ in range(4): # 4 workers
service = bjoern_service.BjoernService(
wsgi_app=my_app,
host="0.0.0.0",
port=80, # Privileged port, needs root to bind
bjoern_kwargs=dict(reuse_port=True),
)
serv_hub.add_service(service)
serv_hub.start()
# Each worker starts as root to bind port 80, then downgrades to 'nobody'
Note: This feature only works on Linux and requires the process to start as root. The target user must exist on the system.
Manual Privilege Downgrade
You can also manually downgrade privileges using the utility function:
from gcl_looper import utils
# Downgrade to 'nobody' user
utils.downgrade_user_group_privileges("nobody")
Launchpad Service
Launchpad service is a service that can run multiple services and execute them sequentially. It's convenient when you have multiple services that need to be run in a specific order or the services aren't heavy and you don't want to use multiprocessing. Also it simplifies the configuration of the services.
Basic usage:
from gcl_looper.services import launchpad
services = [
MyService(),
MyFiniteService(),
]
service = launchpad.LaunchpadService(services)
service.start()
The most important part in the launchpad service is its configuration. In the configuration you specify how to run inner services, how to configure them and how to initialize them.
Configuration options:
services: List of services to run. Each service can be specified as a string in the formatmodule.path:ServiceName::countwherecountis optional and defaults to 1.common_registrator_opts: Common options for all services. These options are passed to the service constructor.common_initializer: Common initializer for all services. This initializer is called after the service is created and before it is started.iter_min_period: Minimum period between iterations of the service loop.iter_pause: Pause between iterations of the service loop.
Example:
[DEFAULT]
verbose = True
debug = True
[launchpad]
services =
my_package.service_foo:FooService,
my_package.service_bar:BarService,
my_package.service_baz:BazService
common_registrator_opts = my_package.service_common:common_opts
common_initializer = my_package.service_common:common_init
[my_package.service_foo:FooService]
name = foo
[my_package.service_bar:BarService]
name = bar
project_id = 123
[my_package.service_baz:BazService]
param1 = value1
param2 = value2
Example with multiple instances of the same service:
[DEFAULT]
verbose = True
debug = True
[launchpad]
services =
my_package.service_foo:FooService,
my_package.service_bar:BarService::2
[my_package.service_foo:FooService]
name = foo
[my_package.service_bar:BarService::0]
name = bar0
project_id = 123
[my_package.service_bar:BarService::1]
name = bar1
project_id = 456
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
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file gcl_looper-1.2.0.tar.gz.
File metadata
- Download URL: gcl_looper-1.2.0.tar.gz
- Upload date:
- Size: 23.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
2e56b3ec84e0a1824bc5e627f822cca28e3bee257ef618a52791801810d5ae99
|
|
| MD5 |
67cb8abbcbab196a7cfef8a3a7befde5
|
|
| BLAKE2b-256 |
d51559571bac751fb862e4923613374b67d87df40bd89e619b2f85106a46314e
|
Provenance
The following attestation bundles were made for gcl_looper-1.2.0.tar.gz:
Publisher:
publish-to-pypi.yml on infraguys/gcl_looper
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
gcl_looper-1.2.0.tar.gz -
Subject digest:
2e56b3ec84e0a1824bc5e627f822cca28e3bee257ef618a52791801810d5ae99 - Sigstore transparency entry: 962472526
- Sigstore integration time:
-
Permalink:
infraguys/gcl_looper@ee1af235cdb4d43ddc7abf8cec13d3847d3589f7 -
Branch / Tag:
refs/tags/1.2.0 - Owner: https://github.com/infraguys
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-to-pypi.yml@ee1af235cdb4d43ddc7abf8cec13d3847d3589f7 -
Trigger Event:
push
-
Statement type:
File details
Details for the file gcl_looper-1.2.0-py3-none-any.whl.
File metadata
- Download URL: gcl_looper-1.2.0-py3-none-any.whl
- Upload date:
- Size: 30.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
07252558395226716bbfa27368f762e1cacfe1878b5d42590a3cce1768c764ad
|
|
| MD5 |
8c997532d40ef1936927c738d48d8d3c
|
|
| BLAKE2b-256 |
1b4cea54047f59993908db686903747b3158d053048862e33284711a691ff0bf
|
Provenance
The following attestation bundles were made for gcl_looper-1.2.0-py3-none-any.whl:
Publisher:
publish-to-pypi.yml on infraguys/gcl_looper
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
gcl_looper-1.2.0-py3-none-any.whl -
Subject digest:
07252558395226716bbfa27368f762e1cacfe1878b5d42590a3cce1768c764ad - Sigstore transparency entry: 962472529
- Sigstore integration time:
-
Permalink:
infraguys/gcl_looper@ee1af235cdb4d43ddc7abf8cec13d3847d3589f7 -
Branch / Tag:
refs/tags/1.2.0 - Owner: https://github.com/infraguys
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish-to-pypi.yml@ee1af235cdb4d43ddc7abf8cec13d3847d3589f7 -
Trigger Event:
push
-
Statement type: