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

Upgrade Python Version v3.8 -> v3.10 #302

Merged
merged 6 commits into from
Jul 8, 2024
Merged
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
2 changes: 1 addition & 1 deletion .github/workflows/deploy.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ jobs:
echo "ENV_URL=https://nccopwatch.org/" >> $GITHUB_ENV
- uses: actions/setup-python@v4
with:
python-version: '3.9'
python-version: '3.10'
cache: 'pip'
cache-dependency-path: 'requirements/*/*.txt'
- name: Install dependencies
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ jobs:
- uses: actions/checkout@v3
- uses: actions/setup-python@v4
with:
python-version: '3.9'
python-version: '3.10'
cache: 'pip'
cache-dependency-path: 'requirements/*/*.txt'
- name: Install dependencies
Expand Down
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ repos:
rev: 22.3.0
hooks:
- id: black
language_version: python3.8
language_version: python3.10
exclude: migrations
- repo: https://github.com/PyCQA/flake8
rev: 4.0.1
Expand Down
4 changes: 2 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ RUN npm install --silent
COPY frontend/ /code/
RUN npm run build

FROM python:3.8-slim-bullseye as base
FROM python:3.10-slim-bullseye as base

# Create a group and user to run our app
ARG APP_USER=appuser
Expand Down Expand Up @@ -93,7 +93,7 @@ ENTRYPOINT ["/code/docker-entrypoint.sh"]
# Start uWSGI
CMD ["newrelic-admin", "run-program", "uwsgi", "--single-interpreter", "--enable-threads", "--show-config"]

FROM python:3.8-slim-bullseye AS dev
FROM python:3.10-slim-bullseye AS dev

ARG USERNAME=appuser
ARG USER_UID=1000
Expand Down
6 changes: 3 additions & 3 deletions docs/dev-setup.rst
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ Below you will find basic setup and deployment instructions for the NC Traffic
Stops project. To begin you should have the following applications installed on
your local development system:

- Python 3.8
- Python 3.10
- NodeJS >= 12.6.0
- `pip >= 8 or so <http://www.pip-installer.org/>`_
- Postgres >= 12
Expand Down Expand Up @@ -85,8 +85,8 @@ To use ``psql`` locally, make sure you have the following env variables loaded
To setup your local environment you should create a virtualenv and install the
necessary requirements::

$ which python3.8 # make sure you have Python 3.8 installed
$ mkvirtualenv --python=`which python3.8` traffic-stops
$ which python3.11 # make sure you have Python 3.11 installed
$ mkvirtualenv --python=`which python3.11` traffic-stops
(traffic-stops)$ pip install -U pip
(traffic-stops)$ make setup

Expand Down
2 changes: 1 addition & 1 deletion nc/tests/api/test_arrests.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ def test_sort_by_stop_purpose(self):
self.assertEqual(sort_by_stop_purpose(df)["stop_purpose"].tolist(), StopPurpose.values)


@pytest.mark.django_db
@pytest.mark.django_db(databases=["traffic_stops_nc"])
class TestArrests:
def test_arrest_contraband_missing_race(self, client, durham):
"""A single stop will result no data for other races"""
Expand Down
3 changes: 3 additions & 0 deletions nc/tests/api/test_basic_search.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,13 @@ def test_no_agency(client, search_url):
assert response.status_code == status.HTTP_400_BAD_REQUEST


@pytest.mark.django_db(databases=["traffic_stops_nc"])
def test_agency_success(client, search_url, durham):
response = client.get(search_url, data={"agency": durham.pk}, format="json")
assert response.status_code == status.HTTP_200_OK


@pytest.mark.django_db(databases=["traffic_stops_nc"])
def test_response_person_fields(client, search_url, durham):
person = factories.PersonFactory(stop__agency=durham)
response = client.get(search_url, data={"agency": durham.pk}, format="json")
Expand All @@ -42,6 +44,7 @@ def test_response_person_fields(client, search_url, durham):
assert result == expected


@pytest.mark.django_db(databases=["traffic_stops_nc"])
@pytest.mark.parametrize("race", RACE_VALUES)
def test_race_filtering(client, search_url, durham, race):
other_races = RACE_VALUES - set(race)
Expand Down
3 changes: 3 additions & 0 deletions nc/tests/api/test_timezones.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ def august_person(durham):
return factories.PersonFactory(stop__agency=durham, stop__date=stop_date)


@pytest.mark.django_db(databases=["traffic_stops_nc"])
def test_stop_date_after_august_excludes_july_stop(client, search_url, durham, july_person):
response = client.get(
search_url,
Expand All @@ -33,6 +34,7 @@ def test_stop_date_after_august_excludes_july_stop(client, search_url, durham, j
assert july_person.stop.stop_id not in stop_ids


@pytest.mark.django_db(databases=["traffic_stops_nc"])
def test_stop_date_after_august_includes_august_stop(client, search_url, durham, august_person):
response = client.get(
search_url,
Expand All @@ -44,6 +46,7 @@ def test_stop_date_after_august_includes_august_stop(client, search_url, durham,
assert august_person.stop.date == response.data["results"][0]["date"]


@pytest.mark.django_db(databases=["traffic_stops_nc"])
def test_stop_date_after_july_includes_both(client, search_url, durham, july_person, august_person):
response = client.get(
search_url,
Expand Down
95 changes: 51 additions & 44 deletions nc/tests/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,10 @@ def test_list_agencies(self):
agency = factories.AgencyFactory()
url = reverse("nc:agency-api-list")
response = self.client.get(url, format="json")
response_data = response.json()
self.assertEqual(response.status_code, status.HTTP_200_OK)
# Other Agencies may have been left around from other tests
self.assertIn((agency.pk, agency.name), [(a["id"], a["name"]) for a in response.data])
self.assertIn((agency.pk, agency.name), [(a["id"], a["name"]) for a in response_data])

def test_agency_census_data(self):
"""
Expand All @@ -39,19 +40,21 @@ def test_agency_census_data(self):
agency = factories.AgencyFactory(census_profile_id=census_profile.id)
url = reverse("nc:agency-api-detail", args=[agency.pk])
response = self.client.get(url, format="json")
response_data = response.json()
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertIn("census_profile", response.data)
self.assertIn("census_profile", response_data)
# CensusProfile tests check census data in more detail
for attr in ("hispanic", "non_hispanic", "total"):
self.assertEqual(response.data["census_profile"][attr], getattr(census_profile, attr))
self.assertEqual(response_data["census_profile"][attr], getattr(census_profile, attr))

def test_stops_api(self):
"""Test Agency stops API endpoint with no stops"""
agency = factories.AgencyFactory()
url = reverse("nc:agency-api-stops", args=[agency.pk])
response = self.client.get(url, format="json")
response_data = response.json()
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(len(response.data), 0)
self.assertEqual(len(response_data), 0)

def test_stops_count(self):
"""Test Agency stop counts"""
Expand All @@ -73,18 +76,19 @@ def test_stops_count(self):
factories.PersonFactory(race="I", stop__agency=agency, ethnicity="H", stop__year=2012)

url = reverse("nc:agency-api-stops", args=[agency.pk])
response = self.client.get(url, format="json")
self.assertEqual(len(response.data), 2)
response = self.client.get(url, format="application/json")
response_data = response.json()
self.assertEqual(len(response_data), 2)
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(response.data[0]["year"], 2010)
self.assertEqual(response.data[0]["black"], 2)
self.assertEqual(response.data[0]["white"], 1)
self.assertEqual(response.data[0]["asian"], 0)
self.assertEqual(response.data[0]["hispanic"], 3)
self.assertEqual(response.data[1]["year"], 2012)
self.assertEqual(response.data[1]["black"], 0)
self.assertEqual(response.data[1]["white"], 1)
self.assertEqual(response.data[1]["hispanic"], 4)
self.assertEqual(response_data[0]["year"], 2010)
self.assertEqual(response_data[0]["black"], 2)
self.assertEqual(response_data[0]["white"], 1)
self.assertEqual(response_data[0]["asian"], 0)
self.assertEqual(response_data[0]["hispanic"], 3)
self.assertEqual(response_data[1]["year"], 2012)
self.assertEqual(response_data[1]["black"], 0)
self.assertEqual(response_data[1]["white"], 1)
self.assertEqual(response_data[1]["hispanic"], 4)

def test_grouping_by_year(self):
"""
Expand All @@ -111,12 +115,13 @@ def test_grouping_by_year(self):
)
url = reverse("nc:agency-api-stops", args=[agency.pk])
response = self.client.get(url, format="json")
response_data = response.json()
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(len(response.data), 2)
self.assertEqual(response.data[0]["year"], year)
self.assertEqual(response.data[0][race_label], 1)
self.assertEqual(response.data[1]["year"], year + 1)
self.assertEqual(response.data[1]["hispanic"], 1)
self.assertEqual(len(response_data), 2)
self.assertEqual(response_data[0]["year"], year)
self.assertEqual(response_data[0][race_label], 1)
self.assertEqual(response_data[1]["year"], year + 1)
self.assertEqual(response_data[1]["hispanic"], 1)

def test_officer_stops_count(self):
"""Test officer (within an agency) stop counts"""
Expand All @@ -131,12 +136,12 @@ def test_officer_stops_count(self):
url = reverse("nc:agency-api-stops", args=[agency.pk])
url = "{}?officer={}".format(url, p1.stop.officer_id)
response = self.client.get(url, format="json")
response_data = response.json()
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(len(response.data), 2)
self.assertEqual(response.data[0]["year"], p1.stop.date.year)
self.assertEqual(response.data[0][GROUPS[p1.race]], 1)
self.assertEqual(response.data[1]["year"], p2.stop.date.year)
self.assertEqual(response.data[1]["hispanic"], 2)
self.assertEqual(len(response_data), 2)
self.assertEqual(response_data[0]["year"], p1.stop.date.year)
self.assertEqual(response_data[1]["year"], p2.stop.date.year)
self.assertEqual(response_data[1]["hispanic"], 2)

def test_stops_by_reason(self):
"""Test Agency stops_by_reason API endpoint"""
Expand Down Expand Up @@ -189,10 +194,11 @@ def test_stops_by_reason(self):
factories.SearchFactory(stop=p5.stop)

response = self.client.get(url, format="json")
response_data = response.json()
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(len(response.data.keys()), 2)
self.assertEqual(len(response_data.keys()), 2)

searches = response.data["searches"]
searches = response_data["searches"]
self.assertEqual(searches[0]["year"], 2010)
self.assertEqual(searches[0]["black"], 0)
self.assertEqual(searches[0]["hispanic"], 3)
Expand All @@ -201,7 +207,7 @@ def test_stops_by_reason(self):
self.assertEqual(searches[1]["black"], 1)
self.assertEqual(searches[1]["purpose"], purpose_label)

stops = response.data["stops"]
stops = response_data["stops"]
self.assertEqual(stops[0]["year"], 2010)
self.assertEqual(stops[0]["black"], 1)
self.assertEqual(stops[0]["hispanic"], 3)
Expand All @@ -227,16 +233,17 @@ def test_searches(self):
factories.SearchFactory(person=p5, stop=p5.stop)
url = reverse("nc:agency-api-searches", args=[agency.pk])
response = self.client.get(url, format="json")
response_data = response.json()
self.assertEqual(response.status_code, status.HTTP_200_OK)
self.assertEqual(len(response.data), 2)
self.assertEqual(len(response_data), 2)
# Everyone got searched, so the expected racial data for 2015 are: 1 black,
# and for 2016 are: 1 native american, 3 hispanic
self.assertEqual(response.data[0]["year"], s1.stop.date.year)
self.assertEqual(response.data[0]["black"], 1)
self.assertEqual(response.data[1]["year"], s2.stop.date.year)
self.assertEqual(response.data[1]["black"], 0)
self.assertEqual(response.data[1]["native_american"], 1)
self.assertEqual(response.data[1]["hispanic"], 3)
self.assertEqual(response_data[0]["year"], s1.stop.date.year)
self.assertEqual(response_data[0]["black"], 1)
self.assertEqual(response_data[1]["year"], s2.stop.date.year)
self.assertEqual(response_data[1]["black"], 0)
self.assertEqual(response_data[1]["native_american"], 1)
self.assertEqual(response_data[1]["hispanic"], 3)

def test_searches_by_reason(self):
agency = factories.AgencyFactory()
Expand All @@ -259,18 +266,18 @@ def test_searches_by_reason(self):
factories.SearchFactory(person=p5, stop=p5.stop, type=type_code)

response = self.client.get(url, format="json")
response_data = response.json()
self.assertEqual(response.status_code, status.HTTP_200_OK)
# Two years = two items
self.assertEqual(len(response.data), 2)
self.assertEqual(len(response_data), 2)

searches = response.data
self.assertEqual(searches[0]["year"], 2015)
self.assertEqual(searches[0]["black"], 1)
self.assertEqual(searches[0]["search_type"], type_label)
self.assertEqual(searches[1]["year"], 2016)
self.assertEqual(searches[1]["hispanic"], 3)
self.assertEqual(searches[1]["native_american"], 1)
self.assertEqual(searches[1]["search_type"], type_label)
self.assertEqual(response_data[0]["year"], 2015)
self.assertEqual(response_data[0]["black"], 1)
self.assertEqual(response_data[0]["search_type"], type_label)
self.assertEqual(response_data[1]["year"], 2016)
self.assertEqual(response_data[1]["hispanic"], 3)
self.assertEqual(response_data[1]["native_american"], 1)
self.assertEqual(response_data[1]["search_type"], type_label)

def test_use_of_force(self):
pass
14 changes: 7 additions & 7 deletions requirements/base/base.in
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
# base requirements.in
django==3.2.14
django==3.2.25
celery
census==0.8.19
census==0.8.22
us
dealer
boto
boto3==1.26.87
botocore==1.29.109
click==8.1.3
boto3==1.34.100
botocore==1.34.100
click==8.1.7
django-cache-machine==1.2.0
django-ckeditor==6.7.0
django-click==2.3.0
Expand All @@ -23,8 +23,8 @@ djangorestframework==3.12.4
dj-database-url
drf-extensions==0.7.1
psycopg2<2.9
requests==2.28.2
urllib3==1.26.14
requests==2.32.3
urllib3==2.2.1
six
whitenoise
pandas<1.5
18 changes: 9 additions & 9 deletions requirements/base/base.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
#
# This file is autogenerated by pip-compile with Python 3.9
# This file is autogenerated by pip-compile with Python 3.10
# by the following command:
#
# pip-compile --output-file=requirements/base/base.txt requirements/base/base.in
Expand All @@ -12,30 +12,30 @@ billiard==3.6.3.0
# via celery
boto==2.49.0
# via -r requirements/base/base.in
boto3==1.26.87
boto3==1.34.100
# via -r requirements/base/base.in
botocore==1.29.109
botocore==1.34.100
# via
# -r requirements/base/base.in
# boto3
# s3transfer
celery==4.4.6
# via -r requirements/base/base.in
census==0.8.19
census==0.8.22
# via -r requirements/base/base.in
certifi==2020.6.20
# via requests
charset-normalizer==3.0.1
# via requests
click==8.1.3
click==8.1.7
# via
# -r requirements/base/base.in
# django-click
dealer==2.1.0
# via -r requirements/base/base.in
dj-database-url==0.5.0
# via -r requirements/base/base.in
django==3.2.14
django==3.2.25
# via
# -r requirements/base/base.in
# django-ckeditor
Expand Down Expand Up @@ -104,11 +104,11 @@ pytz==2022.1
# pandas
redis==3.5.3
# via django-redis
requests==2.28.2
requests==2.32.3
# via
# -r requirements/base/base.in
# census
s3transfer==0.6.0
s3transfer==0.10.1
# via boto3
six==1.15.0
# via
Expand All @@ -117,7 +117,7 @@ six==1.15.0
# python-dateutil
sqlparse==0.3.1
# via django
urllib3==1.26.14
urllib3==2.2.1
# via
# -r requirements/base/base.in
# botocore
Expand Down
Loading
Loading