Skip to content

Commit 0d3d128

Browse files
committed
add pdp stuff;s/_/- for routes
1 parent 2031f01 commit 0d3d128

File tree

9 files changed

+196
-37
lines changed

9 files changed

+196
-37
lines changed

src/webapp/database.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -116,7 +116,7 @@ class InstTable(Base):
116116
schemas = Column(MutableList.as_mutable(JSON))
117117
state = Column(String(VAR_CHAR_LENGTH), nullable=True)
118118
# Only populated for PDP schools. Uncomment once logic available.
119-
# pdp_id: Mapped[int] = mapped_column(nullable=True)
119+
pdp_id: Mapped[int] = mapped_column(nullable=True)
120120
# A short description or note on this inst.
121121
description = Column(String(VAR_CHAR_LONGER_LENGTH))
122122
created_at = Column(DateTime(timezone=True), server_default=func.now())

src/webapp/main.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -127,7 +127,7 @@ async def login_for_access_token(
127127

128128

129129
# Get users that don't have institution specifications. (either datakinders or people who haven't set their institution yet)
130-
@app.get("/non_inst_users", response_model=list[users.UserAccount])
130+
@app.get("/non-inst-users", response_model=list[users.UserAccount])
131131
async def read_cross_inst_users(
132132
current_user: Annotated[BaseUser, Depends(get_current_active_user)],
133133
sql_session: Annotated[Session, Depends(get_session)],
@@ -206,7 +206,7 @@ async def set_datakinders(
206206
return res
207207

208208

209-
@app.get("/check_self", response_model=SelfInfo)
209+
@app.get("/check-self", response_model=SelfInfo)
210210
def read_self_info(
211211
current_user: Annotated[BaseUser, Depends(get_current_active_user)],
212212
sql_session: Annotated[Session, Depends(get_session)],

src/webapp/main_test.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ def test_retrieve_token(client: TestClient):
138138

139139
def test_get_cross_isnt_users(client: TestClient):
140140
"""Test GET /non_inst_users."""
141-
response = client.get("/non_inst_users")
141+
response = client.get("/non-inst-users")
142142
assert response.status_code == 200
143143
assert response.json() == [
144144
{
@@ -167,7 +167,7 @@ def test_set_datakinders(client: TestClient):
167167

168168
def test_check_self_datakinder(client: TestClient):
169169
"""Test GET /check_self."""
170-
response = client.get("/check_self")
170+
response = client.get("/check-self")
171171
assert response.status_code == 200
172172
assert response.json() == {
173173
"access_type": "DATAKINDER",
@@ -179,7 +179,7 @@ def test_check_self_datakinder(client: TestClient):
179179

180180
def test_check_self(user_client: TestClient):
181181
"""Test GET /check_self."""
182-
response = user_client.get("/check_self")
182+
response = user_client.get("/check-self")
183183
assert response.status_code == 200
184184
assert response.json() == {
185185
"access_type": "VIEWER",

src/webapp/routers/data.py

Lines changed: 49 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -117,6 +117,7 @@ class ValidationResult(BaseModel):
117117
name: str
118118
inst_id: str
119119
file_types: set[SchemaType]
120+
source: str
120121

121122

122123
class DataOverview(BaseModel):
@@ -261,7 +262,7 @@ def read_inst_all_input_files(
261262
}
262263

263264

264-
@router.get("/{inst_id}/input_debugging", response_model=list[str])
265+
@router.get("/{inst_id}/input-debugging", response_model=list[str])
265266
def get_all_files_in_bucket(
266267
inst_id: str,
267268
current_user: Annotated[BaseUser, Depends(get_current_active_user)],
@@ -549,7 +550,6 @@ def update_batch(
549550
model_owner_and_higher_or_err(current_user, "modify batch")
550551

551552
update_data = request.model_dump(exclude_unset=True)
552-
print("aaaaaaaaaaaaaaaaaaaaaaaa:" + str(update_data))
553553
local_session.set(sql_session)
554554
# Check that the batch exists.
555555
query_result = (
@@ -676,7 +676,7 @@ def update_batch(
676676
# TODO: check expiration of files and batches
677677

678678

679-
@router.get("/{inst_id}/file_id/{file_id}", response_model=DataInfo)
679+
@router.get("/{inst_id}/file-id/{file_id}", response_model=DataInfo)
680680
def read_file_id_info(
681681
inst_id: str,
682682
file_id: str,
@@ -796,7 +796,7 @@ def read_file_info(
796796

797797

798798
# TODO: ADD TESTS for the below
799-
@router.get("/{inst_id}/download_url/{file_name}", response_model=str)
799+
@router.get("/{inst_id}/download-url/{file_name}", response_model=str)
800800
def download_url_inst_file(
801801
inst_id: str,
802802
file_name: str,
@@ -867,15 +867,14 @@ def download_url_inst_file(
867867
# 3. Validate the file
868868

869869

870-
@router.post("/{inst_id}/input/validate/{file_name}", response_model=ValidationResult)
871-
def validate_file(
870+
def validation_helper(
871+
source_str: str,
872872
inst_id: str,
873873
file_name: str,
874-
current_user: Annotated[BaseUser, Depends(get_current_active_user)],
875-
storage_control: Annotated[StorageControl, Depends(StorageControl)],
876-
sql_session: Annotated[Session, Depends(get_session)],
874+
current_user: BaseUser,
875+
storage_control: StorageControl,
876+
sql_session: Session,
877877
) -> Any:
878-
"""Validate a given file. The file_name should not contain slashes."""
879878
has_access_to_inst_or_err(inst_id, current_user)
880879
if file_name.find("/") != -1:
881880
raise HTTPException(
@@ -916,7 +915,7 @@ def validate_file(
916915
name=file_name,
917916
inst_id=str_to_uuid(inst_id),
918917
uploader=str_to_uuid(current_user.user_id),
919-
source="MANUAL_UPLOAD",
918+
source=source_str,
920919
sst_generated=False,
921920
schemas=list(inferred_schemas),
922921
valid=True,
@@ -926,10 +925,48 @@ def validate_file(
926925
"name": file_name,
927926
"inst_id": inst_id,
928927
"file_types": inferred_schemas,
928+
"source": source_str,
929929
}
930930

931931

932-
@router.get("/{inst_id}/upload_url/{file_name}", response_model=str)
932+
@router.post(
933+
"/{inst_id}/input/validate-sftp/{file_name}", response_model=ValidationResult
934+
)
935+
def validate_file_sftp(
936+
inst_id: str,
937+
file_name: str,
938+
current_user: Annotated[BaseUser, Depends(get_current_active_user)],
939+
storage_control: Annotated[StorageControl, Depends(StorageControl)],
940+
sql_session: Annotated[Session, Depends(get_session)],
941+
) -> Any:
942+
"""Validate a given file pulled from SFTP. The file_name should not contain slashes."""
943+
if not current_user.is_datakinder:
944+
raise HTTPException(
945+
status_code=status.HTTP_401_UNAUTHORIZED,
946+
detail="SFTP validation needs to be done by a datakinder.",
947+
)
948+
return validation_helper(
949+
"PDP_SFTP", inst_id, file_name, current_user, storage_control, sql_session
950+
)
951+
952+
953+
@router.post(
954+
"/{inst_id}/input/validate-upload/{file_name}", response_model=ValidationResult
955+
)
956+
def validate_file_manual_upload(
957+
inst_id: str,
958+
file_name: str,
959+
current_user: Annotated[BaseUser, Depends(get_current_active_user)],
960+
storage_control: Annotated[StorageControl, Depends(StorageControl)],
961+
sql_session: Annotated[Session, Depends(get_session)],
962+
) -> Any:
963+
"""Validate a given file. The file_name should not contain slashes."""
964+
return validation_helper(
965+
"MANUAL_UPLOAD", inst_id, file_name, current_user, storage_control, sql_session
966+
)
967+
968+
969+
@router.get("/{inst_id}/upload-url/{file_name}", response_model=str)
933970
def get_upload_url(
934971
inst_id: str,
935972
file_name: str,

src/webapp/routers/data_test.py

Lines changed: 60 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -404,7 +404,7 @@ def test_read_file_id_info(client: TestClient):
404404
response = client.get(
405405
"/institutions/"
406406
+ uuid_to_str(UUID_INVALID)
407-
+ "/file_id/"
407+
+ "/file-id/"
408408
+ uuid_to_str(FILE_UUID_1)
409409
)
410410

@@ -417,7 +417,7 @@ def test_read_file_id_info(client: TestClient):
417417
response = client.get(
418418
"/institutions/"
419419
+ uuid_to_str(USER_VALID_INST_UUID)
420-
+ "/file_id/"
420+
+ "/file-id/"
421421
+ uuid_to_str(FILE_UUID_1)
422422
)
423423
assert response.status_code == 200
@@ -527,39 +527,81 @@ def test_update_batch(client: TestClient):
527527
def test_validate_success_batch(client: TestClient):
528528
"""Test PATCH /institutions/<uuid>/batch."""
529529
MOCK_STORAGE.validate_file.return_value = {SchemaType.UNKNOWN}
530-
response = client.post(
531-
"/institutions/" + uuid_to_str(UUID_INVALID) + "/input/validate/file_name.csv",
530+
531+
# Use validate for manual upload
532+
response_upload = client.post(
533+
"/institutions/"
534+
+ uuid_to_str(UUID_INVALID)
535+
+ "/input/validate-upload/file_name.csv",
532536
)
533-
assert str(response) == "<Response [401 Unauthorized]>"
537+
assert str(response_upload) == "<Response [401 Unauthorized]>"
534538
assert (
535-
response.text
539+
response_upload.text
536540
== '{"detail":"Not authorized to read this institution\'s resources."}'
537541
)
538542
# Authorized.
539-
response = client.post(
543+
response_upload = client.post(
540544
"/institutions/"
541545
+ uuid_to_str(USER_VALID_INST_UUID)
542-
+ "/input/validate/file_name.csv",
546+
+ "/input/validate-upload/file_name.csv",
543547
)
544-
assert response.status_code == 200
545-
assert response.json()["name"] == "file_name.csv"
546-
assert response.json()["file_types"] == ["UNKNOWN"]
547-
assert response.json()["inst_id"] == uuid_to_str(USER_VALID_INST_UUID)
548+
assert response_upload.status_code == 200
549+
assert response_upload.json()["name"] == "file_name.csv"
550+
assert response_upload.json()["file_types"] == ["UNKNOWN"]
551+
assert response_upload.json()["inst_id"] == uuid_to_str(USER_VALID_INST_UUID)
552+
assert response_upload.json()["source"] == "MANUAL_UPLOAD"
553+
554+
# Use validate for SFTP
555+
response_sftp = client.post(
556+
"/institutions/"
557+
+ uuid_to_str(UUID_INVALID)
558+
+ "/input/validate-sftp/file_name.csv",
559+
)
560+
assert str(response_sftp) == "<Response [401 Unauthorized]>"
561+
assert (
562+
response_sftp.text
563+
== '{"detail":"Not authorized to read this institution\'s resources."}'
564+
)
565+
# Authorized.
566+
response_sftp = client.post(
567+
"/institutions/"
568+
+ uuid_to_str(USER_VALID_INST_UUID)
569+
+ "/input/validate-sftp/file_name.csv",
570+
)
571+
assert response_sftp.status_code == 200
572+
assert response_sftp.json()["name"] == "file_name.csv"
573+
assert response_sftp.json()["file_types"] == ["UNKNOWN"]
574+
assert response_sftp.json()["inst_id"] == uuid_to_str(USER_VALID_INST_UUID)
575+
assert response_sftp.json()["source"] == "PDP_SFTP"
548576

549577

550578
def test_validate_failure_batch(client: TestClient):
551579
"""Test PATCH /institutions/<uuid>/batch."""
552580
MOCK_STORAGE.validate_file.return_value = {SchemaType.PDP_COHORT}
553581
# Authorized.
554-
response = client.post(
582+
# Use validate upload
583+
response_upload = client.post(
555584
"/institutions/"
556585
+ uuid_to_str(USER_VALID_INST_UUID)
557-
+ "/input/validate/file_name.csv",
586+
+ "/input/validate-upload/file_name.csv",
558587
)
559-
assert response.status_code == 200
560-
assert response.json()["name"] == "file_name.csv"
561-
assert response.json()["file_types"] == ["PDP_COHORT"]
562-
assert response.json()["inst_id"] == uuid_to_str(USER_VALID_INST_UUID)
588+
assert response_upload.status_code == 200
589+
assert response_upload.json()["name"] == "file_name.csv"
590+
assert response_upload.json()["file_types"] == ["PDP_COHORT"]
591+
assert response_upload.json()["inst_id"] == uuid_to_str(USER_VALID_INST_UUID)
592+
assert response_upload.json()["source"] == "MANUAL_UPLOAD"
593+
594+
# Use valiate sftp
595+
response_sftp = client.post(
596+
"/institutions/"
597+
+ uuid_to_str(USER_VALID_INST_UUID)
598+
+ "/input/validate-upload/file_name.csv",
599+
)
600+
assert response_sftp.status_code == 200
601+
assert response_sftp.json()["name"] == "file_name.csv"
602+
assert response_sftp.json()["file_types"] == ["PDP_COHORT"]
603+
assert response_sftp.json()["inst_id"] == uuid_to_str(USER_VALID_INST_UUID)
604+
assert response_sftp.json()["source"] == "MANUAL_UPLOAD"
563605

564606

565607
def test_pull_pdp_sftp(client: TestClient):

src/webapp/routers/institutions.py

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ class InstitutionCreationRequest(BaseModel):
7070
allowed_emails: Dict[str, AccessType] | None = None
7171
# The following is a shortcut to specifying the allowed_schemas list and will mean that the allowed_schemas will be augmented with the PDP_SCHEMA_GROUP.
7272
is_pdp: bool | None = None
73+
pdp_id: int | None = None
7374
retention_days: int | None = None
7475

7576

@@ -83,6 +84,7 @@ class Institution(BaseModel):
8384
# The following are characteristics of an institution set at institution creation time.
8485
# If zero, it follows DK defaults (deletion after completion).
8586
retention_days: int | None = None # In Days
87+
pdp_id: int | None = None
8688

8789

8890
@router.get("/institutions", response_model=list[Institution])
@@ -143,6 +145,12 @@ def create_institution(
143145
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
144146
detail="Description length too long.",
145147
)
148+
if (req.is_pdp and not req.pdp_id) or (req.pdp_id and not req.is_pdp):
149+
raise HTTPException(
150+
status_code=status.HTTP_400_BAD_REQUEST,
151+
detail="Please set the PDP's Institution ID for PDP schools and check PDP as a schema type.",
152+
)
153+
146154
local_session.set(sql_session)
147155
query_result = (
148156
local_session.get()
@@ -168,6 +176,7 @@ def create_institution(
168176
name=req.name,
169177
retention_days=req.retention_days,
170178
description=req.description,
179+
pdp_id=req.pdp_id,
171180
# Sets aren't json serializable, so turn them into lists first
172181
schemas=list(set(requested_schemas)),
173182
allowed_emails=req.allowed_emails,
@@ -210,6 +219,7 @@ def create_institution(
210219
"inst_id": uuid_to_str(query_result[0][0].id),
211220
"name": query_result[0][0].name,
212221
"state": query_result[0][0].state,
222+
"pdp_id": query_result[0][0].pdp_id,
213223
"description": query_result[0][0].description,
214224
"retention_days": query_result[0][0].retention_days,
215225
}
@@ -253,6 +263,48 @@ def read_inst_name(
253263
"description": query_result[0][0].description,
254264
"retention_days": query_result[0][0].retention_days,
255265
"state": query_result[0][0].state,
266+
"pdp_id": query_result[0][0].pdp_id,
267+
}
268+
269+
270+
# All other API transactions require the UUID as an identifier, this allows the UUID lookup by PDP ID.
271+
@router.get("/institutions/pdp-id/{pdp_id}", response_model=Institution)
272+
def read_inst_pdp_id(
273+
pdp_id: int,
274+
current_user: Annotated[BaseUser, Depends(get_current_active_user)],
275+
sql_session: Annotated[Session, Depends(get_session)],
276+
) -> Any:
277+
"""Returns overview data on a specific institution.
278+
279+
The root-level API view. Only visible to users of that institution or Datakinder access types.
280+
281+
Args:
282+
current_user: the user making the request.
283+
"""
284+
local_session.set(sql_session)
285+
query_result = (
286+
local_session.get()
287+
.execute(select(InstTable).where(InstTable.pdp_id == pdp_id))
288+
.all()
289+
)
290+
if len(query_result) == 0:
291+
raise HTTPException(
292+
status_code=status.HTTP_404_NOT_FOUND,
293+
detail="Institution not found.",
294+
)
295+
if len(query_result) > 1:
296+
raise HTTPException(
297+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
298+
detail="Institution duplicates found.",
299+
)
300+
has_access_to_inst_or_err(uuid_to_str(query_result[0][0].id), current_user)
301+
return {
302+
"inst_id": uuid_to_str(query_result[0][0].id),
303+
"name": query_result[0][0].name,
304+
"description": query_result[0][0].description,
305+
"retention_days": query_result[0][0].retention_days,
306+
"state": query_result[0][0].state,
307+
"pdp_id": query_result[0][0].pdp_id,
256308
}
257309

258310

@@ -292,4 +344,5 @@ def read_inst_id(
292344
"description": query_result[0][0].description,
293345
"retention_days": query_result[0][0].retention_days,
294346
"state": query_result[0][0].state,
347+
"pdp_id": query_result[0][0].pdp_id,
295348
}

0 commit comments

Comments
 (0)