Metadata-Version: 2.1
Name: flake8-iw
Version: 0.0.16
Summary: A plugin to show lint errors for IW
Home-page: http://github.com/Instawork/flake8-iw
Author: Smit
Author-email: smitpatel@instawork.com
License: MIT
Classifier: Framework :: Flake8
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: MIT License
Classifier: Programming Language :: Python :: 3
Classifier: Topic :: Software Development :: Quality Assurance
Classifier: Operating System :: OS Independent
Description-Content-Type: text/markdown
Requires-Dist: flake8
Provides-Extra: testing
Requires-Dist: pytest ; extra == 'testing'
Requires-Dist: ipython ; extra == 'testing'
Requires-Dist: astpretty ; extra == 'testing'
Requires-Dist: build ; extra == 'testing'
Requires-Dist: twine ; extra == 'testing'

# 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)


## 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
```

## 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: 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
```
