generated from jCloud/repository-template
Add basic API structure
This commit is contained in:
@@ -0,0 +1,7 @@
|
||||
# Changelog
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### Added
|
||||
|
||||
- Add basic API structure
|
||||
@@ -0,0 +1,155 @@
|
||||
# Copyright 2026 jCloud Services GbR
|
||||
|
||||
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||
# you may not use this file except in compliance with the License.
|
||||
# You may obtain a copy of the License at
|
||||
|
||||
# http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
# Unless required by applicable law or agreed to in writing, software
|
||||
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
# See the License for the specific language governing permissions and
|
||||
# limitations under the License.
|
||||
|
||||
from fastapi import FastAPI
|
||||
import os
|
||||
import uvicorn
|
||||
import argparse
|
||||
import ipaddress
|
||||
import jcloud_config_parser
|
||||
import logging
|
||||
import pathlib
|
||||
|
||||
def existing_file(path: str) -> pathlib.Path:
|
||||
'''
|
||||
Checks whether a path is a file and returns it if it exists.
|
||||
|
||||
:param path: The path
|
||||
:type path: str
|
||||
|
||||
:raises argparse.ArgumentTypeError: If the path is a directory or does not exist.
|
||||
|
||||
:return: The path
|
||||
:rtype: pathlib.Path
|
||||
'''
|
||||
|
||||
if os.path.isdir(path):
|
||||
raise argparse.ArgumentTypeError(f'\'{path}\': Is a directory')
|
||||
if not os.path.isfile(path):
|
||||
raise argparse.ArgumentTypeError(f'\'{path}\': No such file or directory')
|
||||
return pathlib.Path(path)
|
||||
|
||||
# parse arguments
|
||||
|
||||
argument_parser = argparse.ArgumentParser(description='The jCloud deployment server.')
|
||||
argument_parser.add_argument('-c', '--config', type=existing_file, default=pathlib.Path(os.path.dirname(__file__) + '/../../config/'), help='The directory containing the configuration files.')
|
||||
argument_parser.add_argument('-H', '--host', type=ipaddress.ip_address, default=None, help='The host to listen')
|
||||
argument_parser.add_argument('-p', '--port', type=int, default=None, help='The port to listen')
|
||||
args = argument_parser.parse_args()
|
||||
|
||||
# load default configuration
|
||||
with open(os.path.dirname(__file__) + '/../../default_configuration.conf') as f:
|
||||
default_configuration = jcloud_config_parser.ini.INIConfiguration.from_string(f.read())
|
||||
f.close()
|
||||
|
||||
# load configuration
|
||||
configuration = jcloud_config_parser.ini.INIConfiguration.from_string(
|
||||
(args.config / 'server.conf').read_text(),
|
||||
default=default_configuration,
|
||||
ignore_errors=True
|
||||
)
|
||||
|
||||
logfile = configuration.logging.logfile
|
||||
if os.path.isdir(logfile):
|
||||
logfile = None
|
||||
if not logfile:
|
||||
logfile = None
|
||||
|
||||
LOGLEVEL = logging._nameToLevel.get(configuration.logging.loglevel.upper(), logging._nameToLevel.get(default_configuration.logging.loglevel.upper(), logging.INFO))
|
||||
|
||||
# set up logging
|
||||
LOGGING_FORMAT = '%(asctime)s [%(levelname)s] %(name)s: %(message)s'
|
||||
logging.basicConfig(level=LOGLEVEL, format=LOGGING_FORMAT)
|
||||
logger = logging.getLogger('api')
|
||||
logger.propagate = False
|
||||
logger.handlers = list()
|
||||
|
||||
formatter = logging.Formatter(LOGGING_FORMAT)
|
||||
if logfile is not None:
|
||||
logger_file_handler = logging.FileHandler(logfile)
|
||||
logger_file_handler.setFormatter(formatter)
|
||||
logger.addHandler(logger_file_handler)
|
||||
|
||||
def validate_host(host: str) -> bool:
|
||||
'''
|
||||
Checks whether the address is a valid address (``ip:port``), e. g. ``0.0.0.0:3000``.
|
||||
|
||||
:param addr: The address
|
||||
:type addr: str
|
||||
|
||||
:return: ``True`` if the address is valid, otherwise ``False``.
|
||||
:rtype: bool
|
||||
'''
|
||||
|
||||
try:
|
||||
ipaddress.ip_address(host)
|
||||
|
||||
return True
|
||||
except Exception:
|
||||
return False
|
||||
|
||||
# host
|
||||
host = args.host or configuration.server.host
|
||||
if not validate_host(host):
|
||||
logger.error(f'Error in configuration: [server]host: \'{host}\' is not a valid host, using default value.')
|
||||
host = default_configuration.server.host
|
||||
|
||||
# port
|
||||
port = args.port or configuration.server.port
|
||||
if not port.isdigit():
|
||||
logger.error(f'Error in configuration: [server]port: \'{port}\' is not an integer, using default value.')
|
||||
port = default_configuration.server.port
|
||||
else:
|
||||
if not (0 <= int(port) <= 65535):
|
||||
logger.error(f'Error in configuration: [server]port: {port} is not between 0 and 65535, using default value.')
|
||||
port = default_configuration.server.port
|
||||
port = int(port)
|
||||
|
||||
# Initialize FastAPI
|
||||
app = FastAPI(
|
||||
docs_url=configuration.docs.swagger,
|
||||
redoc_url=configuration.docs.redoc,
|
||||
openapi_url=configuration.docs.openapi
|
||||
)
|
||||
|
||||
if __name__ == '__main__':
|
||||
uvicorn.run(
|
||||
app,
|
||||
host = host,
|
||||
port = port,
|
||||
server_header = False,
|
||||
log_config = {
|
||||
'version': 1,
|
||||
'disable_existing_loggers': True,
|
||||
'handlers': {
|
||||
'uvicorn_handler': {
|
||||
'class': 'logging.FileHandler',
|
||||
'filename': logfile,
|
||||
'formatter': 'uvicorn_formatter',
|
||||
},
|
||||
},
|
||||
'formatters': {
|
||||
'uvicorn_formatter': {
|
||||
'format': LOGGING_FORMAT
|
||||
},
|
||||
},
|
||||
'loggers': {
|
||||
'uvicorn': {
|
||||
'handlers': ['uvicorn_handler'],
|
||||
'level': LOGLEVEL,
|
||||
'propagate': False
|
||||
},
|
||||
},
|
||||
}
|
||||
)
|
||||
Reference in New Issue
Block a user