Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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 .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.mypy_cache
.venv
uv.lock
1 change: 1 addition & 0 deletions .python-version
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
3.14
Empty file added docs/README-TEMPLATE.md
Empty file.
37 changes: 37 additions & 0 deletions examples/database.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
from __future__ import annotations

from typing import Self

from pydantic import BaseModel

from testing_utils import BaseUtils


class Transaction:
"""
TODO:
"""

def __init__(self, utils: DatabaseUtils) -> None:
self._utils = utils

async def commit(self) -> DatabaseUtils:
await self._utils.commit(self)
return self._utils


class DatabaseUtils(BaseUtils[Transaction, BaseModel]):
"""
TODO:
"""

# TODO: add types
def __init__(self, db, **kwargs) -> None:
super().__init__(**kwargs)
self._db = db

def fork(self, label: str = "") -> Self:
return self._fork(DatabaseUtils, label)

def start(self) -> Transaction:
return Transaction(self)
40 changes: 40 additions & 0 deletions examples/endpoints.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
from __future__ import annotations

from typing import Self

from pydantic import BaseModel

from testing_utils import BaseUtils, Model


class Transaction:
"""
TODO:
"""

def __init__(self, utils: EndpointUtils) -> None:
self._utils = utils

async def commit(self) -> EndpointUtils:
await self._utils.commit(self)
return self._utils


class EndpointUtils(BaseUtils[Transaction, BaseModel]):
"""
TODO:
"""

# TODO: add types
def __init__(self, client, **kwargs) -> None:
super().__init__(**kwargs)
self._client = client
self._models = [
Model(name="", requires=[])
]

def fork(self, label: str = "") -> Self:
return self._fork(EndpointUtils, label)

def start(self) -> Transaction:
return Transaction(self)
15 changes: 15 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
[project]
name = "testing-utils"
version = "0.1.0"
description = "Add your description here"
readme = "README.md"
requires-python = ">=3.14"
dependencies = []

[dependency-groups]
dev = [
"black>=25.11.0",
"isort>=7.0.0",
"mypy>=1.18.2",
"pytest>=9.0.1",
]
8 changes: 8 additions & 0 deletions testing_utils/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from .utils import BaseUtils
from .models import Model, FixtureSpec

__all__ = [
"BaseUtils",
"Model",
"FixtureSpec",
]
45 changes: 45 additions & 0 deletions testing_utils/models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
from dataclasses import dataclass
from typing import Any, Optional, TypeVar


@dataclass
class Model:
"""
TODO:
"""
name: str
requires: list[str]
plural: Optional[str] = None

@property
def plural_name(self) -> str:
if self.plural is not None:
return self.plural

return f"{self.name}s"


@dataclass
class FixtureSpec:
name: str
args: dict[str, Any]


@dataclass
class ModelWithFixture:
"""
TODO:
"""
model: Model
fixture: FixtureSpec


T = TypeVar("T")


def or_(*args: T | None) -> T:
for arg in args:
if arg is not None:
return arg

assert False, "or_() was given no non-None values"
Empty file added testing_utils/py.typed
Empty file.
65 changes: 65 additions & 0 deletions testing_utils/sort.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
from .models import Model, FixtureSpec, ModelWithFixture


def topological_sort_and_fill(
models: list[Model],
fixtures: list[FixtureSpec],
) -> list[ModelWithFixture]:
models_with_fixtures: list[ModelWithFixture] = []
fixture_model_names = {fixture.name for fixture in fixtures}

for fixture in fixtures:
model = next((m for m in models if m.name == fixture.name), None)

assert model is not None, f"Model {fixture.name} not found in models list"

models_with_fixtures.append(ModelWithFixture(model=model, fixture=fixture))

visited_names = set[str]()
stack: list[ModelWithFixture] = []

def dfs(node: ModelWithFixture) -> None:
visited_names.add(node.model.name)

for dependency in node.model.requires:
if (
dependency not in fixture_model_names
and dependency not in visited_names
):
# user didn't specify this model, so add in default
model_to_add = next(
(m for m in models if m.name == dependency),
None,
)

msg = f"Model {dependency} not found in models list"
assert model_to_add is not None, msg

node_to_add = ModelWithFixture(
model=model_to_add,
fixture=FixtureSpec(
name=model_to_add.name,
args={},
),
)

dfs(node_to_add)
elif dependency not in visited_names:
# add parent dependency
user_specified_node_to_add: ModelWithFixture | None = next(
(m for m in models_with_fixtures if m.model.name == dependency),
None,
)

msg = f"Model {dependency} not found in models list"
assert user_specified_node_to_add is not None, msg

dfs(user_specified_node_to_add)

stack.append(node)

for node in models_with_fixtures:
if node.model.name not in visited_names:
dfs(node)

return stack
Loading