Skip to content

Commit

Permalink
feat(cli): Add command to serve a single file
Browse files Browse the repository at this point in the history
Also refactor classes a bit, such that everything is now instance-
as opposed to class-level. Seemed more streamlined.

Closes #14.
  • Loading branch information
alexpovel committed Jul 24, 2022
1 parent bbe46ba commit 6318e96
Show file tree
Hide file tree
Showing 2 changed files with 67 additions and 22 deletions.
21 changes: 18 additions & 3 deletions ancv/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
locally.
"""

import json
import logging
import os
from pathlib import Path
Expand All @@ -17,7 +16,7 @@

from ancv.utils.exceptions import ResumeConfigError
from ancv.visualization.templates import Template
from ancv.web.server import API
from ancv.web.server import APIHandler, FileHandler, ServerContext

app = typer.Typer(no_args_is_help=True, help=__doc__)
server_app = typer.Typer(no_args_is_help=True, help="Interacts with the web server.")
Expand All @@ -35,7 +34,23 @@ def api(
) -> None:
"""Starts the web server and serves the API."""

API.run(host=host, port=port, path=path)
context = ServerContext(host=host, port=port, path=path)
APIHandler().run(context)


@server_app.command()
def file(
file: Path = typer.Argument(Path("resume.json")),
host: str = typer.Option("0.0.0.0", help="Hostname to bind to."),
port: int = typer.Option(8080, help="Port to bind to."),
path: Optional[str] = typer.Option(
None, help="File system path for an HTTP server UNIX domain socket."
),
) -> None:
"""Starts the web server and serves a single, rendered resume file."""

context = ServerContext(host=host, port=port, path=path)
FileHandler(file).run(context)


@app.command()
Expand Down
68 changes: 49 additions & 19 deletions ancv/web/server.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import os
from abc import ABC, abstractmethod
from dataclasses import dataclass
from pathlib import Path
from typing import AsyncGenerator, Optional

from aiohttp import ClientSession, web
Expand All @@ -16,25 +18,36 @@
LOGGER = get_logger()


class Server(ABC):
@classmethod
@abstractmethod
def run(cls, host: Optional[str], port: Optional[int], path: Optional[str]) -> None:
pass
@dataclass
class ServerContext:
host: Optional[str]
port: Optional[int]
path: Optional[str]


class Runnable(ABC):
@abstractmethod
def run(self, context: ServerContext) -> None:
...

class API(Server):
ROUTES = web.RouteTableDef()

@classmethod
def run(cls, host: Optional[str], port: Optional[int], path: Optional[str]) -> None:
class APIHandler(Runnable):
def run(self, context: ServerContext) -> None:
LOGGER.debug("Instantiating web application.")
app = web.Application()

LOGGER.debug("Adding routes.")
app.add_routes(cls.ROUTES)
app.cleanup_ctx.append(cls.app_context)
app.add_routes(
[
web.get("/", self.root),
web.get("/{username}", self.username),
]
)

app.cleanup_ctx.append(self.app_context)

LOGGER.info("Loaded, starting server...")
web.run_app(app, host=host, port=port, path=path)
web.run_app(app, host=context.host, port=context.port, path=context.path)

@staticmethod
async def app_context(app: web.Application) -> AsyncGenerator[None, None]:
Expand Down Expand Up @@ -81,9 +94,7 @@ async def app_context(app: web.Application) -> AsyncGenerator[None, None]:

log.info("App context teardown done.")

@ROUTES.get("/")
@staticmethod
async def root(request: web.Request) -> web.Response:
async def root(self, request: web.Request) -> web.Response:
user_agent = request.headers.get("User-Agent", "")

HOMEPAGE = os.environ.get("HOMEPAGE", METADATA.home_page or "")
Expand All @@ -101,10 +112,8 @@ async def root(request: web.Request) -> web.Response:

raise web.HTTPFound(browser_page) # Redirect

@ROUTES.get("/{username}")
@staticmethod
async def username(request: web.Request) -> web.Response:
log = LOGGER
async def username(self, request: web.Request) -> web.Response:
log = LOGGER.bind(request=request)
log.info(request.message.headers)

user = request.match_info["username"]
Expand All @@ -128,4 +137,25 @@ async def username(request: web.Request) -> web.Response:
except ResumeConfigError as e:
log.warning(str(e))
return web.Response(text=str(e))
log.debug("Serving rendered template.")
return web.Response(text=template.render())


class FileHandler(Runnable):
def __init__(self, file: Path) -> None:
self.template = Template.from_file(file)
self.rendered = self.template.render()

def run(self, context: ServerContext) -> None:
LOGGER.debug("Instantiating web application.")
app = web.Application()

LOGGER.debug("Adding routes.")
app.add_routes([web.get("/", self.root)])

LOGGER.info("Loaded, starting server...")
web.run_app(app, host=context.host, port=context.port, path=context.path)

async def root(self, request: web.Request) -> web.Response:
LOGGER.debug("Serving rendered template.", request=request)
return web.Response(text=self.rendered)

0 comments on commit 6318e96

Please sign in to comment.