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

Python static type checking #599

Merged
merged 10 commits into from
Oct 4, 2023
2 changes: 1 addition & 1 deletion client/src/features/SiteList/SiteList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import { Link } from 'react-router-dom';
* @constructor
*/
export function SiteList() {
const [siteData, loading, error] = useHtApi<Array<HaztrakSite>>('site/');
const [siteData, loading, error] = useHtApi<Array<HaztrakSite>>('site');
const [showErrorModal, setShowErrorModal] = useState(false);

useEffect(() => {
Expand Down
2 changes: 1 addition & 1 deletion client/src/features/home/Home.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { afterAll, afterEach, beforeAll, describe, expect, test, vi } from 'vite
const USERNAME = 'testuser1';

const myAPIHandlers = [
rest.get(`${API_BASE_URL}/api/profile/${USERNAME}`, (req, res, ctx) => {
rest.get(`${API_BASE_URL}/api/user/${USERNAME}/rcra/profile`, (req, res, ctx) => {
return res(
// Respond with a 200 status code
ctx.status(200),
Expand Down
2 changes: 1 addition & 1 deletion client/src/features/profile/RcraProfile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ export function RcraProfile({ profile }: ProfileViewProps) {
setProfileLoading(!profileLoading);
setEditable(!editable);
htApi
.put(`/profile/${profile.user}`, data)
.put(`/user/${profile.user}/rcra/profile`, data)
.then((r) => {
dispatch(updateProfile(r.data));
})
Expand Down
2 changes: 1 addition & 1 deletion client/src/features/profile/UserProfile.spec.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ const DEFAULT_USER: HaztrakUser = {
};

const server = setupServer(
rest.put(`${API_BASE_URL}/api/user/`, (req, res, ctx) => {
rest.put(`${API_BASE_URL}/api/user`, (req, res, ctx) => {
const user: HaztrakUser = { ...DEFAULT_USER };
// @ts-ignore
return res(ctx.status(200), ctx.json({ ...user, ...req.body }));
Expand Down
2 changes: 1 addition & 1 deletion client/src/features/profile/UserProfile.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export function UserProfile({ user }: UserProfileProps) {
const onSubmit = (data: any) => {
setEditable(!editable);
htApi
.put('/user/', data)
.put('/user', data)
.then((r) => {
dispatch(updateUserProfile(r.data));
})
Expand Down
1 change: 0 additions & 1 deletion client/src/services/HtApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ htApi.interceptors.request.use(
config.headers['Authorization'] = `Token ${token}`;
}
return config;
// ToDo: if token does not exist
},
(error) => {
return Promise.reject(error);
Expand Down
4 changes: 2 additions & 2 deletions client/src/store/notificationSlice/notification.slice.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { createAsyncThunk, createSlice, PayloadAction } from '@reduxjs/toolkit';
import axios from 'axios';
import { htApi } from 'services';

/**
* Schema of a user's alerts stored in the Redux store
Expand Down Expand Up @@ -32,7 +32,7 @@ const initialState: NotificationState = {
export const launchExampleTask = createAsyncThunk<HtNotification>(
'notification/getExampleTask',
async () => {
const response = await axios.get(`${import.meta.env.VITE_HT_API_URL}/api/task/example`);
const response = await htApi.get('/task/example');
const newNotification: HtNotification = {
inProgress: false,
message: `Background task launched. Task ID: ${response.data.task}`,
Expand Down
2 changes: 1 addition & 1 deletion client/src/store/rcraProfileSlice/rcraProfile.slice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ export const getProfile = createAsyncThunk<RcraProfileState>(
async (arg, thunkAPI) => {
const state = thunkAPI.getState() as RootState;
const username = state.user.user?.username;
const response = await htApi.get(`/profile/${username}`);
const response = await htApi.get(`/user/${username}/rcra/profile`);
const { rcraSites, ...rest } = response.data as RcraProfileResponse;
// Convert the array of RcraSite permissions we get from our backend
// to an object which each key corresponding to the RcraSite's ID number
Expand Down
6 changes: 3 additions & 3 deletions client/src/store/site.slice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,19 +14,19 @@ interface RcrainfoSiteSearch {
export const siteApi = createApi({
reducerPath: 'siteApi',
baseQuery: htApiBaseQuery({
baseUrl: `${import.meta.env.VITE_HT_API_URL}/api/site/`,
baseUrl: `${import.meta.env.VITE_HT_API_URL}/api/site`,
}),
endpoints: (build) => ({
searchRcrainfoSites: build.query<Array<RcraSite>, RcrainfoSiteSearch>({
query: (data: RcrainfoSiteSearch) => ({
url: 'rcrainfo/search',
url: '/rcrainfo/search',
method: 'post',
data: data,
}),
}),
searchRcraSites: build.query<Array<RcraSite>, RcrainfoSiteSearch>({
query: (data: RcrainfoSiteSearch) => ({
url: 'rcra-site/search',
url: '/search',
method: 'get',
params: { epaId: data.siteId, siteType: data.siteType },
}),
Expand Down
4 changes: 2 additions & 2 deletions client/src/store/userSlice/user.slice.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ const initialState: UserState = {
export const login = createAsyncThunk(
'user/login',
async ({ username, password }: { username: string; password: string }) => {
const response = await axios.post(`${import.meta.env.VITE_HT_API_URL}/api/user/login/`, {
const response = await axios.post(`${import.meta.env.VITE_HT_API_URL}/api/user/login`, {
username,
password,
});
Expand All @@ -46,7 +46,7 @@ export const login = createAsyncThunk(
);

export const getHaztrakUser = createAsyncThunk('user/getHaztrakUser', async (arg, thunkAPI) => {
const response = await htApi.get(`${import.meta.env.VITE_HT_API_URL}/api/user`);
const response = await htApi.get('/user');
if (response.status >= 200 && response.status < 300) {
return response.data as HaztrakUser;
} else {
Expand Down
2 changes: 1 addition & 1 deletion client/src/test-utils/mock/handlers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ export const handlers = [
/**
* User RcraProfile data
*/
rest.get(`${API_BASE_URL}/api/profile/${mockUsername}`, (req, res, ctx) => {
rest.get(`${API_BASE_URL}/api/user/${mockUsername}/rcra/profile`, (req, res, ctx) => {
return res(
// Respond with a 200 status code
ctx.status(200),
Expand Down
10 changes: 4 additions & 6 deletions server/apps/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,17 +11,15 @@
from django.contrib.auth.models import User
from rest_framework.test import APIClient

from apps.core.models import HaztrakUser, RcraProfile
from apps.sites.models import (
from apps.core.models import HaztrakUser, RcraProfile # type: ignore
from apps.sites.models import ( # type: ignore
Address,
Contact,
RcraPhone,
RcraSite,
Site,
)
from apps.trak.models import (
ManifestPhone,
)
from apps.trak.models import ManifestPhone # type: ignore


@pytest.fixture
Expand Down Expand Up @@ -52,7 +50,7 @@ def user_factory(db):
"""Abstract factory for Django's User model"""

def create_user(
username: Optional[str] = f"{''.join(random.choices(string.ascii_letters, k=9))}",
username: str = f"{''.join(random.choices(string.ascii_letters, k=9))}",
first_name: Optional[str] = "John",
last_name: Optional[str] = "Doe",
email: Optional[str] = "[email protected]",
Expand Down
28 changes: 15 additions & 13 deletions server/apps/core/services/rcrainfo_service.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
import logging
import os
from typing import Optional

from django.db import IntegrityError
from emanifest import RcrainfoClient, RcrainfoResponse
from emanifest import RcrainfoClient, RcrainfoResponse # type: ignore

from apps.core.models import RcraProfile
from apps.trak.models import WasteCode
from apps.core.models import RcraProfile # type: ignore
from apps.trak.models import WasteCode # type: ignore

logger = logging.getLogger(__name__)


class RcrainfoService(RcrainfoClient):
Expand All @@ -16,9 +19,8 @@ class RcrainfoService(RcrainfoClient):

datetime_format = "%Y-%m-%dT%H:%M:%S.%f%z"

def __init__(self, *, api_username: str, rcrainfo_env: str = None, **kwargs):
def __init__(self, *, api_username: str, rcrainfo_env: Optional[str] = None, **kwargs):
self.api_user = api_username
self.logger = logging.getLogger(__name__)
if RcraProfile.objects.filter(user__username=self.api_user).exists():
self.profile = RcraProfile.objects.get(user__username=self.api_user)
else:
Expand Down Expand Up @@ -54,7 +56,7 @@ def retrieve_key(self, api_key=None) -> str:
return super().retrieve_key(self.profile.rcra_api_key)
return super().retrieve_key()

def get_user_profile(self, username: str = None):
def get_user_profile(self, username: Optional[str] = None):
"""
Retrieve a user's site permissions from RCRAInfo, It expects the
haztrak user to have their unique RCRAInfo user and API credentials in their
Expand Down Expand Up @@ -90,13 +92,13 @@ def sign_manifest(self, **sign_data):
def search_mtn(
self,
reg: bool = False,
site_id: str = None,
start_date: str = None,
end_date: str = None,
status: str = None,
site_id: Optional[str] = None,
start_date: Optional[str] = None,
end_date: Optional[str] = None,
status: Optional[str] = None,
date_type: str = "UpdatedDate",
state_code: str = None,
site_type: str = None,
state_code: Optional[str] = None,
site_type: Optional[str] = None,
) -> RcrainfoResponse:
# map our python friendly keyword arguments to RCRAInfo expected fields
search_params = {
Expand All @@ -110,7 +112,7 @@ def search_mtn(
}
# Remove arguments that are None
filtered_params = {k: v for k, v in search_params.items() if v is not None}
self.logger.debug(f"rcrainfo manifest search parameters {filtered_params}")
logger.debug(f"rcrainfo manifest search parameters {filtered_params}")
return super().search_mtn(**filtered_params)

def __bool__(self):
Expand Down
18 changes: 10 additions & 8 deletions server/apps/core/services/task_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@
from rest_framework.exceptions import ValidationError
from rest_framework.utils.serializer_helpers import ReturnDict

from apps.core.serializers import TaskStatusSerializer
from apps.core.tasks import example_task
from apps.core.serializers import TaskStatusSerializer # type: ignore
from apps.core.tasks import example_task # type: ignore

logger = logging.getLogger(__name__)

Expand All @@ -17,11 +17,13 @@ class TaskService:
Service class for interacting with the Task model layer and celery tasks.
"""

def __init__(self, task_id, task_name, status="PENDING", result=None):
def __init__(
self, task_id: str, task_name: str, status: str = "PENDING", result: Optional[dict] = None
):
self.task_id = task_id
self.task_name = task_name
self.status = status
self.result: dict | None = result
self.result = result

@classmethod
def get_task_status(cls, task_id) -> ReturnDict:
Expand All @@ -35,7 +37,7 @@ def get_task_status(cls, task_id) -> ReturnDict:
return cls.get_task_results(task_id)

@staticmethod
def get_task_results(task_id):
def get_task_results(task_id: str) -> ReturnDict:
"""
Gets the results of a long-running celery task stored in the database
"""
Expand All @@ -51,7 +53,7 @@ def _parse_status(task_status: dict) -> ReturnDict:
raise ValidationError(task_serializer.errors)

@staticmethod
def _get_cached_status(task_id) -> dict | None:
def _get_cached_status(task_id: str) -> dict | None:
"""
Gets the status of a long-running celery task from our key-value store
if not found or error, returns None
Expand All @@ -67,7 +69,7 @@ def _get_cached_status(task_id) -> dict | None:
return None

@staticmethod
def launch_example_task():
def launch_example_task() -> str | None:
"""
Launches an example long-running celery task
"""
Expand All @@ -77,7 +79,7 @@ def launch_example_task():
except KeyError:
return None

def update_task_status(self, status: str, results: Optional = None) -> object | None:
def update_task_status(self, status: str, results: Optional[dict] = None) -> object | None:
"""
Updates the status of a long-running celery task in our key-value store
returns an error or None
Expand Down
12 changes: 5 additions & 7 deletions server/apps/core/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,19 +4,17 @@
HaztrakUserView,
LaunchExampleTaskView,
Login,
RcraProfileSyncView,
RcraProfileView,
RcraSitePermissionView,
SyncProfileView,
TaskStatusView,
)

urlpatterns = [
# Rcra Profile
path("profile/<str:user>/sync", SyncProfileView.as_view()),
path("profile/<str:user>", RcraProfileView.as_view()),
path("site/permission/<int:pk>", RcraSitePermissionView.as_view()),
path("user/", HaztrakUserView.as_view()),
path("user/login/", Login.as_view()),
path("user/<str:username>/rcra/profile/sync", RcraProfileSyncView.as_view()),
path("user/<str:username>/rcra/profile", RcraProfileView.as_view()),
path("user", HaztrakUserView.as_view()),
path("user/login", Login.as_view()),
path("task/example", LaunchExampleTaskView.as_view()),
path("task/<str:task_id>", TaskStatusView.as_view()),
]
3 changes: 1 addition & 2 deletions server/apps/core/views/__init__.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
from .auth_view import Login
from .profile_views import (
HaztrakUserView,
RcraProfileSyncView,
RcraProfileView,
RcraSitePermissionView,
SyncProfileView,
)
from .task_views import LaunchExampleTaskView, TaskStatusView
37 changes: 11 additions & 26 deletions server/apps/core/views/profile_views.py
Original file line number Diff line number Diff line change
@@ -1,16 +1,11 @@
from celery.exceptions import CeleryError
from django.contrib.auth.models import User
from rest_framework import status
from rest_framework.generics import GenericAPIView, RetrieveAPIView, RetrieveUpdateAPIView
from rest_framework.generics import GenericAPIView, RetrieveUpdateAPIView
from rest_framework.request import Request
from rest_framework.response import Response

from apps.core.models import HaztrakUser, RcraProfile
from apps.core.serializers import HaztrakUserSerializer, RcraProfileSerializer
from apps.sites.models import RcraSitePermission
from apps.sites.serializers import (
RcraSitePermissionSerializer,
)


class HaztrakUserView(RetrieveUpdateAPIView):
Expand All @@ -20,7 +15,6 @@ class HaztrakUserView(RetrieveUpdateAPIView):
serializer_class = HaztrakUserSerializer

def get_object(self):
# return HaztrakUser.objects.get(username="testuser1")
return self.request.user


Expand All @@ -35,36 +29,27 @@ class RcraProfileView(RetrieveUpdateAPIView):
serializer_class = RcraProfileSerializer
response = Response
lookup_field = "user__username"
lookup_url_kwarg = "user"
lookup_url_kwarg = "username"


class SyncProfileView(GenericAPIView):
class RcraProfileSyncView(GenericAPIView):
"""
This endpoint launches a task to sync the logged-in user's RCRAInfo profile
with their haztrak (Rcra)profile.
"""

queryset = None
queryset = RcraProfile.objects.all()
response = Response

def get(self, request: Request, user: str = None) -> Response:
def get(self, request: Request) -> Response:
"""Sync Profile GET method rcra_site"""
try:
profile = RcraProfile.objects.get(user=request.user)
task = profile.sync()
return self.response({"task": task.id})
except (User.DoesNotExist, CeleryError) as exc:
return self.response(data=exc, status=status.HTTP_500_INTERNAL_SERVER_ERROR)


class RcraSitePermissionView(RetrieveAPIView):
"""
For Viewing the RcraSite Permissions for the given user
"""

queryset = RcraSitePermission.objects.all()
serializer_class = RcraSitePermissionSerializer

def get_queryset(self):
user = self.request.user
return RcraSitePermission.objects.filter(profile__user=user)
except RcraProfile.DoesNotExist as exc:
return self.response(data={"error": str(exc)}, status=status.HTTP_404_NOT_FOUND)
except CeleryError as exc:
return self.response(
data={"error": str(exc)}, status=status.HTTP_500_INTERNAL_SERVER_ERROR
)
Loading
Loading