# flake8-iw <!-- omit from toc -->
Linters for some common issues we encounter.- [flake8-iw](#flake8-iw)
- [Building and testing](#building-and-testing)
- [Supported lint checks](#supported-lint-checks)
- [IW01: Use of patch](#iw01-use-of-patch)
- [IW02: Use of patch for time freeze](#iw02-use-of-patch-for-time-freeze)
- [IW03: Error logging without exception info (exc\_info)](#iw03-error-logging-without-exception-info-exc_info)
- [IW04: Use of datetime.now](#iw04-use-of-datetimenow)
- [IW05: Use of datetime.replace(tzinfo=XXX)](#iw05-use-of-datetimereplacetzinfoxxx)
- [IW06: Use of bulk\_update/bulk\_create without batch\_size](#iw06-use-of-bulk_updatebulk_create-without-batch_size)
- [IW07: Use of celery.shared_task, use instawork.decorators.shared_task](#iw07-use-of-celeryshared_task-use-instaworkdecoratorsshared_task)
- [IW08: use of timezone.activate or timezone.deactivate, use with timezone.override instead](#iw08-use-of-timezoneactivate-or-timezonedeactivate-use-with-timezoneoverride-instead)
- [IW09: missing db_constraint=False on foreign key](#iw09-missing-db_constraintfalse-on-foreign-key)
- [IW10: on_delete=DO_NOTHING on foreign key](#iw10-on_deletedo_nothing-on-foreign-key)
- [IW11: Use of db_index=True](#iw11-use_of_db_indextrue)
- [IW12: Use of unique=True](#iw12-use_of_uniquetrue)
- [IW13: Use of index_together instead of Options.indexes](#iw13-use_of_index_together_instead_of_optionsindexes)
- [IW14: Use of unique_together instead of Options.constraints](#iw14-use_of_unique_together_instead_of_optionsconstraints)
## Building and testing
Run command to build wheel and tarball.
```sh
python3 -m build
twine check --strict dist/*
twine upload dist/*
```
Run command to test.
```sh
pytest
```
To run package in edit mode (to test on live code) from the directory where running flake8.
This requires pip >= 21.3.1.
```sh
pip install -e ../flake8_iw/
```
## Supported lint checks
### IW01: Use of patch
Lint check to prevent the use of `patch` directly.
Recommendation: Use `PatchingTestCase` / `PatchingTransactionTestCase` instead
*Correct* ✅
```python
from instawork.tests import PatchingTestCase
class SignUpUpdatedTests(PatchingTestCase):
def setUp(self):
self.mock_call = self.patch("apps.auth.signals.task_send_email.delay")
def test_email(self):
expect(self.mock_call).to(have_been_called_once)
def test_sms(self):
mock_sms = self.patch("apps.auth.signals.task_send_sms.delay")
expect(mock_sms).to(have_been_called_once)
```
*Wrong* ⚠️
```python
from unittest.mock import patch
class SignUpUpdatedTests(TestCase):
def setUp(self):
self.patcher = patch("apps.auth.signals.task_send_email.delay")
self.mock_email = self.patcher.start()
def tearDown(self):
self.patcher.stop()
def test_email(self):
...
expect(self.mock_email).to(have_been_called_once)
@patch("apps.auth.signals.task_send_sms.delay")
def test_sms(self, mock_sms):
...
expect(mock_sms).to(have_been_called_once)
```
### IW02: Use of patch for time freeze
Lint check to prevent the use of `patch` to freeze time.
Recommendation: Use `freeze_time` from `PatchingTestCase` / `PatchingTransactionTestCase` or use `freeze_time` decorator or context manager from `freezegun` package.
*Correct* ✅
```python
from django.utils import timezone
from instawork.tests import PatchingTestCase
class UserFeatureViewTests(PatchingTestCase):
def setUp(self):
self.now = timezone.now()
def test_feature_view(self):
ufv = None
# Option 1
with freeze_time(self.now):
ufv = UserFeatureView.objects.get_or_create(
user=self.shift.worker, feature=UserFeatureView.FEATURE_1
)
# Option 2
self.freeze_time(self.now)
ufv = UserFeatureView.objects.get_or_create(
user=self.shift.worker, feature=UserFeatureView.FEATURE_1
)
...
expect(ufv.date_created).to(equal(self.now))
```
*Wrong* ⚠️
```python
from django.utils import timezone
from instawork.tests import PatchingTestCase
class UserFeatureViewTests(PatchingTestCase):
def setUp(self):
self.now = timezone.now()
self.mock_call = self.patch("django.utils.timezone.now", return_value=self.now)
def test_feature_view(self):
ufv = UserFeatureView.objects.get_or_create(
user=self.shift.worker, feature=UserFeatureView.FEATURE_1
)
...
expect(ufv.date_created).to(equal(self.now))
```
### IW03: Error logging without exception info (exc_info)
Lint check to prevent error logging without exception info.
Recommendation: Add `exc_info=True` keyword argument in `logger.error()`
*Correct* ✅
```python
import logging
custom_logger = logging.getLogger("module.logger")
class UserFeatureView(Model):
def save(self):
try:
...
except ValueError as e:
custom_logger.error(e, exc_info=True)
return name
```
*Wrong* ⚠️
```python
import logging
custom_logger = logging.getLogger("module.logger")
class UserFeatureView(Model):
def save(self):
try:
...
except ValueError as e:
custom_logger.error(e)
return name
```
### IW04: Use of datetime.now
Lint to avoid usage of `datetime.now()` which does not contain timezone information and causes various warnings in tests. Use `timezone.now()` instead.
*Correct* ✅
```python
from django.utils import timezone
now = timezone.now()
```
*Wrong* ⚠️
```python
from datetime import datetime
now = datetime.now()
```
### IW05: Use of datetime.replace(tzinfo=XXX)
Lint to avoid usage of `datetime.replace(tzinfo=XXX)` which is not a viable way of setting timezones with python/pytz.
*Correct* ✅
```python
import pytz
from django.utils import timezone
tz = pytz.timezone("America/Los_Angeles")
now_pt = timezone.now().astimezone(tz)
```
*Wrong* ⚠️
```python
import pytz
from django.utils import timezone
tz = pytz.timezone("America/Los_Angeles")
now_pt = timezone.now().replace(tzinfo=tz)
```
### IW06: Use of bulk_update/bulk_create without batch_size
Lint to avoid usage of [Model.objects.bulk_update](https://docs.djangoproject.com/en/dev/ref/models/querysets/#bulk-update) / [Model.objects.bulk_create](https://docs.djangoproject.com/en/dev/ref/models/querysets/#bulk-create). Use `Model.objects.bulk_update(batch_size=X)` / `Model.objects.bulk_create(batch_size=X)` instead.
*Correct* ✅
```python
# Bulk update
Model.objects.bulk_update([obj1, obj2, ...], batch_size=10)
# Bulk create
Model.objects.bulk_create([obj1, obj2, ...], batch_size=10)
```
*Wrong* ⚠️
```python
# Bulk update
Model.objects.bulk_update([obj1, obj2, ...])
# Bulk create
Model.objects.bulk_create([obj1, obj2, ...])
```
### IW07: Use of celery.shared_task, use instawork.decorators.shared_task
Use our internal decorator instead: `instawork.decorators.shared_task`.
*Correct* ✅
```python
from instawork.decorators import shared_task
@shared_task
def my_task():
pass
```
*Wrong* ⚠️
```python
from celery import shared_task
@shared_task
def my_task():
pass
```
### IW08: use of timezone.activate or timezone.deactivate, use with timezone.override instead
Lint to avoid usage of `timezone.activate()` and instead use `with timezone.override()`. This is to avoid timezone
leakage between different tests and features.
*Correct* ✅
```python
from django.utils import timezone
with timezone.override(zoneinfo.ZoneInfo(tzname)):
<Rest of the code>
```
*Wrong* ⚠️
```python
from django.utils import timezone
timezone.activate(zoneinfo.ZoneInfo(tzname))
<Rest of the code>
timezone.deactivate()
```
### IW09: missing db_constraint=False on foreign key
It's required to pass db_constraint=False when creating a new foreign key relationship.
This is to prevent issues with online schema changes that arise due to MySQL's foreign key
architecture.
*Correct* ✅
```python
x = models.ForeignKey(db_constraint=False, on_delete=models.CASCADE)
```
*Wrong* ⚠️
```python
x = models.ForeignKey(on_delete=models.CASCADE)
```
### IW10: on_delete=DO_NOTHING on foreign key
It's not advisable to use DO_NOTHING on foreign keys because we have removed
foreign key constraints in the database. It's best to have a strategy that deals
with deletions that doesn't leave "orphaned" foreign key ids.
*Correct* ✅
```python
x = models.ForeignKey(db_constraint=False, on_delete=models.CASCADE)
```
*Wrong* ⚠️
```python
x = models.ForeignKey(db_constraint=False, on_delete=models.DO_NOTHING)
```
### IW11: Use of db_index=True
Use `Options.indexes` to define an index rather than argument on field.
See [here](https://docs.djangoproject.com/en/4.2/ref/models/options/#django.db.models.Options.indexes)
*Correct* ✅
```python
x = models.CharField()
class Meta:
indexes = [
models.Index(fields=["x"], name="x_idx"),
]
```
*Wrong* ⚠️
```python
x = models.CharField(db_index=True)
```
### IW12: Use of unique=True
Use `Options.constraints` to define uniqueness rather than argument on field.
See [here](https://docs.djangoproject.com/en/4.2/ref/models/options/#django.db.models.Options.constraints)
and [here](https://docs.djangoproject.com/en/4.2/ref/models/constraints/#uniqueconstraint)
*Correct* ✅
```python
x = models.CharField()
class Meta:
constraints = [
models.UniqueConstraint(fields=["x"], name="unique_x"),
]
```
*Wrong* ⚠️
```python
x = models.CharField(unique=True)
```
### IW13: Use of index_together instead of Options.indexes
Use `Options.indexes` to define an index rather than argument on field.
See [here](https://docs.djangoproject.com/en/4.2/ref/models/options/#django.db.models.Options.indexes)
*Correct* ✅
```python
class Meta:
indexes = [
models.Index(fields=["a", "b"], name="a_b_idx"),
]
```
*Wrong* ⚠️
```python
class Meta:
index_together = (("a", "b"))
```
### IW14: Use of unique_together instead of Options.constraints
Use `Options.constraints` to define uniqueness rather than argument on field.
See [here](https://docs.djangoproject.com/en/4.2/ref/models/options/#django.db.models.Options.constraints)
and [here](https://docs.djangoproject.com/en/4.2/ref/models/constraints/#uniqueconstraint)
*Correct* ✅
```python
class Meta:
constraints = [
models.UniqueConstraint(fields=["a", "b"], name="unique_a_b"),
]
```
*Wrong* ⚠️
```python
class Meta:
unique_together = (("a", "b"))
```