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