Security News
Research
Data Theft Repackaged: A Case Study in Malicious Wrapper Packages on npm
The Socket Research Team breaks down a malicious wrapper package that uses obfuscation to harvest credentials and exfiltrate sensitive data.
pytest-fixture-classes
Advanced tools
Fixtures as classes that work well with dependency injection, autocompletetion, type checkers, and language servers
Typed factory fixtures that work well with dependency injection, autocompletetion, type checkers, and language servers.
No mypy plugins required!
pip install pytest-fixture-classes
Here's a factory fixture from pytest's documentation:
from typing import Any
import pytest
@pytest.fixture()
def orders() -> list[str]:
return ["order1", "order2"]
@pytest.fixture
def make_customer_record(orders: list[str]) -> Callable[str, dict[str, Any]]:
def _make_customer_record(name):
return {"name": name, "orders": orders}
return _make_customer_record
def test_customer_records(make_customer_record: Callable[str, dict[str, Any]]):
customer_1 = make_customer_record("Lisa")
customer_2 = make_customer_record("Mike")
customer_3 = make_customer_record("Meredith")
And here's the same factory implemented using fixture classes:
from pytest_fixture_classes import fixture_class
from typing import Any
import pytest
@pytest.fixture()
def orders():
return ["order1", "order2"]
@fixture_class(name="make_customer_record")
class MakeCustomerRecord:
orders: list[str]
def __call__(self, name: str) -> dict[str, Any]:
return return {"name": name, "orders": orders}
def test_customer_records(make_customer_record: MakeCustomerRecord):
customer_1 = make_customer_record("Lisa")
customer_2 = make_customer_record("Mike")
customer_3 = make_customer_record("Meredith")
You can, of course, add any methods you like into the class but I prefer to keep it a simple callable.
If we want factory fixtures that automatically make use of pytest's dependency injection, we are essentially giving up any IDE/typechecker/language server support because such fixtures cannot be properly typehinted because they are returning a callable, not a value. And python is still pretty new to typehinting callables.
So we can't use ctrl + click, we don't get any autocompletion, and mypy/pyright won't warn us when we are using the factory incorrectly. Additionally, any changes to the factory's interface will require us to search for its usages by hand and fix every single one.
Fixture classes solve all of the problems I mentioned:
Let's say that we have a few pre-existing fixtures: db_connection
, http_session
, and current_user
. Now we would like to write a new fixture that can create arbitrary users based on name
, age
, and height
arguments. We want our new fixture, create_user
, to automatically get our old fixtures using dependency injection. Let's see what such a fixture will look like:
import pytest
import requests
@pytest.fixture
def db_connection() -> dict[str, str]:
...
@pytest.fixture
def http_session() -> requests.Session:
...
@pytest.fixture
def current_user() -> requests.Session:
...
@pytest.fixture
async def create_user(
db_connection: dict[str, str],
http_session: requests.Session,
current_user: requests.Session,
) -> Callable[[str, int, int], dict[str, str | int | bool]]:
async def inner(name: str, age: int, height: int):
user = {...}
self.db_connection.execute(...)
if self.current_user[...] is not None:
self.http_session.post(...)
return user
return inner
def test_my_code(create_user: Callable[[str, int str], dict[str, str | int | bool]]):
johny = create_user("Johny", 27, 183)
michael = create_user("Michael", 43, 165)
loretta = create_user("Loretta", 31, 172)
# Some testing code below
...
See how ugly and vague the typehints for create_user are? Also, see how we duplicate the return type and argument information? Additionally, if you had thousands of tests and if test_my_code
with create_user
were in different files, you would have to use plaintext search to find the definition of the fixture if you wanted to see how to use it. Not too nice.
Now let's rewrite this code to solve all of the problems I mentioned:
from pytest_fixture_classes import fixture_class
from collections.abc import Mapping
import requests
import pytest
@pytest.fixture
def db_connection() -> dict[str, str]:
...
@pytest.fixture
def http_session() -> requests.Session:
...
@pytest.fixture
def current_user() -> Mapping[str, str | int | bool]:
...
@fixture_class(name="create_user")
class CreateUser:
db_connection: Mapping[str, str]
http_session: requests.Session
current_user: Mapping[str, str | int | bool]
def __call__(self, name: str, age: int, height: int) -> dict[str, str | int | bool]:
user = {...}
self.db_connection.execute(...)
if self.current_user[...] is not None:
self.http_session.post(...)
return user
def test_my_code(create_user: CreateUser):
johny = create_user("Johny", 27, 183)
michael = create_user("Michael", 43, 165)
loretta = create_user("Loretta", 31, 172)
# Some testing code below
...
__init__
.FAQs
Fixtures as classes that work well with dependency injection, autocompletetion, type checkers, and language servers
We found that pytest-fixture-classes demonstrated a healthy version release cadence and project activity because the last version was released less than a year ago. It has 1 open source maintainer collaborating on the project.
Did you know?
Socket for GitHub automatically highlights issues in each pull request and monitors the health of all your open source dependencies. Discover the contents of your packages and block harmful activity before you install or update your dependencies.
Security News
Research
The Socket Research Team breaks down a malicious wrapper package that uses obfuscation to harvest credentials and exfiltrate sensitive data.
Research
Security News
Attackers used a malicious npm package typosquatting a popular ESLint plugin to steal sensitive data, execute commands, and exploit developer systems.
Security News
The Ultralytics' PyPI Package was compromised four times in one weekend through GitHub Actions cache poisoning and failure to rotate previously compromised API tokens.