Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .git-blame-ignore-revs
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,6 @@

# flake8 + isort -> ruff
1d060be6488aa0617de66357dd59da153325564d

# ruff update
d3084f2c0db6b3dbd40e939f2ff01f9c3ded318d
16 changes: 3 additions & 13 deletions .github/workflows/docs.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,26 +11,16 @@ jobs:
strategy:
matrix:
os: [ubuntu-latest]
python: [3.8]
python: [3.14]
steps:
- uses: actions/checkout@v2
- name: Set up Python ${{ matrix.python }}
uses: actions/setup-python@v1
uses: astral-sh/setup-uv@v7
with:
python-version: ${{ matrix.python }}
- name: Install GraphViz
run: sudo apt install graphviz
- name: Install Poetry
run: pip install poetry
- name: Use in-project virtualenv
run: poetry config virtualenvs.in-project true
- uses: actions/cache@v2
with:
path: .venv/
key: ${{ runner.os }}-${{ matrix.python }}-pip-${{ hashFiles('poetry.lock') }}-${{ hashFiles('pyproject.toml') }}
- name: Install dependencies
run: poetry install


- name: Build newsletters
run: make docs

Expand Down
26 changes: 8 additions & 18 deletions .github/workflows/pypi.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,24 +10,14 @@ jobs:
strategy:
matrix:
os: [ubuntu-latest]
python: [3.9]
python: [3.14]
steps:
- uses: actions/checkout@v2
- name: Set up Python ${{ matrix.python }}
uses: actions/setup-python@v1
- name: Install uv
uses: astral-sh/setup-uv@v7
with:
python-version: ${{ matrix.python }}
- name: Install Poetry
run: pip install poetry
- name: Use in-project virtualenv
run: poetry config virtualenvs.in-project true
- uses: actions/cache@v2
with:
path: .venv/
key: ${{ runner.os }}-${{ matrix.python }}-pip-${{ hashFiles('poetry.lock') }}-${{ hashFiles('pyproject.toml') }}
- name: Install dependencies
run: poetry install
- name: Build and publish to pypi
uses: JRubics/poetry-publish@v1.6
with:
pypi_token: ${{ secrets.PYPI_TOKEN }}
python-version: ${{ matrix.python-version }}
- name: Build
run: uv build
- name: Publish to PyPI
run: uv publish
18 changes: 4 additions & 14 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,30 +12,20 @@ jobs:
strategy:
matrix:
os: [ubuntu-latest]
python: [3.8, 3.9, "3.10", "3.11"]
python: ["3.13", "3.14"]
steps:
- uses: actions/checkout@v2
- name: Set up Python ${{ matrix.python }}
uses: actions/setup-python@v4
uses: astral-sh/setup-uv@v7
with:
python-version: ${{ matrix.python }}
- name: Install APT dependencies
run: sudo apt install graphviz mosquitto
- name: Install Poetry
run: pip install poetry
- name: Use in-project virtualenv
run: poetry config virtualenvs.in-project true
- uses: actions/cache@v2
with:
path: .venv/
key: ${{ runner.os }}-${{ matrix.python }}-pip-${{ hashFiles('poetry.lock') }}-${{ hashFiles('pyproject.toml') }}
- name: Install dependencies
run: poetry install
run: uv sync
- name: Lint
run: make lint
- name: Static type checking
run: make type
- name: Unit tests
run: make test-cov
- name: Build Docs
run: make docs
run: make test-cov
8 changes: 3 additions & 5 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,11 +1,9 @@
FROM python:3.9-slim as base
FROM ghcr.io/astral-sh/uv:latest as base

WORKDIR /app

RUN pip install --upgrade pip

COPY docker/astoria.toml /etc/
COPY astoria/ /app/astoria
COPY pyproject.toml README.md /app/
COPY pyproject.toml uv.lock README.md /app/

RUN pip install .
RUN uv sync
10 changes: 4 additions & 6 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
.PHONY: all clean docs docs-serve lint lint-fix type test test-cov

CMD:=poetry run
CMD:=uv run
PYMODULE:=astoria
TESTS:=tests
EXTRACODE:=docs/_code
Expand All @@ -17,15 +17,13 @@ docs-serve:
$(CMD) sphinx-autobuild $(SPHINX_ARGS)

lint:
$(CMD) ruff $(PYMODULE) $(TESTS) $(EXTRACODE) --exclude $(EXCLUDED_PATHS)
$(CMD) black --check $(PYMODULE) $(TESTS) $(EXTRACODE) --exclude $(EXCLUDED_PATHS)
$(CMD) ruff check $(PYMODULE) $(TESTS) $(EXTRACODE) --exclude $(EXCLUDED_PATHS)

lint-fix:
$(CMD) ruff --fix $(PYMODULE) $(TESTS) $(EXTRACODE) --exclude $(EXCLUDED_PATHS)
$(CMD) black $(PYMODULE) $(TESTS) $(EXTRACODE) --exclude $(EXCLUDED_PATHS)
$(CMD) ruff check --fix $(PYMODULE) $(TESTS) $(EXTRACODE) --exclude $(EXCLUDED_PATHS)

type:
$(CMD) mypy $(PYMODULE) $(TESTS) $(EXTRACODE) --exclude $(EXCLUDED_PATHS)
$(CMD) ty check $(PYMODULE) $(TESTS) $(EXTRACODE) --exclude $(EXCLUDED_PATHS)

test:
$(CMD) pytest $(PYTEST_FLAGS) --cov=$(PYMODULE) $(TESTS)
Expand Down
2 changes: 1 addition & 1 deletion astoria.toml
Original file line number Diff line number Diff line change
Expand Up @@ -27,4 +27,4 @@ default_usercode_entrypoint = "robot.py"
cache_dir = ".cache_dir" # Use a directory in /var for production

[env]
EXAMPLE_ENV_VAR=123
EXAMPLE_ENV_VAR = "123"
1 change: 1 addition & 0 deletions astoria/astctl/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

Split up into one class per command.
"""

import click

from .list_disks import list_disks
Expand Down
14 changes: 6 additions & 8 deletions astoria/astctl/command.py
Original file line number Diff line number Diff line change
@@ -1,19 +1,17 @@
"""Command base for astctl."""
import asyncio
from abc import abstractmethod
from json import JSONDecodeError, loads
from typing import Generic, Match, Type, TypeVar
from re import Match
from typing import TypeVar
from uuid import uuid4

from pydantic import parse_obj_as
from pydantic import TypeAdapter

from astoria.common.components import StateConsumer
from astoria.common.ipc import ManagerMessage

T = TypeVar("T", bound=ManagerMessage)

loop = asyncio.get_event_loop()


class Command(StateConsumer):
"""
Expand All @@ -40,7 +38,7 @@ def _setup_logging(
super()._setup_logging(verbose, welcome_message=False)


class SingleManagerMessageCommand(Command, Generic[T]):
class SingleManagerMessageCommand[T: ManagerMessage](Command):
"""
A command that waits for the message from a single manager and does something with it.

Expand All @@ -55,7 +53,7 @@ def manager(self) -> str:

@property
@abstractmethod
def message_schema(self) -> Type[T]:
def message_schema(self) -> type[T]:
"""The schema of the message for the manager."""
raise NotImplementedError

Expand Down Expand Up @@ -86,7 +84,7 @@ async def _handle_raw_message(
self._received = True
try:
data = loads(payload)
message = parse_obj_as(self.message_schema, data)
message = TypeAdapter(self.message_schema).validate_python(data)
if message.status == self.message_schema.Status.RUNNING:
self.handle_message(message)
else:
Expand Down
9 changes: 2 additions & 7 deletions astoria/astctl/list_disks.py
Original file line number Diff line number Diff line change
@@ -1,23 +1,18 @@
"""Command to list information about mounted disks."""
import asyncio
from typing import Optional

import click

from astoria.common.ipc import DiskManagerMessage

from .command import SingleManagerMessageCommand

loop = asyncio.get_event_loop()


@click.command("list-disks")
@click.option("-v", "--verbose", is_flag=True)
@click.option("-c", "--config-file", type=click.Path(exists=True))
def list_disks(*, verbose: bool, config_file: Optional[str]) -> None:
def list_disks(*, verbose: bool, config_file: str | None) -> None:
"""List information about mounted disks."""
command = ListDisksCommand(verbose, config_file)
loop.run_until_complete(command.run())
ListDisksCommand(verbose, config_file).execute()


class ListDisksCommand(SingleManagerMessageCommand[DiskManagerMessage]):
Expand Down
3 changes: 2 additions & 1 deletion astoria/astctl/metadata/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
"""Commands to interact with metadata."""

import click

from .set import set
from .set import set # noqa: A004
from .show import show


Expand Down
11 changes: 3 additions & 8 deletions astoria/astctl/metadata/set.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,10 @@
"""Command to set a metadata attribute."""
import asyncio
from typing import Optional

import click

from astoria.astctl.command import Command
from astoria.common.ipc import MetadataSetManagerRequest

loop = asyncio.get_event_loop()


@click.command("set")
@click.argument("attribute")
Expand All @@ -20,11 +16,10 @@ def set( # noqa: A001
value: str,
*,
verbose: bool,
config_file: Optional[str],
config_file: str | None,
) -> None:
"""Set a metadata attribute."""
command = SetMetadataCommand(attribute, value, verbose, config_file)
loop.run_until_complete(command.run())
SetMetadataCommand(attribute, value, verbose, config_file).execute()


class SetMetadataCommand(Command):
Expand All @@ -37,7 +32,7 @@ def __init__(
attribute: str,
value: str,
verbose: bool, # noqa: FBT001
config_file: Optional[str],
config_file: str | None,
) -> None:
super().__init__(verbose, config_file)
self._attr = attribute
Expand Down
9 changes: 2 additions & 7 deletions astoria/astctl/metadata/show.py
Original file line number Diff line number Diff line change
@@ -1,22 +1,17 @@
"""Command to show metadata."""
import asyncio
from typing import Optional

import click

from astoria.astctl.command import SingleManagerMessageCommand
from astoria.common.ipc import MetadataManagerMessage

loop = asyncio.get_event_loop()


@click.command("show")
@click.option("-v", "--verbose", is_flag=True)
@click.option("-c", "--config-file", type=click.Path(exists=True))
def show(*, verbose: bool, config_file: Optional[str]) -> None:
def show(*, verbose: bool, config_file: str | None) -> None:
"""Show current metadata."""
command = ShowMetadataCommand(verbose, config_file)
loop.run_until_complete(command.run())
ShowMetadataCommand(verbose, config_file).execute()


class ShowMetadataCommand(SingleManagerMessageCommand[MetadataManagerMessage]):
Expand Down
1 change: 1 addition & 0 deletions astoria/astctl/static_disks/__init__.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
"""Commands for managing static disks."""

import click

from .add import add
Expand Down
12 changes: 4 additions & 8 deletions astoria/astctl/static_disks/add.py
Original file line number Diff line number Diff line change
@@ -1,24 +1,20 @@
"""Command to add a filesystem path as a static disk."""
import asyncio

from pathlib import Path
from typing import Optional

import click

from astoria.astctl.command import Command
from astoria.common.ipc import AddStaticDiskRequest

loop = asyncio.get_event_loop()


@click.command("add")
@click.argument("path")
@click.option("-v", "--verbose", is_flag=True)
@click.option("-c", "--config-file", type=click.Path(exists=True))
def add(path: str, *, verbose: bool, config_file: Optional[str]) -> None:
def add(path: str, *, verbose: bool, config_file: str | None) -> None:
"""Mount a filesystem path as a disk."""
command = AddStaticDiskCommand(path, verbose, config_file)
loop.run_until_complete(command.run())
AddStaticDiskCommand(path, verbose, config_file).execute()


class AddStaticDiskCommand(Command):
Expand All @@ -32,7 +28,7 @@ def __init__(
self,
path: str,
verbose: bool, # noqa: FBT001
config_file: Optional[str],
config_file: str | None,
) -> None:
super().__init__(verbose, config_file)
self._path = Path(path).resolve()
Expand Down
12 changes: 4 additions & 8 deletions astoria/astctl/static_disks/remove.py
Original file line number Diff line number Diff line change
@@ -1,24 +1,20 @@
"""Command to add a filesystem path as a static disk."""
import asyncio

from pathlib import Path
from typing import Optional

import click

from astoria.astctl.command import Command
from astoria.common.ipc import RemoveStaticDiskRequest

loop = asyncio.get_event_loop()


@click.command("remove")
@click.argument("path")
@click.option("-v", "--verbose", is_flag=True)
@click.option("-c", "--config-file", type=click.Path(exists=True))
def remove(path: str, *, verbose: bool, config_file: Optional[str]) -> None:
def remove(path: str, *, verbose: bool, config_file: str | None) -> None:
"""Unmount a static disk."""
command = RemoveStaticDiskCommand(path, verbose, config_file)
loop.run_until_complete(command.run())
RemoveStaticDiskCommand(path, verbose, config_file).execute()


class RemoveStaticDiskCommand(Command):
Expand All @@ -32,7 +28,7 @@ def __init__(
self,
path: str,
verbose: bool, # noqa: FBT001
config_file: Optional[str],
config_file: str | None,
) -> None:
super().__init__(verbose, config_file)
self._path = Path(path).resolve()
Expand Down
Loading