Skip to content

Commit aea094f

Browse files
authored
Merge pull request #48 from cfpb/actions
Add action that runs backend linting and unit tests
2 parents 2b5bb1c + 1ade9c1 commit aea094f

79 files changed

Lines changed: 1493 additions & 575 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/backend_test.yml

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
name: Back-end tests
2+
3+
on:
4+
push:
5+
branches: [main]
6+
pull_request:
7+
branches: [main]
8+
9+
jobs:
10+
lint:
11+
runs-on: ubuntu-latest
12+
13+
defaults:
14+
run:
15+
working-directory: django
16+
17+
steps:
18+
- uses: actions/checkout@v6
19+
- uses: actions/setup-python@v6
20+
with:
21+
python-version: 3.13
22+
- run: python -m pip install --upgrade pip -r ./requirements.txt
23+
- run: ruff check
24+
25+
test:
26+
runs-on: ubuntu-latest
27+
28+
defaults:
29+
run:
30+
working-directory: django
31+
32+
steps:
33+
- uses: actions/checkout@v6
34+
- uses: actions/setup-python@v6
35+
with:
36+
python-version: 3.13
37+
- run: python -m pip install --upgrade pip -r ./requirements.txt
38+
- run: coverage run ./manage.py test
39+
- run: coverage report

django/django_application/asgi.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
from django.core.asgi import get_asgi_application
1313

14+
1415
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'django_application.settings')
1516

1617
application = get_asgi_application()
Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import json
22

3+
34
def get_json_file_contents(filepath: str):
45
return json.loads(get_file_contents(filepath))
56

67
def get_file_contents(filepath: str):
7-
with open(filepath, "r") as f:
8+
with open(filepath) as f:
89
return f.read().strip()

django/django_application/s3_utils.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
import boto3
21
from django.conf import settings
32

3+
import boto3
4+
45

56
def s3_bucket_files(bucket_directory: str):
67
bucket_name = settings.S3_BUCKET_NAME
Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,23 @@
1-
from django.conf import settings
21
from os import path
2+
3+
from django.conf import settings
34
from django.test import TestCase
5+
46
from django_application.file_utils import get_file_contents, get_json_file_contents
57

68

79
class TestReadFilesystemFiles(TestCase):
8-
file_path = path.join(settings.BASE_DIR, 'django_application', 'tests', 'sample_file.json')
10+
file_path = path.join(
11+
settings.BASE_DIR, 'django_application', 'tests', 'sample_file.json'
12+
)
913

1014
def test_read_plaintext_file(self):
1115
result = get_file_contents(self.file_path)
1216
self.assertEqual(result,
13-
'{"NAME":"myName","USER":"myUser","PASSWORD":"password123456","PORT":9876,"HOST":"myHost.sample.example.gov"}')
17+
'{"NAME":"myName","USER":"myUser","PASSWORD":"password123456","PORT":9876,"HOST":"myHost.sample.example.gov"}' # noqa: E501
18+
)
1419

1520
def test_read_json_file(self):
1621
result = get_json_file_contents(self.file_path)
1722
keys = sorted(list(result.keys()))
18-
self.assertEqual(keys, ['HOST', 'NAME', 'PASSWORD', 'PORT', 'USER'])
23+
self.assertEqual(keys, ['HOST', 'NAME', 'PASSWORD', 'PORT', 'USER'])

django/django_application/urls.py

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -14,15 +14,17 @@
1414
1. Import the include() function: from django.urls import include, path
1515
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
1616
"""
17-
from django.core.exceptions import ImproperlyConfigured
17+
from contextlib import suppress
18+
1819
from django.contrib import admin
19-
from django.urls import path, include, re_path
20+
from django.core.exceptions import ImproperlyConfigured
21+
from django.urls import include, path, re_path
2022
from django.views.generic import TemplateView
2123

22-
from users import views
23-
from evaluate_m2 import views as eval_views
24-
from evaluate_m2 import urls as evaluate_m2_urls
2524
from django_application import views as error_view
25+
from evaluate_m2 import urls as evaluate_m2_urls
26+
from evaluate_m2 import views as eval_views
27+
from users import views
2628

2729

2830
urlpatterns = [
@@ -33,16 +35,16 @@
3335
path('api/users/<int:user_id>/', views.users_view)
3436
]
3537

36-
try:
38+
with suppress(ImproperlyConfigured):
3739
# If the SSO library is installed, include auth-related URLs
3840
urlpatterns += [
3941
path('oauth2/', include('django_auth_adfs.urls')),
4042
]
41-
except ImproperlyConfigured:
42-
pass
4343

4444
# All erroneous API calls to return 400: bad request
4545
urlpatterns.append(re_path(r'api(?:.*)?', error_view.bad_request_view ))
4646

4747
# Fall through route: Handle all other URLs through the front end
48-
urlpatterns.append(re_path(r'^(?:.*)?', TemplateView.as_view(template_name='m2/index.html')))
48+
urlpatterns.append(
49+
re_path(r'^(?:.*)?', TemplateView.as_view(template_name='m2/index.html'))
50+
)

django/django_application/views.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1+
from rest_framework import status
12
from rest_framework.decorators import api_view
23
from rest_framework.response import Response
3-
from rest_framework import status
4+
45

56
@api_view()
67
def bad_request_view(request):

django/django_application/wsgi.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111

1212
from django.core.wsgi import get_wsgi_application
1313

14+
1415
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'django_application.settings')
1516

1617
application = get_wsgi_application()

django/evaluate_m2/admin.py

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,11 @@
11
from django.contrib import admin
2-
from evaluate_m2.models import EvaluatorMetadata, EvaluatorResultSummary, EvaluatorResult
2+
3+
from evaluate_m2.models import (
4+
EvaluatorMetadata,
5+
EvaluatorResult,
6+
EvaluatorResultSummary,
7+
)
8+
39

410
# Register your models here.
511
class EvaluatorMetadataAdmin(admin.ModelAdmin):

django/evaluate_m2/evaluate.py

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -5,15 +5,13 @@
55
from django.db import connection
66
from django.utils.module_loading import import_string
77

8-
from evaluate_m2.evaluate_utils import create_eval_insert_query
9-
from evaluate_m2.upload_utils import stream_results_files_to_s3
10-
8+
from evaluate_m2.evaluate_utils import create_eval_insert_query
119
from evaluate_m2.models import EvaluatorMetadata, EvaluatorResultSummary
12-
10+
from evaluate_m2.upload_utils import stream_results_files_to_s3
1311
from parse_m2.models import Metro2Event
1412

1513

16-
class Evaluate():
14+
class Evaluate:
1715
# Evaluator version is saved on each evaluator result summary.
1816
# Increment this version for all updates to evaluator functionality.
1917
evaluator_version = "1.3"
@@ -27,12 +25,12 @@ def load_evaluators(self):
2725
for eval_id, eval_import_str in evaluators_dict.items():
2826
try:
2927
eval_callable = import_string(eval_import_str)
30-
except ImportError:
28+
except ImportError as e:
3129
raise ImproperlyConfigured(
3230
f"Unable to import {eval_import_str} for evaluator "
3331
f"{eval_id}. Are you sure the package and evaluator "
3432
"callable exist?"
35-
)
33+
) from e
3634
self.evaluators[eval_id] = eval_callable
3735

3836
# runs evaluators to produce results
@@ -82,7 +80,9 @@ def save_evaluator_results(self, result_summary, eval_query):
8280
with connection.cursor() as cursor:
8381
cursor.execute(full_query, query_params)
8482

85-
def prepare_result_summary(self, event: Metro2Event, eval_id: str) -> EvaluatorResultSummary:
83+
def prepare_result_summary(
84+
self, event: Metro2Event, eval_id: str
85+
) -> EvaluatorResultSummary:
8686
"""
8787
Create an EvaluatorResultSummary object so we can associate results with it.
8888
Later, we will update the values related to eval hits.
@@ -102,7 +102,9 @@ def prepare_result_summary(self, event: Metro2Event, eval_id: str) -> EvaluatorR
102102
evaluator_version = self.evaluator_version,
103103
)
104104

105-
def update_result_summary_with_actual_results(self, result_summary: EvaluatorResultSummary):
105+
def update_result_summary_with_actual_results(
106+
self, result_summary: EvaluatorResultSummary
107+
):
106108
"""
107109
If the evaluator had any hits, update the information about the hits
108110
in the EvaluatorResultSummary record.

0 commit comments

Comments
 (0)