repair tests
This commit is contained in:
parent
3637ea99a8
commit
1b1142a0b0
@ -29,123 +29,114 @@ class ApplicationError(Exception):
|
||||
|
||||
|
||||
class Application:
|
||||
__instance__: Optional[Self] = None
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
config: Config,
|
||||
logger: LoggerInstance,
|
||||
database: Database,
|
||||
cache: Cache,
|
||||
cron: Cron,
|
||||
backend: FastAPI,
|
||||
):
|
||||
if Application.__instance__:
|
||||
raise ApplicationError("Cannot create multiple applications")
|
||||
self.config: Config = config
|
||||
self.logger: Optional[LoggerInstance] = None
|
||||
self.database: Optional[Database] = None
|
||||
self.cache: Optional[Cache] = None
|
||||
self.cron: Optional[Cron] = None
|
||||
self.backend: Optional[FastAPI] = None
|
||||
|
||||
self.config = config
|
||||
self.logger = logger
|
||||
self.database = database
|
||||
self.cache = cache
|
||||
self.cron = cron
|
||||
self.backend = backend
|
||||
self.prepare_logger()
|
||||
|
||||
@staticmethod
|
||||
async def new(config: Config):
|
||||
if Application.__instance__:
|
||||
raise ApplicationError("Cannot create multiple applications")
|
||||
|
||||
logger = Logger.new(**config.log.model_dump())
|
||||
app = Application(config)
|
||||
|
||||
# if user := config.application.user:
|
||||
# os.setuid(pwd.getpwnam(user).pw_uid)
|
||||
# if group := config.application.group:
|
||||
# os.setgid(pwd.getpwnam(user).pw_gid)
|
||||
logger.debug("Initializing application...")
|
||||
app.logger.debug("Initializing application...")
|
||||
await app.prepare_working_directory()
|
||||
|
||||
try:
|
||||
logger.debug("Changing working directory")
|
||||
os.chdir(config.application.working_directory.resolve())
|
||||
except FileNotFoundError as e:
|
||||
logger.error("Failed to change working directory: {}", e)
|
||||
sys.exit()
|
||||
|
||||
try:
|
||||
logger.info("Connecting to database {}", config.database.url())
|
||||
database = await Database.new(config.database.url()) # type: ignore
|
||||
|
||||
logger.info("Connecting to cache server {}", config.cache.url())
|
||||
cache = await Cache.new(config.cache.url()) # type: ignore
|
||||
|
||||
logger.info("Prepairing cron")
|
||||
cron = Cron.new(
|
||||
config.cron.workers_count,
|
||||
backend_url=config.cache.url(),
|
||||
broker_url=config.cache.url(),
|
||||
)
|
||||
|
||||
logger.info("Running database migrations")
|
||||
await database.run_migrations()
|
||||
await app.prepare_database()
|
||||
await app.prepare_cache()
|
||||
await app.prepare_cron()
|
||||
await app.prepare_server()
|
||||
except Exception as e:
|
||||
logger.error(" ".join(e.args))
|
||||
app.logger.error(" ".join(e.args))
|
||||
sys.exit()
|
||||
|
||||
try:
|
||||
import materia_frontend
|
||||
except ModuleNotFoundError:
|
||||
logger.warning(
|
||||
app.logger.warning(
|
||||
"`materia_frontend` is not installed. No user interface will be served."
|
||||
)
|
||||
|
||||
return app
|
||||
|
||||
def prepare_logger(self):
|
||||
self.logger = Logger.new(**self.config.log.model_dump())
|
||||
|
||||
async def prepare_working_directory(self):
|
||||
try:
|
||||
self.logger.debug("Changing working directory")
|
||||
os.chdir(self.config.application.working_directory.resolve())
|
||||
except FileNotFoundError as e:
|
||||
self.logger.error("Failed to change working directory: {}", e)
|
||||
sys.exit()
|
||||
|
||||
async def prepare_database(self):
|
||||
url = self.config.database.url()
|
||||
self.logger.info("Connecting to database {}", url)
|
||||
self.database = await Database.new(url) # type: ignore
|
||||
|
||||
async def prepare_cache(self):
|
||||
url = self.config.cache.url()
|
||||
self.logger.info("Connecting to cache server {}", url)
|
||||
self.cache = await Cache.new(url) # type: ignore
|
||||
|
||||
async def prepare_cron(self):
|
||||
url = self.config.cache.url()
|
||||
self.logger.info("Prepairing cron")
|
||||
self.cron = Cron.new(
|
||||
self.config.cron.workers_count, backend_url=url, broker_url=url
|
||||
)
|
||||
|
||||
async def prepare_server(self):
|
||||
@asynccontextmanager
|
||||
async def lifespan(app: FastAPI) -> AsyncIterator[Context]:
|
||||
yield Context(config=config, logger=logger, database=database, cache=cache)
|
||||
yield Context(
|
||||
config=self.config,
|
||||
logger=self.logger,
|
||||
database=self.database,
|
||||
cache=self.cache,
|
||||
)
|
||||
|
||||
if database.engine is not None:
|
||||
await database.dispose()
|
||||
if self.database.engine is not None:
|
||||
await self.database.dispose()
|
||||
|
||||
backend = FastAPI(
|
||||
self.backend = FastAPI(
|
||||
title="materia",
|
||||
version="0.1.0",
|
||||
docs_url="/api/docs",
|
||||
lifespan=lifespan,
|
||||
)
|
||||
backend.add_middleware(
|
||||
self.backend.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=["http://localhost", "http://localhost:5173"],
|
||||
allow_credentials=True,
|
||||
allow_methods=["*"],
|
||||
allow_headers=["*"],
|
||||
)
|
||||
backend.include_router(routers.api.router)
|
||||
backend.include_router(routers.resources.router)
|
||||
backend.include_router(routers.root.router)
|
||||
|
||||
return Application(
|
||||
config=config,
|
||||
logger=logger,
|
||||
database=database,
|
||||
cache=cache,
|
||||
cron=cron,
|
||||
backend=backend,
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def instance() -> Optional[Self]:
|
||||
return Application.__instance__
|
||||
self.backend.include_router(routers.api.router)
|
||||
self.backend.include_router(routers.resources.router)
|
||||
self.backend.include_router(routers.root.router)
|
||||
|
||||
async def start(self):
|
||||
self.logger.info(f"Spinning up cron workers [{self.config.cron.workers_count}]")
|
||||
self.cron.run_workers()
|
||||
|
||||
try:
|
||||
# uvicorn.run(
|
||||
# self.backend,
|
||||
# port=self.config.server.port,
|
||||
# host=str(self.config.server.address),
|
||||
# # reload = config.application.mode == "development",
|
||||
# log_config=Logger.uvicorn_config(self.config.log.level),
|
||||
# )
|
||||
self.logger.info("Running database migrations")
|
||||
await self.database.run_migrations()
|
||||
|
||||
uvicorn_config = uvicorn.Config(
|
||||
self.backend,
|
||||
port=self.config.server.port,
|
||||
@ -157,3 +148,7 @@ class Application:
|
||||
await server.serve()
|
||||
except (KeyboardInterrupt, SystemExit):
|
||||
self.logger.info("Exiting...")
|
||||
sys.exit()
|
||||
except Exception as e:
|
||||
self.logger.error(" ".join(e.args))
|
||||
sys.exit()
|
||||
|
@ -95,19 +95,19 @@ class Repository(Base):
|
||||
await session.refresh(user, attribute_names=["repository"])
|
||||
return user.repository
|
||||
|
||||
async def used(self, session: SessionContext) -> int:
|
||||
async def used_capacity(self, session: SessionContext) -> int:
|
||||
session.add(self)
|
||||
await session.refresh(self, attribute_names=["files"])
|
||||
|
||||
return sum([file.size for file in self.files])
|
||||
|
||||
async def remaining_capacity(self, session: SessionContext) -> int:
|
||||
used = await self.used(session)
|
||||
used = await self.used_capacity(session)
|
||||
return self.capacity - used
|
||||
|
||||
async def info(self, session: SessionContext) -> "RepositoryInfo":
|
||||
info = RepositoryInfo.model_validate(self)
|
||||
info.used = await self.used(session)
|
||||
info.used = await self.used_capacity(session)
|
||||
|
||||
return info
|
||||
|
||||
|
@ -74,8 +74,7 @@ async def create(
|
||||
repository: Repository = Depends(middleware.repository),
|
||||
ctx: middleware.Context = Depends(),
|
||||
):
|
||||
database = await Database.new(ctx.config.database.url(), test_connection=False)
|
||||
async with database.session() as session:
|
||||
async with ctx.database.session() as session:
|
||||
capacity = await repository.remaining_capacity(session)
|
||||
|
||||
try:
|
||||
@ -116,7 +115,7 @@ async def create(
|
||||
file.remove()
|
||||
raise HTTPException(status.HTTP_500_INTERNAL_SERVER_ERROR, "Invalid path")
|
||||
|
||||
async with database.session() as session:
|
||||
async with ctx.database.session() as session:
|
||||
target_directory = await validate_target_directory(
|
||||
path, repository, session, ctx.config
|
||||
)
|
||||
|
@ -1,27 +1,17 @@
|
||||
import pytest_asyncio
|
||||
from materia.config import Config
|
||||
from materia.models import (
|
||||
Database,
|
||||
Cache,
|
||||
User,
|
||||
LoginType,
|
||||
)
|
||||
from materia.models.base import Base
|
||||
from materia import security
|
||||
from materia.app import Application
|
||||
from materia.core import Config, Database, Cache, Cron
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy.pool import NullPool
|
||||
from materia.app import make_application, AppContext
|
||||
from materia._logging import make_logger
|
||||
from httpx import AsyncClient, ASGITransport, Cookies
|
||||
import asyncio
|
||||
from fastapi import FastAPI
|
||||
from contextlib import asynccontextmanager
|
||||
from typing import AsyncIterator
|
||||
from asgi_lifespan import LifespanManager
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from materia import routers
|
||||
from pathlib import Path
|
||||
from copy import deepcopy
|
||||
|
||||
|
||||
@pytest_asyncio.fixture(scope="session")
|
||||
@ -75,6 +65,17 @@ async def cache(config: Config) -> Cache:
|
||||
yield cache_pytest
|
||||
|
||||
|
||||
@pytest_asyncio.fixture(scope="session")
|
||||
async def cron(config: Config) -> Cache:
|
||||
cron_pytest = Cron.new(
|
||||
config.cron.workers_count,
|
||||
backend_url=config.cache.url(),
|
||||
broker_url=config.cache.url(),
|
||||
)
|
||||
|
||||
yield cron_pytest
|
||||
|
||||
|
||||
@pytest_asyncio.fixture(scope="function", autouse=True)
|
||||
async def setup_database(database: Database):
|
||||
async with database.connection() as connection:
|
||||
@ -121,30 +122,36 @@ async def api_config(config: Config, tmpdir) -> Config:
|
||||
|
||||
@pytest_asyncio.fixture(scope="function")
|
||||
async def api_client(
|
||||
api_config: Config, database: Database, cache: Cache
|
||||
api_config: Config, database: Database, cache: Cache, cron: Cron
|
||||
) -> AsyncClient:
|
||||
|
||||
logger = make_logger(api_config)
|
||||
app = Application(api_config)
|
||||
app.database = database
|
||||
app.cache = cache
|
||||
app.cron = cron
|
||||
await app.prepare_server()
|
||||
|
||||
@asynccontextmanager
|
||||
async def lifespan(app: FastAPI) -> AsyncIterator[AppContext]:
|
||||
yield AppContext(
|
||||
config=api_config, database=database, cache=cache, logger=logger
|
||||
)
|
||||
# logger = make_logger(api_config)
|
||||
|
||||
app = FastAPI(lifespan=lifespan)
|
||||
app.include_router(routers.api.router)
|
||||
app.include_router(routers.resources.router)
|
||||
app.include_router(routers.root.router)
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=["*"],
|
||||
allow_credentials=True,
|
||||
allow_methods=["*"],
|
||||
allow_headers=["*"],
|
||||
)
|
||||
# @asynccontextmanager
|
||||
# async def lifespan(app: FastAPI) -> AsyncIterator[AppContext]:
|
||||
# yield AppContext(
|
||||
# config=api_config, database=database, cache=cache, logger=logger
|
||||
# )
|
||||
|
||||
async with LifespanManager(app) as manager:
|
||||
# app = FastAPI(lifespan=lifespan)
|
||||
# app.include_router(routers.api.router)
|
||||
# app.include_router(routers.resources.router)
|
||||
# app.include_router(routers.root.router)
|
||||
# app.add_middleware(
|
||||
# CORSMiddleware,
|
||||
# allow_origins=["*"],
|
||||
# allow_credentials=True,
|
||||
# allow_methods=["*"],
|
||||
# allow_headers=["*"],
|
||||
# )
|
||||
|
||||
async with LifespanManager(app.backend) as manager:
|
||||
async with AsyncClient(
|
||||
transport=ASGITransport(app=manager.app), base_url=api_config.server.url()
|
||||
) as client:
|
||||
|
@ -1,8 +1,6 @@
|
||||
import pytest
|
||||
from materia.config import Config
|
||||
from materia.core import Config
|
||||
from httpx import AsyncClient, Cookies
|
||||
from materia.models.base import Base
|
||||
import aiofiles
|
||||
from io import BytesIO
|
||||
|
||||
# TODO: replace downloadable images for tests
|
||||
@ -188,6 +186,6 @@ async def test_file(auth_client: AsyncClient, api_config: Config):
|
||||
pytest_logo = BytesIO(pytest_logo_res.content)
|
||||
|
||||
create = await auth_client.post(
|
||||
"/api/file", files={"file": ("pytest.png", pytest_logo)}, json={"path", "/"}
|
||||
"/api/file", files={"file": ("pytest.png", pytest_logo)}, data={"path": "/"}
|
||||
)
|
||||
assert create.status_code == 200, create.text
|
||||
|
@ -1,7 +1,6 @@
|
||||
import pytest_asyncio
|
||||
import pytest
|
||||
from pathlib import Path
|
||||
from materia.config import Config
|
||||
from materia.models import (
|
||||
User,
|
||||
Repository,
|
||||
@ -9,7 +8,7 @@ from materia.models import (
|
||||
RepositoryError,
|
||||
File,
|
||||
)
|
||||
from materia.models.database import SessionContext
|
||||
from materia.core import Config, SessionContext
|
||||
from materia import security
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy.orm.session import make_transient
|
||||
|
Loading…
Reference in New Issue
Block a user