Commit b38b44a7 authored by Massimo Costantini's avatar Massimo Costantini
Browse files

Updated comments and style

parent 00087fb2
Loading
Loading
Loading
Loading
+1 −1
Original line number Diff line number Diff line
@@ -71,7 +71,6 @@ async function IVOA_tablesEndpoint(req, IO) {
 * @returns {Promise<void>} - Starts the server and binds all routes
 */
async function IVOA_main(IO) {
  await putStr("IVOA server started")(IO);
  const port = await readRESTPort(IO);
  startRESTServer(port, [
    { path: "/tap", endpoint: IVOA_tapEndpoint },
@@ -79,6 +78,7 @@ async function IVOA_main(IO) {
    { path: "/tap/capabilities", endpoint: IVOA_capabilitiesEndpoint },
    { path: "/tap/tables", endpoint: IVOA_tablesEndpoint },
  ])(IO);
  await putStr("IVOA server started")(IO);
}

/**
+64 −47
Original line number Diff line number Diff line
# IVOA
# IVOA implementation

from Lib.IO import InternalError, IO, performIO
from Lib.IO import putStr, readFile
import asyncio

from Lib.IO import InternalError, IO, perform_io
from Lib.IO import put_str, read_file

from Lib.PostgreSQL import connPostgreSQL, fetchRows, quitPostgreSQL
from Lib.PostgreSQL import conn_postgresql, fetch_rows, quit_postgresql

from Lib.REST import startRESTServer, readRESTPort
from Lib.REST import start_rest_server, read_rest_port

from Lib.VOTable import buildVOTablesFromRows
from Lib.VOTable import build_votables_from_rows


async def IVOA_tapEndpoint(req, IO):
async def ivoa_tap_endpoint(req, io):
    """
    Serves the static /tap HTML landing page (functional signature: Request -> IO -> IO String).

    @param req: Incoming HTTP request
    @param IO: IO token
    @returns: Coroutine[str] - HTML content
    Args:
        req: Incoming HTTP request.
        io: IO token.

    Returns:
        Awaitable[str]: HTML content.
    """
    return await readFile("Stat/tap.html")(IO)
    return await read_file("Stat/tap.html")(io)


async def IVOA_availabilityEndpoint(req, IO):
async def ivoa_availability_endpoint(req, io):
    """
    Serves the static /availability XML response (functional signature: Request -> IO -> IO String).

    @param req: Incoming HTTP request
    @param IO: IO token
    @returns: Coroutine[str] - XML content
    Args:
        req: Incoming HTTP request.
        io: IO token.

    Returns:
        Awaitable[str]: XML content.
    """
    return await readFile("Stat/availability.xml")(IO)
    return await read_file("Stat/availability.xml")(io)


async def IVOA_capabilitiesEndpoint(req, IO):
async def ivoa_capabilities_endpoint(req, io):
    """
    Serves the static /capabilities XML response (functional signature: Request -> IO -> IO String).

    @param req: Incoming HTTP request
    @param IO: IO token
    @returns: Coroutine[str] - XML content
    Args:
        req: Incoming HTTP request.
        io: IO token.

    Returns:
        Awaitable[str]: XML content.
    """
    return await readFile("Stat/capabilities.xml")(IO)
    return await read_file("Stat/capabilities.xml")(io)


async def IVOA_tablesEndpoint(req, IO):
async def ivoa_tables_endpoint(req, io):
    """
    Executes an SQL query from file and returns a VOTable representation (functional signature: Request -> IO -> IO String).

    @param req: Incoming HTTP request
    @param IO: IO token
    @returns: Coroutine[str] - XML content in VOTable format or error message
    Args:
        req: Incoming HTTP request.
        io: IO token.

    Returns:
        Awaitable[str]: XML content in VOTable format or error message.
    """
    query = await readFile("Query/tables.sql")(IO)
    conn = await connPostgreSQL()(IO)
    result = await fetchRows(conn, query, [])(IO)
    await quitPostgreSQL(conn)(IO)
    query = await read_file("Query/tables.sql")(io)
    conn = await conn_postgresql()(io)
    result = await fetch_rows(conn, query, [])(io)
    await quit_postgresql(conn)(io)

    if result["tag"] == "nothing":
        return '<?xml version="1.0" encoding="UTF-8"?><error>No tables found</error>'
    return buildVOTablesFromRows(result["value"])

    return build_votables_from_rows(result["value"])


async def IVOA_main(IO):
async def ivoa_main(io):
    """
    Main entry point for the IVOA TAP and DataLink server (functional signature: IO -> IO ()).

    @param IO: IO token
    @returns: Coroutine[None] - Starts the server and binds all routes
    Args:
        io: IO token.

    Returns:
        Awaitable[None]: Starts the server and binds all routes.
    """
    await putStr("IVOA server started")(IO)
    port = readRESTPort(IO)
    await startRESTServer(
    port = read_rest_port(io)
    await start_rest_server(
        port,
        [
            {"path": "/tap", "endpoint": IVOA_tapEndpoint},
            {"path": "/tap/availability", "endpoint": IVOA_availabilityEndpoint},
            {"path": "/tap/capabilities", "endpoint": IVOA_capabilitiesEndpoint},
            {"path": "/tap/tables", "endpoint": IVOA_tablesEndpoint},
            {"path": "/tap", "endpoint": ivoa_tap_endpoint},
            {"path": "/tap/availability", "endpoint": ivoa_availability_endpoint},
            {"path": "/tap/capabilities", "endpoint": ivoa_capabilities_endpoint},
            {"path": "/tap/tables", "endpoint": ivoa_tables_endpoint},
        ],
    )(IO)
    )(io)
    await put_str("IVOA server started")(io)
    await asyncio.Event().wait()


def mainExpression():
def main_expression():
    """
    Wraps the program execution in a safe IO environment (functional signature: () -> IO ()).

    @returns: IO () - The IO token after the execution
    Returns:
        IO: The IO token after the execution.
    """
    return performIO(lambda IO: IVOA_main(IO))
    return perform_io(lambda io: ivoa_main(io))


if __name__ == "__main__":
    import asyncio

    try:
        asyncio.run(mainExpression())
        asyncio.run(main_expression())
    except InternalError as e:
        asyncio.run(putStr("ERROR: " + str(e))(IO))
        asyncio.run(put_str("ERROR: " + str(e))(IO))
    except Exception as e:
        raise
+2 −2
Original line number Diff line number Diff line
@@ -42,7 +42,7 @@ export function performIO(f) {
 * Prints a string to the console (functional signature: String -> IO -> IO).
 *
 * @param {string} x - Text to print
 * @returns {function(Symbol): Promise<Symbol>} - A CPS IO action
 * @returns {function(Symbol): Promise<Symbol>} - A CPS IO action that prints a string to the console
 */
export const putStr = (x) => async (IO) => {
  console.log(x);
@@ -53,7 +53,7 @@ export const putStr = (x) => async (IO) => {
 * Reads the contents of a file as UTF-8 (functional signature: String -> IO -> IO String).
 *
 * @param {string} path - Path to the file
 * @returns {function(Symbol): Promise<string>} - A CPS IO action
 * @returns {function(Symbol): Promise<string>} - A CPS IO action that reads the contents of a file as a UTF-8 string
 */
export const readFile = (path) => async (IO) => {
  return await fs.readFile(path, "utf-8");
+42 −30
Original line number Diff line number Diff line
@@ -5,87 +5,99 @@
# - pip install aiofiles

import aiofiles
from typing import Callable, Awaitable


class InternalError(Exception):
    """
    Custom error type to represent internal application exceptions (functional signature: String -> InternalError).

    @param message: The error message
    Args:
        message (str): The error message.
    """

    def __init__(self, message):
    def __init__(self, message: str):
        super().__init__(message)


# Token representing the 'IO world' in an implicit continuation-passing style (functional signature: Symbol).
IO = object()
"""
Token representing the 'IO world' in an implicit continuation-passing style (functional signature: Symbol).
"""


def createIO(f):
def create_io(f: Callable[[object], any]) -> any:
    """
    Applies an implicit CPS IO continuation to the IO token (functional signature: ((Symbol) -> a) -> a).

    @param f: A CPS function expecting an IO token
    @returns: The result of applying the function to the "IO world"
    Args:
        f (Callable[[Symbol], Any]): A CPS function expecting an IO token.

    Returns:
        Any: The result of applying the function to the IO world.
    """
    return f(IO)


def performIO(f):
def perform_io(f: Callable[[object], Awaitable[any]]) -> Awaitable[any]:
    """
    Alias for createIO which semantically marks a point where an effect is executed (functional signature: ((Symbol) -> a) -> a).
    Alias for create_io which semantically marks a point where an effect is executed (functional signature: ((Symbol) -> a) -> a).

    @param f: A CPS function
    @returns: The result of the function performing IO operations
    Args:
        f (Callable[[Symbol], Awaitable[Any]]): A CPS function.

    Returns:
        Awaitable[Any]: The result of the function performing IO operations.
    """
    return createIO(lambda w: f(w))
    return create_io(lambda w: f(w))


def putStr(x):
def put_str(x: str) -> Callable[[object], Awaitable[object]]:
    """
    Prints a string to the console (functional signature: String -> IO -> IO).

    @param x: Text to print
    @returns: A CPS IO action
    Args:
        x (str): Text to print.

    Returns:
        Callable[[Symbol], Awaitable[Symbol]]: A CPS IO action that prints the string.
    """

    async def inner(IO):
    async def inner(io: object) -> object:
        print(x)
        return IO
        return io

    return inner


def readFile(path):
def read_file(path: str) -> Callable[[object], Awaitable[str]]:
    """
    Reads the contents of a file as UTF-8 (functional signature: String -> IO -> IO String).

    @param path: Path to the file
    @returns: A CPS IO action
    Args:
        path (str): Path to the file.

    Returns:
        Callable[[Symbol], Awaitable[str]]: A CPS IO action that reads the contents.
    """

    async def inner(IO):
    async def inner(io: object) -> str:
        async with aiofiles.open(path, mode="r", encoding="utf-8") as f:
            content = await f.read()
            return content
            return await f.read()

    return inner


# Represents the absence of a value (functional signature: Maybe a).
nothing = {"tag": "nothing"}
"""
Represents the absence of a value (functional signature: Maybe a).
"""


def just(value):
def just(value: any) -> dict:
    """
    Wraps a value into a 'just' tagged union (functional signature: a -> Maybe a).

    @param value: The value to wrap
    @returns: A tagged value {tag: 'just', value: *}
    Args:
        value (Any): The value to wrap.

    Returns:
        dict: A tagged value {'tag': 'just', 'value': *}.
    """
    return {"tag": "just", "value": value}
+25 −14
Original line number Diff line number Diff line
@@ -15,19 +15,24 @@ import config from "../Conf/PostgreSQL.json" with { type: "json" };

const { Client } = pkg;

/**
 * @typedef {import("pg").Client} PGConn - A PostgreSQL connection in the CPS model
 */

/**
 * Connects to the PostgreSQL server using parameters from the config file (functional signature: () -> IO PGConn).
 *
 * @returns {function(Symbol): Promise<Client>} - A CPS IO action
 * @returns {function(Symbol): Promise<PGConn>} - A CPS IO action that returns a connected PostgreSQL connection
 * @throws {InternalError} - If the connection fails
 */

export function connPostgreSQL() {
  return async (IO) => {
    const client = new Client(config);
    const conn = new Client(config);
    try {
      await client.connect();
      putStr("Connected to PostgreSQL")(IO);
      return client;
      await conn.connect();
      await putStr("Connected to PostgreSQL")(IO);
      return conn;
    } catch (err) {
      throw new InternalError("Connection failed: " + err.message);
    }
@@ -37,17 +42,17 @@ export function connPostgreSQL() {
/**
 * Executes an SQL query with parameters, returning no result (functional signature: PGConn -> String -> List String -> IO ()).
 *
 * @param {Client} conn - A connected PostgreSQL client
 * @param {PGConn} conn - A connected PostgreSQL client
 * @param {string} sql - The SQL statement
 * @param {Array<string>} params - A list of parameters to bind
 * @returns {function(Symbol): Promise<Symbol>} - A CPS IO action
 * @returns {function(Symbol): Promise<Symbol>} - A CPS IO action that executes an SQL query without returning any result
 * @throws {InternalError} - If the query execution fails
 */
export function execQuery(conn, sql, params) {
  return async (IO) => {
    try {
      await conn.query(sql, params);
      putStr("Query executed")(IO);
      await putStr("Query executed")(IO);
      return IO;
    } catch (err) {
      throw new InternalError("Execution failed: " + err.message);
@@ -58,17 +63,22 @@ export function execQuery(conn, sql, params) {
/**
 * Executes a SELECT query and returns the resulting rows (functional signature: PGConn -> String -> List String -> IO (Maybe (List (List String)))).
 *
 * @param {Client} conn - A connected PostgreSQL client
 * @param {PGConn} conn - A connected PostgreSQL client
 * @param {string} sql - The SQL query
 * @param {Array<string>} params - A list of parameters to bind
 * @returns {function(Symbol): Promise<{ tag: "just", value: any[] }>} - A CPS IO action
 * @returns {function(Symbol): Promise<{ tag: "just", value: any[] }>} - A CPS IO action that returns query result rows wrapped in a Maybe
 * @throws {InternalError} - If the query fails
 */
export function fetchRows(conn, sql, params) {
  return async (IO) => {
    try {
      const res = await conn.query(sql, params);
      putStr(`${res.rows.length} rows retrieved`)(IO);
      await putStr(`${res.rows.length} rows retrieved`)(IO);

      if (res.rows.length === 0) {
        return nothing;
      }

      const rowsAsArrays = res.rows.map((row) => [
        row.schema_name,
        row.table_name,
@@ -77,6 +87,7 @@ export function fetchRows(conn, sql, params) {
        row.datatype,
        row.description,
      ]);

      return just(rowsAsArrays);
    } catch (err) {
      throw new InternalError("Select failed: " + err.message);
@@ -87,13 +98,13 @@ export function fetchRows(conn, sql, params) {
/**
 * Closes the PostgreSQL connection (functional signature: PGConn -> IO ()).
 *
 * @param {Client} conn - A connected PostgreSQL client
 * @returns {function(Symbol): Promise<Symbol>} - A CPS IO action
 * @param {PGConn} conn - A connected PostgreSQL client
 * @returns {function(Symbol): Promise<Symbol>} - A CPS IO action that closes the PostgreSQL connection
 */
export function quitPostgreSQL(conn) {
  return async (IO) => {
    await conn.end();
    putStr("PostgreSQL connection closed")(IO);
    await putStr("PostgreSQL connection closed")(IO);
    return IO;
  };
}
Loading