Skip to content

Commit 04295cc

Browse files
committed
Merge branch 'main' into dependabot/pip/httpx-0.28.0
2 parents a874171 + e8349a3 commit 04295cc

File tree

19 files changed

+801
-489
lines changed

19 files changed

+801
-489
lines changed

locust-load-test/locust_scripts/leis.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ def get_leis():
1212
with httpx.Client() as client:
1313
url = os.getenv(
1414
"LEI_REPO",
15-
"https://raw.githubusercontent.com/cfpb/sbl-test-data/test_leis/",
15+
"https://raw.githubusercontent.com/cfpb/sbl-test-data/refs/heads/main/test_leis/",
1616
)
1717
full_path = url + os.getenv("LEI_FILE", "test_leis.json")
1818
response = client.get(full_path)

poetry.lock

Lines changed: 199 additions & 199 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,7 @@ regtech-api-commons = {git = "https://github.com/cfpb/regtech-api-commons.git"}
1515
regtech-data-validator = {git = "https://github.com/cfpb/regtech-data-validator.git"}
1616
regtech-regex = {git = "https://github.com/cfpb/regtech-regex.git"}
1717
boto3 = "~1.34.0"
18-
python-multipart = "^0.0.18"
18+
python-multipart = "^0.0.19"
1919
alembic = "^1.14.0"
2020
async-lru = "^2.0.4"
2121
ujson = "^5.10.0"
@@ -31,7 +31,7 @@ aiosqlite = "^0.20.0"
3131

3232

3333
[tool.poetry.group.linters.dependencies]
34-
ruff = "0.7.4"
34+
ruff = "0.8.3"
3535
black = "24.8.0"
3636

3737

@@ -78,7 +78,9 @@ env = [
7878
"FS_UPLOAD_CONFIG__MKDIR=true",
7979
"FS_DOWNLOAD_CONFIG__PROTOCOL=file",
8080
"ENV=TEST",
81-
"MAIL_API_URL=http://mail-api:8765/internal/confirmation/send"
81+
"MAIL_API_URL=http://mail-api:8765/internal/confirmation/send",
82+
'REQUEST_VALIDATORS__SIGN_AND_SUBMIT=["valid_lei_status","valid_lei_tin","valid_filing_exists","valid_sub_accepted","valid_voluntary_filer","valid_contact_info"]',
83+
'REQUEST_VALIDATORS__FILING_CREATE=["valid_period_exists", "valid_no_filing_exists"]'
8284
]
8385
testpaths = ["tests"]
8486

src/.env.local

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,6 @@ FS_UPLOAD_CONFIG__PROTOCOL="file"
2020
FS_UPLOAD_CONFIG__ROOT="../upload"
2121
EXPIRED_SUBMISSION_CHECK_SECS=120
2222
SERVER_CONFIG__RELOAD="true"
23-
MAIL_API_URL=http://mail-api:8765/internal/confirmation/send
23+
MAIL_API_URL=http://mail-api:8765/internal/confirmation/send
24+
REQUEST_VALIDATORS__SIGN_AND_SUBMIT=["valid_lei_status","valid_lei_tin","valid_filing_exists","valid_sub_accepted","valid_voluntary_filer","valid_contact_info"]
25+
REQUEST_VALIDATORS__FILING_CREATE=["valid_period_exists", "valid_no_filing_exists"]

src/.env.template

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,4 +17,5 @@ CERTS_URL=${KC_REALM_URL}/protocol/openid-connect/certs
1717
FS_UPLOAD_CONFIG__PROTOCOL="file"
1818
FS_UPLOAD_CONFIG__ROOT="../upload"
1919
USER_FI_API_URL=http://localhost:8881/v1/institutions/
20-
EXPIRED_SUBMISSION_CHECK_SECS=120
20+
EXPIRED_SUBMISSION_CHECK_SECS=120
21+
REQUEST_VALIDATORS__SIGN_AND_SUBMIT=["check_lei_status","check_lei_tin","check_filing_exists","check_sub_accepted","check_voluntary_filer","check_contact_info"]

src/sbl_filing_api/config.py

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from enum import StrEnum
22
import os
33
from urllib import parse
4-
from typing import Any
4+
from typing import Any, Set
55

66
from pydantic import field_validator, ValidationInfo, BaseModel
77
from pydantic.networks import PostgresDsn
@@ -83,8 +83,25 @@ def build_postgres_dsn(cls, postgres_dsn, info: ValidationInfo) -> Any:
8383
model_config = SettingsConfigDict(env_file=env_files_to_load, extra="allow", env_nested_delimiter="__")
8484

8585

86+
class RequestActionValidations(BaseSettings):
87+
sign_and_submit: Set[str] = {
88+
"valid_lei_status",
89+
"valid_lei_tin",
90+
"valid_filing_exists",
91+
"valid_sub_accepted",
92+
"valid_voluntary_filer",
93+
"valid_contact_info",
94+
}
95+
96+
filing_create: Set[str] = {"valid_period_exists", "valid_no_filing_exists"}
97+
98+
model_config = SettingsConfigDict(env_prefix="request_validators__", env_file=env_files_to_load, extra="allow")
99+
100+
86101
settings = Settings()
87102

103+
request_action_validations = RequestActionValidations()
104+
88105
kc_settings = KeycloakSettings(_env_file=env_files_to_load)
89106

90107
regex_configs = RegexConfigs.instance()

src/sbl_filing_api/entities/models/dao.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
from sbl_filing_api.entities.models.model_enums import FilingType, FilingTaskState, SubmissionState, UserActionType
22
from datetime import datetime
33
from typing import Any, List
4-
from sqlalchemy import Enum as SAEnum, String
4+
from sqlalchemy import Enum as SAEnum, String, desc
55
from sqlalchemy import ForeignKey, func, UniqueConstraint
66
from sqlalchemy.orm import Mapped, mapped_column, DeclarativeBase, relationship
77
from sqlalchemy.ext.asyncio import AsyncAttrs
@@ -114,7 +114,10 @@ class FilingDAO(Base):
114114
lei: Mapped[str]
115115
tasks: Mapped[List[FilingTaskProgressDAO] | None] = relationship(lazy="selectin", cascade="all, delete-orphan")
116116
institution_snapshot_id: Mapped[str] = mapped_column(nullable=True)
117-
contact_info: Mapped[ContactInfoDAO] = relationship("ContactInfoDAO", lazy="joined")
117+
contact_info: Mapped[ContactInfoDAO | None] = relationship("ContactInfoDAO", lazy="joined")
118+
submissions: Mapped[List[SubmissionDAO] | None] = relationship(
119+
"SubmissionDAO", lazy="select", order_by=desc(SubmissionDAO.submission_time)
120+
)
118121
signatures: Mapped[List[UserActionDAO] | None] = relationship(
119122
"UserActionDAO", secondary="filing_signature", lazy="selectin", order_by="desc(UserActionDAO.timestamp)"
120123
)

src/sbl_filing_api/entities/repos/submission_repo.py

Lines changed: 7 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,6 @@
77

88
from regtech_api_commons.models.auth import AuthenticatedUser
99

10-
from copy import deepcopy
11-
1210
from async_lru import alru_cache
1311

1412
from sbl_filing_api.entities.models.dao import (
@@ -64,23 +62,17 @@ async def get_submission_by_counter(session: AsyncSession, lei: str, filing_peri
6462

6563
async def get_filing(session: AsyncSession, lei: str, filing_period: str) -> FilingDAO:
6664
result = await query_helper(session, FilingDAO, lei=lei, filing_period=filing_period)
67-
if result:
68-
result = await populate_missing_tasks(session, result)
6965
return result[0] if result else None
7066

7167

7268
async def get_filings(session: AsyncSession, leis: list[str], filing_period: str) -> list[FilingDAO]:
7369
stmt = select(FilingDAO).filter(FilingDAO.lei.in_(leis), FilingDAO.filing_period == filing_period)
7470
result = (await session.scalars(stmt)).all()
75-
if result:
76-
result = await populate_missing_tasks(session, result)
7771
return result if result else []
7872

7973

8074
async def get_period_filings(session: AsyncSession, filing_period: str) -> List[FilingDAO]:
8175
filings = await query_helper(session, FilingDAO, filing_period=filing_period)
82-
if filings:
83-
filings = await populate_missing_tasks(session, filings)
8476
return filings
8577

8678

@@ -148,9 +140,7 @@ async def upsert_filing(session: AsyncSession, filing: FilingDTO) -> FilingDAO:
148140

149141
async def create_new_filing(session: AsyncSession, lei: str, filing_period: str, creator_id: int) -> FilingDAO:
150142
new_filing = FilingDAO(filing_period=filing_period, lei=lei, creator_id=creator_id)
151-
new_filing = await upsert_helper(session, new_filing, FilingDAO)
152-
new_filing = await populate_missing_tasks(session, [new_filing])
153-
return new_filing[0]
143+
return await upsert_helper(session, new_filing, FilingDAO)
154144

155145

156146
async def update_task_state(
@@ -171,7 +161,12 @@ async def update_contact_info(
171161
session: AsyncSession, lei: str, filing_period: str, new_contact_info: ContactInfoDTO
172162
) -> FilingDAO:
173163
filing = await get_filing(session, lei=lei, filing_period=filing_period)
174-
filing.contact_info = ContactInfoDAO(**new_contact_info.__dict__.copy(), filing=filing.id)
164+
if filing.contact_info:
165+
for key, value in new_contact_info.__dict__.items():
166+
if key != "id":
167+
setattr(filing.contact_info, key, value)
168+
else:
169+
filing.contact_info = ContactInfoDAO(**new_contact_info.__dict__.copy(), filing=filing.id)
175170
return await upsert_helper(session, filing, FilingDAO)
176171

177172

@@ -202,23 +197,3 @@ async def query_helper(session: AsyncSession, table_obj: T, **filter_args) -> Li
202197
if filter_args:
203198
stmt = stmt.filter_by(**filter_args)
204199
return (await session.scalars(stmt)).all()
205-
206-
207-
async def populate_missing_tasks(session: AsyncSession, filings: List[FilingDAO]):
208-
filing_tasks = await get_filing_tasks(session)
209-
filings_copy = deepcopy(filings)
210-
for f in filings_copy:
211-
tasks = [t.task.name for t in f.tasks]
212-
missing_tasks = [t for t in filing_tasks if t.name not in tasks]
213-
for mt in missing_tasks:
214-
f.tasks.append(
215-
FilingTaskProgressDAO(
216-
filing=f.id,
217-
task_name=mt.name,
218-
task=mt,
219-
state=FilingTaskState.NOT_STARTED,
220-
user="",
221-
)
222-
)
223-
224-
return filings_copy

src/sbl_filing_api/routers/filing.py

Lines changed: 46 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,11 @@
1111
from regtech_api_commons.api.exceptions import RegTechHttpException
1212
from regtech_api_commons.models.auth import AuthenticatedUser
1313

14+
from sbl_filing_api.entities.models.dao import FilingDAO
1415
from sbl_filing_api.entities.models.model_enums import UserActionType
1516
from sbl_filing_api.services import submission_processor
1617
from sbl_filing_api.services.multithread_handler import handle_submission
18+
from sbl_filing_api.config import request_action_validations
1719
from typing import Annotated, List
1820

1921
from sbl_filing_api.entities.engine.engine import get_session
@@ -39,6 +41,8 @@
3941

4042
from sbl_filing_api.services.request_handler import send_confirmation_email
4143

44+
from sbl_filing_api.services.request_action_validator import UserActionContext, validate_user_action, set_context
45+
4246
logger = logging.getLogger(__name__)
4347

4448

@@ -72,92 +76,57 @@ async def get_filings(request: Request, period_code: str):
7276
return await repo.get_filings(request.state.db_session, user.institutions, period_code)
7377

7478

75-
@router.post("/institutions/{lei}/filings/{period_code}", response_model=FilingDTO)
79+
@router.post(
80+
"/institutions/{lei}/filings/{period_code}",
81+
response_model=FilingDTO,
82+
dependencies=[
83+
Depends(set_context({UserActionContext.PERIOD, UserActionContext.FILING})),
84+
Depends(validate_user_action(request_action_validations.filing_create, "Filing Create Forbidden")),
85+
],
86+
)
7687
@requires("authenticated")
7788
async def post_filing(request: Request, lei: str, period_code: str):
78-
period = await repo.get_filing_period(request.state.db_session, filing_period=period_code)
79-
80-
if period:
81-
filing = await repo.get_filing(request.state.db_session, lei, period_code)
82-
if filing:
83-
raise RegTechHttpException(
84-
status_code=status.HTTP_409_CONFLICT,
85-
name="Filing Creation Conflict",
86-
detail=f"Filing already exists for Filing Period {period_code} and LEI {lei}",
87-
)
88-
creator = None
89-
try:
90-
creator = await repo.add_user_action(
91-
request.state.db_session,
92-
UserActionDTO(
93-
user_id=request.user.id,
94-
user_name=request.user.name,
95-
user_email=request.user.email,
96-
action_type=UserActionType.CREATE,
97-
),
98-
)
99-
except Exception:
100-
logger.exception("Error while trying to create the filing.creator UserAction.")
101-
raise RegTechHttpException(
102-
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
103-
name="Filing.creator UserAction error",
104-
detail="Error while trying to create the filing.creator UserAction.",
105-
)
106-
107-
try:
108-
return await repo.create_new_filing(request.state.db_session, lei, period_code, creator_id=creator.id)
109-
except Exception:
110-
raise RegTechHttpException(
111-
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
112-
name="Filing Creation Error",
113-
detail=f"An error occurred while creating a filing for LEI {lei} and Filing Period {period_code}.",
114-
)
89+
creator = None
90+
try:
91+
creator = await repo.add_user_action(
92+
request.state.db_session,
93+
UserActionDTO(
94+
user_id=request.user.id,
95+
user_name=request.user.name,
96+
user_email=request.user.email,
97+
action_type=UserActionType.CREATE,
98+
),
99+
)
100+
except Exception:
101+
logger.exception("Error while trying to create the filing.creator UserAction.")
102+
raise RegTechHttpException(
103+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
104+
name="Filing.creator UserAction error",
105+
detail="Error while trying to create the filing.creator UserAction.",
106+
)
115107

116-
else:
108+
try:
109+
return await repo.create_new_filing(request.state.db_session, lei, period_code, creator_id=creator.id)
110+
except Exception:
117111
raise RegTechHttpException(
118-
status_code=status.HTTP_404_NOT_FOUND,
119-
name="Filing Period Not Found",
120-
detail=f"The period ({period_code}) does not exist, therefore a Filing can not be created for this period.",
112+
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
113+
name="Filing Creation Error",
114+
detail=f"An error occurred while creating a filing for LEI {lei} and Filing Period {period_code}.",
121115
)
122116

123117

124-
@router.put("/institutions/{lei}/filings/{period_code}/sign", response_model=FilingDTO)
118+
@router.put(
119+
"/institutions/{lei}/filings/{period_code}/sign",
120+
response_model=FilingDTO,
121+
dependencies=[
122+
Depends(set_context({UserActionContext.INSTITUTION, UserActionContext.FILING})),
123+
Depends(validate_user_action(request_action_validations.sign_and_submit, "Filing Action Forbidden")),
124+
],
125+
)
125126
@requires("authenticated")
126127
async def sign_filing(request: Request, lei: str, period_code: str):
127-
filing = await repo.get_filing(request.state.db_session, lei, period_code)
128-
if not filing:
129-
raise RegTechHttpException(
130-
status_code=status.HTTP_404_NOT_FOUND,
131-
name="Filing Not Found",
132-
detail=f"There is no Filing for LEI {lei} in period {period_code}, unable to sign a non-existent Filing.",
133-
)
134-
latest_sub = await repo.get_latest_submission(request.state.db_session, lei, period_code)
135-
if not latest_sub or latest_sub.state != SubmissionState.SUBMISSION_ACCEPTED:
136-
raise RegTechHttpException(
137-
status_code=status.HTTP_403_FORBIDDEN,
138-
name="Filing Action Forbidden",
139-
detail=f"Cannot sign filing. Filing for {lei} for period {period_code} does not have a latest submission the SUBMISSION_ACCEPTED state.",
140-
)
141-
if filing.is_voluntary is None:
142-
raise RegTechHttpException(
143-
status_code=status.HTTP_403_FORBIDDEN,
144-
name="Filing Action Forbidden",
145-
detail=f"Cannot sign filing. Filing for {lei} for period {period_code} does not have a selection of is_voluntary defined.",
146-
)
147-
if not filing.contact_info:
148-
raise RegTechHttpException(
149-
status_code=status.HTTP_403_FORBIDDEN,
150-
name="Filing Action Forbidden",
151-
detail=f"Cannot sign filing. Filing for {lei} for period {period_code} does not have contact info defined.",
152-
)
153-
"""
154-
if not filing.institution_snapshot_id:
155-
return JSONResponse(
156-
status_code=status.HTTP_403_FORBIDDEN,
157-
content=f"Cannot sign filing. Filing for {lei} for period {period_code} does not have institution snapshot id defined.",
158-
)
159-
"""
160-
128+
filing: FilingDAO = request.state.context["filing"]
129+
latest_sub = (await filing.awaitable_attrs.submissions)[0]
161130
sig = await repo.add_user_action(
162131
request.state.db_session,
163132
UserActionDTO(

0 commit comments

Comments
 (0)