snapshot of https://github.com/ActivityWatch/aw-server.git (master, 2026-09-16; blobs > 131072B omitted)
Some checks failed
Build / Test on ubuntu-latest (push) Successful in 3m6s
Lint / lint (push) Successful in 22s
Build / typecheck (ubuntu-latest, 3.9) (push) Successful in 1m49s
Lint / format (push) Successful in 42s
Build / Test on macOS-latest (push) Has been cancelled
Build / Test on windows-latest (push) Has been cancelled
CodeQL / Analyze (python) (push) Failing after 1m31s
Some checks failed
Build / Test on ubuntu-latest (push) Successful in 3m6s
Lint / lint (push) Successful in 22s
Build / typecheck (ubuntu-latest, 3.9) (push) Successful in 1m49s
Lint / format (push) Successful in 42s
Build / Test on macOS-latest (push) Has been cancelled
Build / Test on windows-latest (push) Has been cancelled
CodeQL / Analyze (python) (push) Failing after 1m31s
This commit is contained in:
176
aw_server/main.py
Normal file
176
aw_server/main.py
Normal file
@@ -0,0 +1,176 @@
|
||||
import logging
|
||||
import sys
|
||||
|
||||
from aw_core.log import setup_logging
|
||||
from aw_datastore import get_storage_methods
|
||||
|
||||
from . import __version__
|
||||
from .config import config_section, default_port, load_config
|
||||
from .profile import (
|
||||
DEFAULT_PROFILE,
|
||||
export_profile,
|
||||
is_testing,
|
||||
resolve_profile,
|
||||
)
|
||||
from .server import _start
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def main():
|
||||
"""Called from the executable and __main__.py"""
|
||||
|
||||
settings, storage_method = parse_settings()
|
||||
|
||||
# FIXME: The LogResource API endpoint relies on the log being in JSON format
|
||||
# at the path specified by aw_core.log.get_log_file_path(). We probably want
|
||||
# to write the LogResource API so that it does not depend on any physical file
|
||||
# but instead add a logging handler that it can use privately.
|
||||
# That is why log_file_json=True currently.
|
||||
# UPDATE: The LogResource API is no longer available so log_file_json is now False.
|
||||
setup_logging(
|
||||
"aw-server",
|
||||
testing=settings.testing,
|
||||
verbose=settings.verbose,
|
||||
log_stderr=True,
|
||||
log_file=True,
|
||||
)
|
||||
|
||||
logger.info(f"Using storage method: {settings.storage}")
|
||||
|
||||
if settings.testing:
|
||||
logger.info("Will run in testing mode")
|
||||
|
||||
if settings.profile != DEFAULT_PROFILE:
|
||||
logger.info(f"Running with profile: {settings.profile}")
|
||||
|
||||
if settings.custom_static:
|
||||
logger.info(f"Using custom_static: {settings.custom_static}")
|
||||
|
||||
logger.info("Starting up...")
|
||||
_start(
|
||||
host=settings.host,
|
||||
port=settings.port,
|
||||
testing=settings.testing,
|
||||
storage_method=storage_method,
|
||||
cors_origins=settings.cors_origins,
|
||||
custom_static=settings.custom_static,
|
||||
)
|
||||
|
||||
|
||||
def parse_settings():
|
||||
import argparse
|
||||
|
||||
""" CLI Arguments """
|
||||
parser = argparse.ArgumentParser(description="Starts an ActivityWatch server")
|
||||
parser.add_argument(
|
||||
"--testing",
|
||||
action="store_true",
|
||||
help="Run aw-server in testing mode using different ports and database (alias for --profile testing)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--profile",
|
||||
dest="profile",
|
||||
default=None,
|
||||
help="Named instance profile (data, config, port and settings are isolated). --testing is an alias for --profile testing.",
|
||||
)
|
||||
parser.add_argument("--verbose", action="store_true", help="Be chatty.")
|
||||
parser.add_argument(
|
||||
"--version",
|
||||
action="store_true",
|
||||
help="Print version and quit",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--log-json", action="store_true", help="Output the logs in JSON format"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--host", dest="host", help="Which host address to bind the server to"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--port", dest="port", type=int, help="Which port to run the server on"
|
||||
)
|
||||
parser.add_argument(
|
||||
"--storage",
|
||||
dest="storage",
|
||||
help="The method to use for storing data. Some methods (such as MongoDB) require specific Python packages to be available (in the MongoDB case: pymongo)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--cors-origins",
|
||||
dest="cors_origins",
|
||||
help="CORS origins to allow (as a comma separated list)",
|
||||
)
|
||||
parser.add_argument(
|
||||
"--custom-static",
|
||||
dest="custom_static",
|
||||
help="The custom static directories. Format: watcher_name=path,watcher_name2=path2,...",
|
||||
)
|
||||
args = parser.parse_args()
|
||||
if args.version:
|
||||
print(__version__)
|
||||
sys.exit(0)
|
||||
|
||||
try:
|
||||
profile = resolve_profile(args.profile, args.testing)
|
||||
except ValueError as e:
|
||||
parser.error(str(e))
|
||||
# Export before loading config so aw-core dirs isolate this profile.
|
||||
export_profile(profile)
|
||||
testing = is_testing(profile)
|
||||
|
||||
""" Parse config file """
|
||||
config = load_config()
|
||||
section = config_section(profile)
|
||||
if section not in config:
|
||||
if profile not in (DEFAULT_PROFILE,):
|
||||
logger.warning(
|
||||
"Profile %s has no [%s] section, falling back to [server] "
|
||||
"(port %s may collide with the default instance)",
|
||||
profile,
|
||||
section,
|
||||
default_port(profile),
|
||||
)
|
||||
section = "server"
|
||||
settings = argparse.Namespace()
|
||||
settings.host = config[section]["host"]
|
||||
settings.port = int(config[section]["port"])
|
||||
settings.storage = config[section]["storage"]
|
||||
settings.cors_origins = config[section]["cors_origins"]
|
||||
settings.custom_static = dict(config[section]["custom_static"])
|
||||
settings.profile = profile
|
||||
settings.testing = testing
|
||||
|
||||
""" If a argument is not none, override the config value """
|
||||
for key, value in vars(args).items():
|
||||
if key in ("testing", "profile"):
|
||||
# Resolved above; --profile testing must keep testing=True
|
||||
# even when the raw --testing flag was absent.
|
||||
continue
|
||||
if value is not None:
|
||||
if key == "custom_static":
|
||||
settings.custom_static = parse_str_to_dict(value)
|
||||
else:
|
||||
vars(settings)[key] = value
|
||||
|
||||
settings.cors_origins = [o for o in settings.cors_origins.split(",") if o]
|
||||
|
||||
storage_methods = get_storage_methods()
|
||||
storage_method = storage_methods[settings.storage]
|
||||
|
||||
return settings, storage_method
|
||||
|
||||
|
||||
def parse_str_to_dict(str_value):
|
||||
"""Parses a dict from a string in format: key=value,key2=value2,..."""
|
||||
output = dict()
|
||||
key_value_pairs = str_value.split(",")
|
||||
|
||||
for pair in key_value_pairs:
|
||||
pair_split = pair.split("=")
|
||||
|
||||
if len(pair_split) != 2:
|
||||
raise ValueError(f"Cannot parse key value pair: {pair}")
|
||||
|
||||
key, value = pair_split
|
||||
output[key] = value
|
||||
|
||||
return output
|
||||
Reference in New Issue
Block a user