added command line arguments
This commit is contained in:
@@ -36,7 +36,7 @@ ENV GRANIAN_PORT=8000
|
|||||||
ENV GRANIAN_INTERFACE=asgi
|
ENV GRANIAN_INTERFACE=asgi
|
||||||
ENV GRANIAN_LOOP=asyncio
|
ENV GRANIAN_LOOP=asyncio
|
||||||
ENV GRANIAN_LOG_ENABLED=false
|
ENV GRANIAN_LOG_ENABLED=false
|
||||||
|
ENV GRANIAN_LOG_ACCESS_ENABLED=true
|
||||||
ENTRYPOINT ["/var/lib/bugis/.venv/bin/python", "-m", "granian", "bugis.asgi:application"]
|
ENTRYPOINT ["/var/lib/bugis/.venv/bin/python", "-m", "granian", "bugis.asgi:application"]
|
||||||
EXPOSE 8000/tcp
|
EXPOSE 8000/tcp
|
||||||
|
|
||||||
|
@@ -1,5 +1,4 @@
|
|||||||
import argparse
|
from .cli import main
|
||||||
from cli import main
|
|
||||||
import sys
|
import sys
|
||||||
|
|
||||||
main(sys.argv[1:])
|
main(sys.argv[1:])
|
||||||
|
@@ -1,20 +1,11 @@
|
|||||||
import logging
|
import logging
|
||||||
from logging.config import dictConfig as configure_logging
|
from asyncio import get_running_loop
|
||||||
|
from typing import Optional, Awaitable, Callable, Any, Mapping
|
||||||
from yaml import safe_load
|
|
||||||
|
|
||||||
from .configuration import Configuration
|
|
||||||
|
|
||||||
with open(Configuration.instance.logging_configuration_file, 'r') as input_file:
|
|
||||||
conf = safe_load(input_file)
|
|
||||||
configure_logging(conf)
|
|
||||||
|
|
||||||
|
|
||||||
from pwo import Maybe
|
from pwo import Maybe
|
||||||
|
|
||||||
from .server import Server
|
from .server import Server
|
||||||
from asyncio import get_running_loop
|
|
||||||
from .asgi_utils import decode_headers
|
|
||||||
from typing import Optional, Awaitable, Callable, Any, Mapping
|
|
||||||
|
|
||||||
log = logging.getLogger('access')
|
log = logging.getLogger('access')
|
||||||
log.propagate = False
|
log.propagate = False
|
||||||
@@ -33,22 +24,6 @@ async def application(scope, receive, send : Callable[[Mapping[str, Any]], Await
|
|||||||
await _server.stop()
|
await _server.stop()
|
||||||
await send({'type': 'lifespan.shutdown.complete'})
|
await send({'type': 'lifespan.shutdown.complete'})
|
||||||
else:
|
else:
|
||||||
def maybe_log(evt):
|
|
||||||
d = {
|
|
||||||
'response_headers': (Maybe.of_nullable(evt.get('headers'))
|
|
||||||
.map(decode_headers)
|
|
||||||
.or_none()),
|
|
||||||
'status': evt['status']
|
|
||||||
}
|
|
||||||
log.info(None, extra=dict(**{k : v for k, v in d.items() if k is not None}, **scope))
|
|
||||||
def wrapped_send(*args, **kwargs):
|
|
||||||
result = send(*args, **kwargs)
|
|
||||||
(Maybe.of(args)
|
|
||||||
.filter(lambda it: len(it) > 0)
|
|
||||||
.map(lambda it: it[0])
|
|
||||||
.filter(lambda it: it.get('type') == 'http.response.start')
|
|
||||||
.if_present(maybe_log))
|
|
||||||
return result
|
|
||||||
pathsend = (Maybe.of_nullable(scope.get('extensions'))
|
pathsend = (Maybe.of_nullable(scope.get('extensions'))
|
||||||
.map(lambda it: it.get("http.response.pathsend"))
|
.map(lambda it: it.get("http.response.pathsend"))
|
||||||
.is_present)
|
.is_present)
|
||||||
@@ -61,7 +36,7 @@ async def application(scope, receive, send : Callable[[Mapping[str, Any]], Await
|
|||||||
.map(lambda it: it.decode())
|
.map(lambda it: it.decode())
|
||||||
.or_else(None),
|
.or_else(None),
|
||||||
Maybe.of_nullable(scope.get('query_string', None)).map(lambda it: it.decode()).or_else(None),
|
Maybe.of_nullable(scope.get('query_string', None)).map(lambda it: it.decode()).or_else(None),
|
||||||
wrapped_send,
|
send,
|
||||||
pathsend
|
pathsend
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@@ -9,7 +9,7 @@ from granian import Granian
|
|||||||
from pwo import Maybe
|
from pwo import Maybe
|
||||||
|
|
||||||
from .configuration import Configuration
|
from .configuration import Configuration
|
||||||
|
from granian.constants import HTTPModes, Interfaces, ThreadModes, Loops
|
||||||
|
|
||||||
def main(args: Optional[Sequence[str]] = None):
|
def main(args: Optional[Sequence[str]] = None):
|
||||||
parser = argparse.ArgumentParser(description="A simple CLI program to render Markdown files")
|
parser = argparse.ArgumentParser(description="A simple CLI program to render Markdown files")
|
||||||
@@ -27,15 +27,88 @@ def main(args: Optional[Sequence[str]] = None):
|
|||||||
default=default_configuration_file,
|
default=default_configuration_file,
|
||||||
type=Path,
|
type=Path,
|
||||||
)
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'-a',
|
||||||
|
'--address',
|
||||||
|
help='Server bind address',
|
||||||
|
default='127.0.0.1',
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'-p',
|
||||||
|
'--port',
|
||||||
|
help='Server port',
|
||||||
|
default='8000',
|
||||||
|
type=int
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--access-log',
|
||||||
|
help='Enable access log',
|
||||||
|
action='store_true',
|
||||||
|
dest='log_access'
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--logging-configuration',
|
||||||
|
help='Logging configuration file',
|
||||||
|
dest='log_config_file'
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'-w', '--workers',
|
||||||
|
help='Number of worker processes',
|
||||||
|
default='1',
|
||||||
|
dest='workers',
|
||||||
|
type = int
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'-t', '--threads',
|
||||||
|
help='Number of threads per worker',
|
||||||
|
default='1',
|
||||||
|
dest='threads',
|
||||||
|
type=int
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--http',
|
||||||
|
help='HTTP protocol version',
|
||||||
|
dest='http',
|
||||||
|
type=lambda it: HTTPModes(it),
|
||||||
|
choices=[str(mode) for mode in HTTPModes],
|
||||||
|
default = 'auto',
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--threading-mode',
|
||||||
|
help='Threading mode',
|
||||||
|
dest='threading_mode',
|
||||||
|
type=lambda it: ThreadModes(it),
|
||||||
|
choices=[str(mode) for mode in ThreadModes],
|
||||||
|
default=ThreadModes.workers
|
||||||
|
)
|
||||||
|
parser.add_argument(
|
||||||
|
'--loop',
|
||||||
|
help='Loop',
|
||||||
|
dest='loop',
|
||||||
|
type=lambda it: Loops(it),
|
||||||
|
choices=[str(mode) for mode in Loops]
|
||||||
|
)
|
||||||
args = parser.parse_args(args)
|
args = parser.parse_args(args)
|
||||||
|
|
||||||
def parse(configuration: Path):
|
def parse(configuration: Path):
|
||||||
with open(configuration, 'r') as f:
|
with open(configuration, 'r') as f:
|
||||||
return Configuration.from_dict(yaml.safe_load(f))
|
return yaml.safe_load(f)
|
||||||
|
|
||||||
conf = Maybe.of_nullable(args.configuration).map(parse).or_else(Configuration.instance)
|
def assign(it: Configuration):
|
||||||
|
Configuration.instance = it
|
||||||
|
Maybe.of_nullable(args.configuration).map(parse).if_present(assign)
|
||||||
|
conf = Configuration.instance
|
||||||
|
|
||||||
|
|
||||||
|
granian_conf = asdict(conf).setdefault('granian', dict())
|
||||||
|
for k, v in vars(args).items():
|
||||||
|
if v is not None:
|
||||||
|
granian_conf[k] = v
|
||||||
|
if args.log_config_file:
|
||||||
|
with open(args.log_config_file, 'r') as f:
|
||||||
|
granian_conf['log_dictconfig'] = yaml.safe_load(f)
|
||||||
|
granian_conf = Configuration.GranianConfiguration.from_dict(granian_conf)
|
||||||
|
|
||||||
Granian(
|
Granian(
|
||||||
"bugis.asgi:application",
|
"bugis.asgi:application",
|
||||||
**asdict(conf.granian)
|
**asdict(granian_conf)
|
||||||
).serve()
|
).serve()
|
@@ -1,6 +1,8 @@
|
|||||||
from os import environ
|
from os import environ
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from dataclasses import dataclass, field, asdict
|
from dataclasses import dataclass, field, asdict
|
||||||
|
|
||||||
|
import yaml
|
||||||
from granian.constants import Loops, Interfaces, ThreadModes, HTTPModes, StrEnum
|
from granian.constants import Loops, Interfaces, ThreadModes, HTTPModes, StrEnum
|
||||||
from granian.log import LogLevels
|
from granian.log import LogLevels
|
||||||
from granian.http import HTTP1Settings, HTTP2Settings
|
from granian.http import HTTP1Settings, HTTP2Settings
|
||||||
@@ -8,16 +10,27 @@ from typing import Optional, Sequence, Dict, Any
|
|||||||
from pwo import classproperty, Maybe
|
from pwo import classproperty, Maybe
|
||||||
from yaml import add_representer, SafeDumper, SafeLoader
|
from yaml import add_representer, SafeDumper, SafeLoader
|
||||||
|
|
||||||
|
def parse_log_config(conf_file=None) -> Dict[str, Any]:
|
||||||
|
if conf_file is None:
|
||||||
|
conf_file = environ.get("LOGGING_CONFIGURATION_FILE",
|
||||||
|
Path(__file__).parent / 'default-conf' / 'logging.yaml')
|
||||||
|
with open(conf_file, 'r') as file:
|
||||||
|
return yaml.safe_load(file)
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
class Configuration:
|
class Configuration:
|
||||||
logging_configuration_file: str = environ.get("LOGGING_CONFIGURATION_FILE",
|
|
||||||
Path(__file__).parent / 'default-conf' / 'logging.yaml')
|
|
||||||
plant_uml_server_address: str = environ.get('PLANT_UML_SERVER_ADDRESS', None)
|
plant_uml_server_address: str = environ.get('PLANT_UML_SERVER_ADDRESS', None)
|
||||||
|
_instance = None
|
||||||
|
|
||||||
@classproperty
|
@classproperty
|
||||||
def instance(cls) -> 'Configuration':
|
def instance(cls) -> 'Configuration':
|
||||||
return Configuration()
|
if cls._instance is None:
|
||||||
|
cls._instance = Configuration()
|
||||||
|
return cls._instance
|
||||||
|
|
||||||
|
@instance.setter
|
||||||
|
def bar(cls, value):
|
||||||
|
cls._instance = value
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
@dataclass(frozen=True)
|
||||||
class GranianConfiguration:
|
class GranianConfiguration:
|
||||||
@@ -77,7 +90,7 @@ class Configuration:
|
|||||||
http2_settings=Maybe.of_nullable(d.get('http2_settings')).map(lambda it: HTTP2Settings(**it)).or_else(None),
|
http2_settings=Maybe.of_nullable(d.get('http2_settings')).map(lambda it: HTTP2Settings(**it)).or_else(None),
|
||||||
log_enabled=d.get('log_enabled', None),
|
log_enabled=d.get('log_enabled', None),
|
||||||
log_level=Maybe.of_nullable(d.get('log_level')).map(lambda it: LogLevels(it)).or_else(None),
|
log_level=Maybe.of_nullable(d.get('log_level')).map(lambda it: LogLevels(it)).or_else(None),
|
||||||
# log_dictconfig: Optional[Dict[str, Any]] = None,
|
log_dictconfig=parse_log_config(d.get('log_config_file')),
|
||||||
log_access=d.get('log_access', None),
|
log_access=d.get('log_access', None),
|
||||||
log_access_format=d.get('log_access_format', None),
|
log_access_format=d.get('log_access_format', None),
|
||||||
ssl_cert=d.get('ssl_cert', None),
|
ssl_cert=d.get('ssl_cert', None),
|
||||||
|
@@ -1,5 +1,5 @@
|
|||||||
version: 1
|
version: 1
|
||||||
disable_existing_loggers: True
|
disable_existing_loggers: False
|
||||||
handlers:
|
handlers:
|
||||||
console:
|
console:
|
||||||
class : logging.StreamHandler
|
class : logging.StreamHandler
|
||||||
@@ -8,28 +8,23 @@ handlers:
|
|||||||
stream : ext://sys.stderr
|
stream : ext://sys.stderr
|
||||||
access:
|
access:
|
||||||
class : logging.StreamHandler
|
class : logging.StreamHandler
|
||||||
formatter: request
|
formatter: access
|
||||||
level : INFO
|
level : INFO
|
||||||
stream : ext://sys.stderr
|
stream : ext://sys.stdout
|
||||||
formatters:
|
formatters:
|
||||||
brief:
|
|
||||||
format: '%(message)s'
|
|
||||||
default:
|
default:
|
||||||
format: '{asctime} [{levelname}] ({processName:s}/{threadName:s}) - {name} - {message}'
|
format: '{asctime}.{msecs:0<3.0f} [{levelname}] ({processName:s}/{threadName:s}) - {name} - {message}'
|
||||||
style: '{'
|
|
||||||
datefmt: '%Y-%m-%d %H:%M:%S'
|
|
||||||
request:
|
|
||||||
format: '{asctime} {client[0]}:{client[1]} HTTP/{http_version} {method} {path} - {status}'
|
|
||||||
style: '{'
|
style: '{'
|
||||||
datefmt: '%Y-%m-%d %H:%M:%S'
|
datefmt: '%Y-%m-%d %H:%M:%S'
|
||||||
|
access:
|
||||||
|
format: '%(message)s'
|
||||||
loggers:
|
loggers:
|
||||||
root:
|
root:
|
||||||
handlers: [console]
|
handlers: [console]
|
||||||
level: DEBUG
|
_granian:
|
||||||
access:
|
|
||||||
handlers: [access]
|
|
||||||
level: INFO
|
level: INFO
|
||||||
watchdog.observers.inotify_buffer:
|
propagate: False
|
||||||
|
granian.access:
|
||||||
|
handlers: [ access ]
|
||||||
level: INFO
|
level: INFO
|
||||||
MARKDOWN:
|
propagate: False
|
||||||
level: INFO
|
|
||||||
|
@@ -19,6 +19,7 @@ from pwo import Maybe
|
|||||||
|
|
||||||
from .asgi_utils import encode_headers
|
from .asgi_utils import encode_headers
|
||||||
from .async_watchdog import FileWatcher
|
from .async_watchdog import FileWatcher
|
||||||
|
from .configuration import Configuration
|
||||||
from .md2html import compile_html, load_from_cache, STATIC_RESOURCES, MARDOWN_EXTENSIONS
|
from .md2html import compile_html, load_from_cache, STATIC_RESOURCES, MARDOWN_EXTENSIONS
|
||||||
from .plantuml import render_plant_uml
|
from .plantuml import render_plant_uml
|
||||||
|
|
||||||
@@ -179,7 +180,7 @@ class Server:
|
|||||||
'type': 'http.response.body',
|
'type': 'http.response.body',
|
||||||
'body': body
|
'body': body
|
||||||
})
|
})
|
||||||
elif is_plant_uml(path):
|
elif Configuration.instance.plant_uml_server_address and is_plant_uml(path):
|
||||||
logger.debug("Starting PlantUML rendering for file '%s'", path)
|
logger.debug("Starting PlantUML rendering for file '%s'", path)
|
||||||
logger.debug("Completed PlantUML rendering for file '%s'", path)
|
logger.debug("Completed PlantUML rendering for file '%s'", path)
|
||||||
await send({
|
await send({
|
||||||
|
Reference in New Issue
Block a user