Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Also run tests without extra dependencies #33

Open
wants to merge 15 commits into
base: master
Choose a base branch
from
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
31 changes: 30 additions & 1 deletion .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ jobs:
- uses: actions/checkout@v2

- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v2
uses: actions/setup-python@v1
with:
python-version: ${{ matrix.python-version }}

Expand All @@ -55,6 +55,35 @@ jobs:
- name: Run tests
run: tox -e py

- name: Upload coverage report
run: bash <(curl -s https://codecov.io/bash) -cF tests


tests-no-extra-deps:
name: "Test: py${{ matrix.python-version }}, Ubuntu, no extra deps"
runs-on: ubuntu-18.04
strategy:
matrix:
python-version: [3.6, 3.7, 3.8]

steps:
- uses: actions/checkout@v2

- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v2
with:
python-version: ${{ matrix.python-version }}

- name: Install tox
run: pip install tox

- name: Run tests
run: tox -e no-extra-deps

- name: Upload coverage report
run: bash <(curl -s https://codecov.io/bash) -cF tests


- name: Upload coverage report
run: bash <(curl -s https://codecov.io/bash)

Expand Down
164 changes: 113 additions & 51 deletions tests/__init__.py
Original file line number Diff line number Diff line change
@@ -1,62 +1,50 @@
from itemadapter.adapter import ItemAdapter
import os
import sys
from unittest import skipIf, TestCase as _TestCase


try:
import attr
except ImportError:
AttrsItem = None
AttrsItemNested = None
AttrsItemWithoutInit = None
else:
if os.environ.get("ITEMADAPTER_NO_EXTRA_DEPS"):
AttrsItem = None
AttrsItemWithoutInit = None
else:

@attr.s
class AttrsItem:
name = attr.ib(default=None, metadata={"serializer": str})
value = attr.ib(default=None, metadata={"serializer": int})
@attr.s
class AttrsItem:
name = attr.ib(default=None, metadata={"serializer": str})
value = attr.ib(default=None, metadata={"serializer": int})

@attr.s
class AttrsItemNested:
nested = attr.ib(type=AttrsItem)
adapter = attr.ib(type=ItemAdapter)
dict_ = attr.ib(type=dict)
list_ = attr.ib(type=list)
set_ = attr.ib(type=set)
tuple_ = attr.ib(type=tuple)
int_ = attr.ib(type=int)

@attr.s(init=False)
class AttrsItemWithoutInit:
name = attr.ib(default=None, metadata={"serializer": str})
value = attr.ib(default=None, metadata={"serializer": int})
@attr.s(init=False)
class AttrsItemWithoutInit:
name = attr.ib(default=None, metadata={"serializer": str})
value = attr.ib(default=None, metadata={"serializer": int})


try:
from dataclasses import dataclass, field
except ImportError:
DataClassItem = None
DataClassItemNested = None
DataClassWithoutInit = None
else:
if os.environ.get("ITEMADAPTER_NO_EXTRA_DEPS") and (3, 6) <= sys.version_info < (3, 7):
DataClassItem = None
DataClassWithoutInit = None
else:

@dataclass
class DataClassItem:
name: str = field(default_factory=lambda: None, metadata={"serializer": str})
value: int = field(default_factory=lambda: None, metadata={"serializer": int})

@dataclass
class DataClassItemNested:
nested: DataClassItem
adapter: ItemAdapter
dict_: dict
list_: list
set_: set
tuple_: tuple
int_: int
@dataclass
class DataClassItem:
name: str = field(default_factory=lambda: None, metadata={"serializer": str})
value: int = field(default_factory=lambda: None, metadata={"serializer": int})

@dataclass(init=False)
class DataClassWithoutInit:
name: str = field(metadata={"serializer": str})
value: int = field(metadata={"serializer": int})
@dataclass(init=False)
class DataClassWithoutInit:
name: str = field(metadata={"serializer": str})
value: int = field(metadata={"serializer": int})


try:
Expand All @@ -66,16 +54,90 @@ class DataClassWithoutInit:
ScrapySubclassedItem = None
ScrapySubclassedItemNested = None
else:
if os.environ.get("ITEMADAPTER_NO_EXTRA_DEPS"):
ScrapyItem = None
ScrapySubclassedItem = None
ScrapySubclassedItemNested = None
else:

class ScrapySubclassedItem(ScrapyItem):
name = Field(serializer=str)
value = Field(serializer=int)

class ScrapySubclassedItemNested(ScrapyItem):
nested = Field()
adapter = Field()
dict_ = Field()
list_ = Field()
set_ = Field()
tuple_ = Field()
int_ = Field()


class ImportRaiser:
def __init__(self, *packages):
self.packages = set(packages)

def find_spec(self, fullname, path, target=None):
if fullname in self.packages:
raise ImportError


class TestCase(_TestCase):
"""Custom TestCase subclass which handles disabling installed extra
packages during tests when ITEMADAPTER_NO_EXTRA_DEPS is set, as well as
disabling test cases that require one or more unavailable extra
dependencies.

This is needed to disable packages that cannot be uninstalled because
pytest depends on them.
"""

_extra_modules = ("attr", "scrapy")

def setUp(self):
super().setUp()

required_extra_modules = getattr(self, "required_extra_modules", None)
if required_extra_modules:
requirement_map = {
"attr": AttrsItem,
"dataclasses": DataClassItem,
"scrapy": ScrapyItem,
}
unknown_extra_modules = [
module for module in required_extra_modules if module not in requirement_map
]
if unknown_extra_modules:
raise NotImplementedError(
"Unknown extra modules: {}".format(unknown_extra_modules)
)
unavaliable_extra_modules = [
module for module in required_extra_modules if not requirement_map[module]
]
if unavaliable_extra_modules:
self.skipTest("cannot import; {}".format(", ".join(unavaliable_extra_modules)))

self._removed_modules = {}
if os.environ.get("ITEMADAPTER_NO_EXTRA_DEPS"):
if (3, 6) <= sys.version_info < (3, 7):
self._extra_modules = self._extra_modules + ("dataclasses",)
sys.meta_path.insert(0, ImportRaiser(*self._extra_modules))
for package in self._extra_modules:
if package in sys.modules:
self._removed_modules[package] = sys.modules[package]
del sys.modules[package]

def tearDown(self):
super().tearDown()

if os.environ.get("ITEMADAPTER_NO_EXTRA_DEPS"):
del sys.meta_path[0]
for package in self._extra_modules:
if package in self._removed_modules:
sys.modules[package] = self._removed_modules[package]


class ScrapySubclassedItem(ScrapyItem):
name = Field(serializer=str)
value = Field(serializer=int)

class ScrapySubclassedItemNested(ScrapyItem):
nested = Field()
adapter = Field()
dict_ = Field()
list_ = Field()
set_ = Field()
tuple_ = Field()
int_ = Field()
requires_attr = skipIf(not AttrsItem, "cannot import attr")
requires_dataclasses = skipIf(not DataClassItem, "cannot import dataclasses")
requires_scrapy = skipIf(not ScrapyItem, "cannot import scrapy")
19 changes: 19 additions & 0 deletions tests/attr_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
"""Code for attr.s tests that must only be imported from within tests because
it imports from itemadapter."""

import attr

from itemadapter import ItemAdapter

from tests import AttrsItem


@attr.s
class AttrsItemNested:
nested = attr.ib(type=AttrsItem)
adapter = attr.ib(type=ItemAdapter)
dict_ = attr.ib(type=dict)
list_ = attr.ib(type=list)
set_ = attr.ib(type=set)
tuple_ = attr.ib(type=tuple)
int_ = attr.ib(type=int)
19 changes: 19 additions & 0 deletions tests/dataclasses_utils.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
"""Code for dataclass tests that must only be imported from within tests
because it imports from itemadapter."""

from dataclasses import dataclass

from itemadapter import ItemAdapter

from tests import DataClassItem


@dataclass
class DataClassItemNested:
nested: DataClassItem
adapter: ItemAdapter
dict_: dict
list_: list
set_: set
tuple_: tuple
int_: int
5 changes: 0 additions & 5 deletions tests/requirements.txt

This file was deleted.

Loading