From 9db7ad344c57f39083de3dc98d12187b0ce3557b Mon Sep 17 00:00:00 2001 From: Padraic Shafer Date: Sat, 8 Aug 2026 09:51:49 -0700 Subject: [PATCH 1/4] Apply ruff format auto-fix --- src/nsls2api/api/models/facility_model.py | 3 +- src/nsls2api/api/models/person_model.py | 8 +- src/nsls2api/api/models/proposal_model.py | 3 +- src/nsls2api/api/v1/facility_api.py | 16 +-- src/nsls2api/api/v1/proposal_api.py | 11 +- src/nsls2api/api/v1/user_api.py | 15 +-- src/nsls2api/infrastructure/config.py | 6 +- src/nsls2api/services/beamline_service.py | 4 +- src/nsls2api/services/ldap_service.py | 59 +++++++---- src/nsls2api/services/proposal_service.py | 23 ++-- .../tests/services/test_proposal_service.py | 100 ++++++++++++++---- 11 files changed, 166 insertions(+), 82 deletions(-) diff --git a/src/nsls2api/api/models/facility_model.py b/src/nsls2api/api/models/facility_model.py index e3ad7293..0302f0f0 100644 --- a/src/nsls2api/api/models/facility_model.py +++ b/src/nsls2api/api/models/facility_model.py @@ -18,10 +18,11 @@ class FacilityCurrentOperatingCycleResponseModel(pydantic.BaseModel): facility: str cycle: str + class FacilityCycleDetailsResponseModel(pydantic.BaseModel): facility: str cycle: str start_date: datetime | None = None end_date: datetime | None = None is_current_operating_cycle: bool - accepting_proposals: bool | None = None \ No newline at end of file + accepting_proposals: bool | None = None diff --git a/src/nsls2api/api/models/person_model.py b/src/nsls2api/api/models/person_model.py index 5d8ebce8..aab9dbcd 100644 --- a/src/nsls2api/api/models/person_model.py +++ b/src/nsls2api/api/models/person_model.py @@ -104,6 +104,7 @@ class UnixInfo(pydantic.BaseModel): homeDirectory: Optional[str] = None loginShell: Optional[str] = None + class IdentityInfo(pydantic.BaseModel): displayName: Optional[str] = None email: Optional[str] = None @@ -111,6 +112,7 @@ class IdentityInfo(pydantic.BaseModel): manager: Optional[str] = None unix: Optional[UnixInfo] = None + class AccountInfo(pydantic.BaseModel): accountExpires: Optional[str] = None badPasswordTime: Optional[str] = None @@ -126,6 +128,7 @@ class AccountInfo(pydantic.BaseModel): uSNCreated: Optional[int] = None uSNChanged: Optional[int] = None + class DirectoryInfo(pydantic.BaseModel): objectGUID: Optional[str] = None objectSid: Optional[str] = None @@ -134,6 +137,7 @@ class DirectoryInfo(pydantic.BaseModel): whenCreated: Optional[str] = None whenChanged: Optional[str] = None + class AttributesInfo(pydantic.BaseModel): sn: Optional[str] = None givenName: Optional[str] = None @@ -145,8 +149,10 @@ class AttributesInfo(pydantic.BaseModel): instanceType: Optional[str] = None objectClass: List[str] = pydantic.Field(default_factory=list) + class LDAPUserResponse(pydantic.BaseModel): """Complete LDAP user data from direct LDAP query""" + dn: Optional[str] = None status: str = "Read" readTime: Optional[str] = None @@ -154,4 +160,4 @@ class LDAPUserResponse(pydantic.BaseModel): account: Optional[AccountInfo] = None directory: Optional[DirectoryInfo] = None groups: List[str] = pydantic.Field(default_factory=list) - attributes: Optional[AttributesInfo] = None \ No newline at end of file + attributes: Optional[AttributesInfo] = None diff --git a/src/nsls2api/api/models/proposal_model.py b/src/nsls2api/api/models/proposal_model.py index b426e65b..7285edae 100644 --- a/src/nsls2api/api/models/proposal_model.py +++ b/src/nsls2api/api/models/proposal_model.py @@ -159,8 +159,9 @@ class ProposalIdDataSession(pydantic.BaseModel): proposal_id: str data_session: str | None = None + class ProposalIdDataSessionList(pydantic.BaseModel): proposals: list[ProposalIdDataSession] count: int page_size: int - page: int \ No newline at end of file + page: int diff --git a/src/nsls2api/api/v1/facility_api.py b/src/nsls2api/api/v1/facility_api.py index a525d115..a1d9053b 100644 --- a/src/nsls2api/api/v1/facility_api.py +++ b/src/nsls2api/api/v1/facility_api.py @@ -3,16 +3,20 @@ from nsls2api.api.models.facility_model import ( FacilityCurrentOperatingCycleResponseModel, - FacilityCycleDetailsResponseModel, FacilityCyclesResponseModel, - FacilityName) + FacilityCycleDetailsResponseModel, + FacilityCyclesResponseModel, + FacilityName, +) from nsls2api.api.models.proposal_model import CycleProposalList from nsls2api.infrastructure.logging import logger from nsls2api.infrastructure.security import validate_admin_role from nsls2api.services import facility_service, proposal_service -from nsls2api.services.facility_service import (CycleNotFoundError, - CycleOperationError, - CycleUpdateError, - CycleVerificationError) +from nsls2api.services.facility_service import ( + CycleNotFoundError, + CycleOperationError, + CycleUpdateError, + CycleVerificationError, +) router = fastapi.APIRouter() diff --git a/src/nsls2api/api/v1/proposal_api.py b/src/nsls2api/api/v1/proposal_api.py index 92532e4b..e39a27d2 100644 --- a/src/nsls2api/api/v1/proposal_api.py +++ b/src/nsls2api/api/v1/proposal_api.py @@ -15,7 +15,7 @@ RecentProposalsList, SingleProposal, UsernamesList, - ProposalIdDataSessionList + ProposalIdDataSessionList, ) from nsls2api.infrastructure.logging import logger from nsls2api.infrastructure.security import get_current_user, validate_admin_role @@ -105,13 +105,14 @@ async def get_proposals( cycle: Annotated[list[str], Query()] = [], facility: Annotated[list[FacilityName], Query()] = [FacilityName.nsls2], username: str | None = Query(None, description="Filter proposals by username"), - saf_status: list[str] | None = Query(default=None, description="Filter proposals and SAFs by SAF status"), + saf_status: list[str] | None = Query( + default=None, description="Filter proposals and SAFs by SAF status" + ), page_size: int = Query(10, ge=1, le=200), page: int = Query(1, ge=1), include_directories: bool = False, ): - proposal_list = await proposal_service.fetch_proposals( proposal_id=proposal_id, beamline=beamline, @@ -123,7 +124,6 @@ async def get_proposals( page=page, include_directories=include_directories, ) - response_model = { "proposals": proposal_list, @@ -135,7 +135,6 @@ async def get_proposals( return response_model - @router.get( "/proposals/data-sessions", response_model=ProposalIdDataSessionList, @@ -156,7 +155,7 @@ async def get_proposals_data_sessions( cycle=cycle, facility=facility, page_size=page_size, - page=page + page=page, ) response_model = { diff --git a/src/nsls2api/api/v1/user_api.py b/src/nsls2api/api/v1/user_api.py index 539ed96a..e9cda89a 100644 --- a/src/nsls2api/api/v1/user_api.py +++ b/src/nsls2api/api/v1/user_api.py @@ -73,25 +73,26 @@ async def get_person_by_department(department_code: str = "PS"): # TODO: Add back into schema if we decide to use this endpoint. -@router.get("/person/me",include_in_schema=True) +@router.get("/person/me", include_in_schema=True) async def get_myself(upn: str = Header(...)): - #upn: User principal name + # upn: User principal name if not upn: - raise HTTPException(status_code=400, detail = "upn not found") + raise HTTPException(status_code=400, detail="upn not found") settings = get_settings() - ldap_info = await asyncio.to_thread(get_user_info, + ldap_info = await asyncio.to_thread( + get_user_info, upn, settings.ldap_server, settings.ldap_base_dn, settings.ldap_bind_user, - settings.ldap_bind_password + settings.ldap_bind_password, ) if not ldap_info: raise HTTPException(status_code=404, detail="User not found in LDAP") - + shaped_info = shape_ldap_response(ldap_info) return LDAPUserResponse(**shaped_info) - + @router.get("/data-session/{username}", response_model=DataSessionAccess, tags=["data"]) @router.get( diff --git a/src/nsls2api/infrastructure/config.py b/src/nsls2api/infrastructure/config.py index d6d0eb55..8e2cfd36 100644 --- a/src/nsls2api/infrastructure/config.py +++ b/src/nsls2api/infrastructure/config.py @@ -71,8 +71,10 @@ class Settings(BaseSettings): extra="ignore", ) - #Whoami LDAP settings - ldap_server: str = Field(default="ldaps://ldapproxy.nsls2.bnl.gov", alias="LDAP_SERVER") + # Whoami LDAP settings + ldap_server: str = Field( + default="ldaps://ldapproxy.nsls2.bnl.gov", alias="LDAP_SERVER" + ) ldap_base_dn: str = Field(default="dc=bnl,dc=gov", alias="LDAP_BASE_DN") ldap_bind_user: str = Field(default="", alias="LDAP_BIND_USER") ldap_bind_password: str = Field(default="", alias="LDAP_BIND_PASSWORD") diff --git a/src/nsls2api/services/beamline_service.py b/src/nsls2api/services/beamline_service.py index 18b6f5ba..aa2b22c0 100644 --- a/src/nsls2api/services/beamline_service.py +++ b/src/nsls2api/services/beamline_service.py @@ -77,7 +77,9 @@ async def all_services(name: str) -> Optional[ServicesOnly]: async def detectors(name: str) -> list[Detector]: - beamline_detectors = await Beamline.find_one(Beamline.name == name.upper()).project(DetectorView) + beamline_detectors = await Beamline.find_one(Beamline.name == name.upper()).project( + DetectorView + ) if beamline_detectors is None: raise LookupError(f"Beamline '{name.upper()}' does not exist.") return beamline_detectors.detectors diff --git a/src/nsls2api/services/ldap_service.py b/src/nsls2api/services/ldap_service.py index fcd93981..55ff1575 100644 --- a/src/nsls2api/services/ldap_service.py +++ b/src/nsls2api/services/ldap_service.py @@ -7,30 +7,33 @@ def to_hex(val): - + if isinstance(val, bytes): return binascii.hexlify(val).decode() return None + def get_user_info(upn, ldap_server, ldap_base_dn, ldap_bind_user, bind_password): - conn = None + conn = None try: server = Server(ldap_server) - conn = Connection(server, user=ldap_bind_user, password=bind_password, auto_bind=True) + conn = Connection( + server, user=ldap_bind_user, password=bind_password, auto_bind=True + ) search_filter = f"(&(objectclass=person)(userPrincipalName={upn}))" - conn.search(ldap_base_dn, search_filter, attributes=['sAMAccountName']) + conn.search(ldap_base_dn, search_filter, attributes=["sAMAccountName"]) if not conn.entries: logger.warning("No entries found for the given UPN.") return None entry = conn.entries[0] - username = entry.sAMAccountName.value if 'sAMAccountName' in entry else None + username = entry.sAMAccountName.value if "sAMAccountName" in entry else None if username is None: return None search_filter = f"(&(objectclass=posixaccount)(sAMAccountName={username}))" - conn.search(ldap_base_dn, search_filter, attributes=['*']) + conn.search(ldap_base_dn, search_filter, attributes=["*"]) if not conn.entries: logger.warning("no posix entries found for the given username.") @@ -52,36 +55,50 @@ def get_user_info(upn, ldap_server, ldap_base_dn, ldap_bind_user, bind_password) if conn is not None: conn.unbind() + def filetime_to_str(filetime): try: - if filetime is None or int(filetime) == 0 or int(filetime) == 9223372036854775807: + if ( + filetime is None + or int(filetime) == 0 + or int(filetime) == 9223372036854775807 + ): return "Never" dt = datetime(1601, 1, 1) + timedelta(microseconds=int(filetime) // 10) return dt.strftime("%Y-%m-%d %H:%M:%S UTC") except Exception: return str(filetime) + def generalized_time_to_str(gt): try: - if not gt: return "" + if not gt: + return "" dt = datetime.strptime(gt.split(".")[0], "%Y%m%d%H%M%S") return dt.strftime("%Y-%m-%d %H:%M:%S UTC") except Exception: return str(gt) + def decode_uac(uac): flags = [] try: val = int(uac) - if val & 0x0001: flags.append("SCRIPT") - if val & 0x0002: flags.append("ACCOUNTDISABLE") - if val & 0x0008: flags.append("HOMEDIR_REQUIRED") - if val & 0x0200: flags.append("NORMAL_ACCOUNT") - if val & 0x1000: flags.append("PASSWORD_EXPIRED") + if val & 0x0001: + flags.append("SCRIPT") + if val & 0x0002: + flags.append("ACCOUNTDISABLE") + if val & 0x0008: + flags.append("HOMEDIR_REQUIRED") + if val & 0x0200: + flags.append("NORMAL_ACCOUNT") + if val & 0x1000: + flags.append("PASSWORD_EXPIRED") except Exception: return [] return flags or ["NORMAL_ACCOUNT"] + def shape_ldap_response(user_info, dn=None, status="Read", read_time=None): def clean_groups(groups_val): if not groups_val: @@ -89,7 +106,9 @@ def clean_groups(groups_val): if isinstance(groups_val, list): return groups_val elif isinstance(groups_val, str): - return [g.strip() for g in groups_val.replace("\n", ",").split(",") if g.strip()] + return [ + g.strip() for g in groups_val.replace("\n", ",").split(",") if g.strip() + ] return [] return { @@ -106,8 +125,8 @@ def clean_groups(groups_val): "uidNumber": user_info.get("uidNumber"), "gidNumber": user_info.get("gidNumber"), "homeDirectory": user_info.get("homeDirectory"), - "loginShell": user_info.get("loginShell") - } + "loginShell": user_info.get("loginShell"), + }, }, "account": { "accountExpires": filetime_to_str(user_info.get("accountExpires")), @@ -142,6 +161,8 @@ def clean_groups(groups_val): "codePage": user_info.get("codePage"), "countryCode": user_info.get("countryCode"), "instanceType": user_info.get("instanceType"), - "objectClass": [s.strip() for s in user_info.get("objectClass", "").split() if s.strip()] - } - } \ No newline at end of file + "objectClass": [ + s.strip() for s in user_info.get("objectClass", "").split() if s.strip() + ], + }, + } diff --git a/src/nsls2api/services/proposal_service.py b/src/nsls2api/services/proposal_service.py index 3397ba72..8a5c57e2 100644 --- a/src/nsls2api/services/proposal_service.py +++ b/src/nsls2api/services/proposal_service.py @@ -16,7 +16,7 @@ ProposalDiagnostics, ProposalFullDetails, ProposalsToChangeList, - ProposalIdDataSession + ProposalIdDataSession, ) from nsls2api.infrastructure.logging import logger from nsls2api.models.cycles import Cycle @@ -350,12 +350,10 @@ async def fetch_proposals( page_size: int = 10, page: int = 1, include_directories: bool = False, - ) -> Optional[list[ProposalFullDetails]]: query = [] saf_status_upper: list[str] = [] - if beamline: beamline_upper = [beamline_name.upper() for beamline_name in beamline] query.append(In(Proposal.instruments, beamline_upper)) @@ -365,7 +363,7 @@ async def fetch_proposals( if proposal_id: query.append(In(Proposal.proposal_id, proposal_id)) - + if username is not None: username = username.strip() if username: @@ -373,16 +371,14 @@ async def fetch_proposals( if saf_status: saf_status_upper = [ - stripped.upper() - for s in saf_status - if (stripped := s.strip()) + stripped.upper() for s in saf_status if (stripped := s.strip()) ] query.append( ElemMatch( Proposal.safs, { "status": {"$in": saf_status_upper}, - } + }, ) ) @@ -408,9 +404,7 @@ async def fetch_proposals( filtered_proposals = [] for proposal in proposals: proposal.safs = [ - saf - for saf in (proposal.safs or []) - if saf.status in saf_status_upper + saf for saf in (proposal.safs or []) if saf.status in saf_status_upper ] if proposal.safs: filtered_proposals.append(proposal) @@ -429,6 +423,7 @@ async def fetch_proposals( else: return proposals + async def fetch_data_sessions( proposal_id: list[str] | None = None, beamline: list[str] | None = None, @@ -456,10 +451,7 @@ async def fetch_data_sessions( filter_query = And(*query) if query else {} proposals = ( - await Proposal.find_many( - filter_query, - projection_model=ProposalIdDataSession - ) + await Proposal.find_many(filter_query, projection_model=ProposalIdDataSession) .sort(-Proposal.last_updated) .limit(page_size) .skip(page_size * (page - 1)) @@ -468,6 +460,7 @@ async def fetch_data_sessions( return proposals + async def proposal_type_description_from_pass_type_id( pass_type_id: int, ) -> Optional[str]: diff --git a/src/nsls2api/tests/services/test_proposal_service.py b/src/nsls2api/tests/services/test_proposal_service.py index 45a44ff9..b4a0ccd3 100644 --- a/src/nsls2api/tests/services/test_proposal_service.py +++ b/src/nsls2api/tests/services/test_proposal_service.py @@ -191,6 +191,7 @@ async def test_data_sessions_invalid_beamline(admin_api_key): assert body["count"] == 0 assert body["proposals"] == [] + @pytest.mark.anyio async def test_fetch_proposals_filter_username_positive(): """Username filter positive - target username A vs control username B.""" @@ -199,34 +200,50 @@ async def test_fetch_proposals_filter_username_positive(): data_session="pass-1001", cycles=["2025-1"], instruments=["TEST"], - users=[User(first_name="Target", last_name="User", email="target@example.com", username="alice")], + users=[ + User( + first_name="Target", + last_name="User", + email="target@example.com", + username="alice", + ) + ], safs=[], ) await target_proposal.insert() - + control_proposal = Proposal( proposal_id="1002", data_session="pass-1002", cycles=["2025-1"], instruments=["TEST"], - users=[User(first_name="Control", last_name="User", email="control@example.com", username="bob")], + users=[ + User( + first_name="Control", + last_name="User", + email="control@example.com", + username="bob", + ) + ], safs=[], ) await control_proposal.insert() - + results = await proposal_service.fetch_proposals(username="alice") - + result_ids = {p.proposal_id for p in results} assert "1001" in result_ids assert "1002" not in result_ids + @pytest.mark.anyio async def test_fetch_proposals_filter_username_negative(): """Username filter negative - nonexistent username returns empty.""" results = await proposal_service.fetch_proposals(username="nonexistent_user_xyz") - + assert len(results) == 0 + @pytest.mark.anyio async def test_fetch_proposals_filter_saf_status_positive(): """SAF status positive - APPROVED proposal vs EXPIRED proposal.""" @@ -239,7 +256,7 @@ async def test_fetch_proposals_filter_saf_status_positive(): safs=[SafetyForm(saf_id="SAF001", status="APPROVED", instruments=["TEST"])], ) await target_proposal.insert() - + control_proposal = Proposal( proposal_id="2002", data_session="pass-2002", @@ -249,13 +266,14 @@ async def test_fetch_proposals_filter_saf_status_positive(): safs=[SafetyForm(saf_id="SAF002", status="EXPIRED", instruments=["TEST"])], ) await control_proposal.insert() - + results = await proposal_service.fetch_proposals(saf_status=["APPROVED"]) - + result_ids = {p.proposal_id for p in results} assert "2001" in result_ids assert "2002" not in result_ids + @pytest.mark.anyio async def test_fetch_proposals_filter_saf_status_multiple(): """SAF status multiple - find only proposals and SAFs with specified statuses.""" @@ -272,7 +290,7 @@ async def test_fetch_proposals_filter_saf_status_multiple(): ], ) await target_proposal.insert() - + control_proposal = Proposal( proposal_id="3002", data_session="pass-3002", @@ -286,9 +304,9 @@ async def test_fetch_proposals_filter_saf_status_multiple(): ], ) await control_proposal.insert() - + results = await proposal_service.fetch_proposals(saf_status=["APPROVED", "DRAFT"]) - + result_ids = {p.proposal_id for p in results} assert "3001" in result_ids assert "3002" not in result_ids @@ -299,6 +317,7 @@ async def test_fetch_proposals_filter_saf_status_multiple(): assert "DRAFT" in saf_statuses assert "EXPIRED" not in saf_statuses + @pytest.mark.anyio async def test_fetch_proposals_filter_combined_all_filters(): """Combined filters - username + cycle + beamline + saf_status all required.""" @@ -307,58 +326,93 @@ async def test_fetch_proposals_filter_combined_all_filters(): data_session="pass-4001", cycles=["2025-combined"], instruments=["COMBO-BL"], - users=[User(first_name="Test", last_name="User", email="test@example.com", username="combo_user")], + users=[ + User( + first_name="Test", + last_name="User", + email="test@example.com", + username="combo_user", + ) + ], safs=[SafetyForm(saf_id="SAF-C1", status="APPROVED", instruments=["COMBO-BL"])], ) await matching.insert() - + nonmatching_user = Proposal( proposal_id="4002", data_session="pass-4002", cycles=["2025-combined"], instruments=["COMBO-BL"], - users=[User(first_name="Other", last_name="User", email="other@example.com", username="other_user")], + users=[ + User( + first_name="Other", + last_name="User", + email="other@example.com", + username="other_user", + ) + ], safs=[SafetyForm(saf_id="SAF-C2", status="APPROVED", instruments=["COMBO-BL"])], ) await nonmatching_user.insert() - + nonmatching_cycle = Proposal( proposal_id="4003", data_session="pass-4003", cycles=["2025-other"], instruments=["COMBO-BL"], - users=[User(first_name="Test", last_name="User", email="test@example.com", username="combo_user")], + users=[ + User( + first_name="Test", + last_name="User", + email="test@example.com", + username="combo_user", + ) + ], safs=[SafetyForm(saf_id="SAF-C3", status="APPROVED", instruments=["COMBO-BL"])], ) await nonmatching_cycle.insert() - + nonmatching_beamline = Proposal( proposal_id="4004", data_session="pass-4004", cycles=["2025-combined"], instruments=["OTHER-BL"], - users=[User(first_name="Test", last_name="User", email="test@example.com", username="combo_user")], + users=[ + User( + first_name="Test", + last_name="User", + email="test@example.com", + username="combo_user", + ) + ], safs=[SafetyForm(saf_id="SAF-C4", status="APPROVED", instruments=["OTHER-BL"])], ) await nonmatching_beamline.insert() - + nonmatching_saf_status = Proposal( proposal_id="4005", data_session="pass-4005", cycles=["2025-combined"], instruments=["COMBO-BL"], - users=[User(first_name="Test", last_name="User", email="test@example.com", username="combo_user")], + users=[ + User( + first_name="Test", + last_name="User", + email="test@example.com", + username="combo_user", + ) + ], safs=[SafetyForm(saf_id="SAF-C5", status="DRAFT", instruments=["COMBO-BL"])], ) await nonmatching_saf_status.insert() - + results = await proposal_service.fetch_proposals( username="combo_user", cycle=["2025-combined"], beamline=["COMBO-BL"], saf_status=["APPROVED"], ) - + result_ids = {p.proposal_id for p in results} assert "4001" in result_ids assert "4002" not in result_ids From 242b22eebd1e16c1ddceaaba085e2b9752e3fff6 Mon Sep 17 00:00:00 2001 From: Padraic Shafer Date: Sat, 8 Aug 2026 10:16:45 -0700 Subject: [PATCH 2/4] Apply ruff check auto-fix --- src/nsls2api/api/models/facility_model.py | 3 +- src/nsls2api/api/models/person_model.py | 209 +++++++++--------- src/nsls2api/api/models/proposal_model.py | 23 +- src/nsls2api/api/models/stats_model.py | 5 +- src/nsls2api/api/v1/admin_api.py | 6 +- src/nsls2api/api/v1/jobs_api.py | 3 +- src/nsls2api/api/v1/proposal_api.py | 4 +- src/nsls2api/api/v1/user_api.py | 2 +- src/nsls2api/cli/auth.py | 3 +- src/nsls2api/cli/cli.py | 5 +- src/nsls2api/cli/settings.py | 9 +- src/nsls2api/cli/utils/api.py | 3 +- src/nsls2api/exception_handlers.py | 1 - src/nsls2api/infrastructure/config.py | 4 +- src/nsls2api/infrastructure/mongodb_setup.py | 1 - src/nsls2api/infrastructure/security.py | 8 +- src/nsls2api/models/apikeys.py | 7 +- src/nsls2api/models/beamlines.py | 61 +++-- src/nsls2api/models/cycles.py | 17 +- src/nsls2api/models/facilities.py | 7 +- src/nsls2api/models/jobs.py | 23 +- src/nsls2api/models/pass_models.py | 193 ++++++++-------- src/nsls2api/models/proposal_types.py | 7 +- src/nsls2api/models/proposals.py | 29 ++- src/nsls2api/scratchpad_main.py | 4 +- src/nsls2api/services/background_service.py | 9 +- src/nsls2api/services/beamline_service.py | 21 +- src/nsls2api/services/bnlpeople_service.py | 11 +- src/nsls2api/services/facility_service.py | 29 ++- src/nsls2api/services/n2sn_service.py | 9 +- src/nsls2api/services/pass_service.py | 21 +- src/nsls2api/services/person_service.py | 5 +- src/nsls2api/services/proposal_service.py | 39 ++-- src/nsls2api/services/slack_service.py | 7 +- .../tests/services/test_proposal_service.py | 3 +- .../diagnostics/proposal_viewmodel.py | 5 +- .../viewmodels/diagnostics/user_viewmodel.py | 5 +- .../viewmodels/proposals/details_viewmodel.py | 3 +- .../viewmodels/proposals/search_viewmodel.py | 3 +- .../viewmodels/shared/viewmodelbase.py | 3 +- src/nsls2api/views/diagnostics.py | 2 +- 41 files changed, 389 insertions(+), 423 deletions(-) diff --git a/src/nsls2api/api/models/facility_model.py b/src/nsls2api/api/models/facility_model.py index 0302f0f0..6fb5c8e8 100644 --- a/src/nsls2api/api/models/facility_model.py +++ b/src/nsls2api/api/models/facility_model.py @@ -1,5 +1,6 @@ -from enum import StrEnum from datetime import datetime +from enum import StrEnum + import pydantic diff --git a/src/nsls2api/api/models/person_model.py b/src/nsls2api/api/models/person_model.py index aab9dbcd..2676cfc7 100644 --- a/src/nsls2api/api/models/person_model.py +++ b/src/nsls2api/api/models/person_model.py @@ -1,62 +1,61 @@ import datetime -from typing import List, Optional import pydantic class BNLPerson(pydantic.BaseModel): - ActiveDirectoryName: Optional[str] = None - AltEmail: Optional[str] = None - AppointmentEndDate: Optional[str] = None - BNLEmail: Optional[str] = None - BNLExtension: Optional[str] = None - BNLFax: Optional[str] = None - BNLPager: Optional[str] = None - BNLStreet: Optional[str] = None - IsUSCitizen: Optional[bool] = None - CyberAgreementSigned: Optional[str] = None - DeliveryOffice: Optional[str] = None - DepartmentCode: Optional[str] = None - DepartmentId: Optional[int] = None - DepartmentName: Optional[str] = None - DisplayContactInformation: Optional[bool] = None - EmployeeNumber: Optional[str] = None - EmployeeStatus: Optional[str] = None - EmployeeType: Optional[str] = None - Facility: Optional[str] = None - FacilityCode: Optional[str] = None - FirstName: Optional[str] = None - Institution: Optional[str] = None - LastName: Optional[str] = None - ManagerEmail: Optional[str] = None - ManagerEmployeeNumber: Optional[str] = None - ManagerFirstName: Optional[str] = None - ManagerLastName: Optional[str] = None - TermDate: Optional[str] = None - TimeStamp: Optional[str] = None + ActiveDirectoryName: str | None = None + AltEmail: str | None = None + AppointmentEndDate: str | None = None + BNLEmail: str | None = None + BNLExtension: str | None = None + BNLFax: str | None = None + BNLPager: str | None = None + BNLStreet: str | None = None + IsUSCitizen: bool | None = None + CyberAgreementSigned: str | None = None + DeliveryOffice: str | None = None + DepartmentCode: str | None = None + DepartmentId: int | None = None + DepartmentName: str | None = None + DisplayContactInformation: bool | None = None + EmployeeNumber: str | None = None + EmployeeStatus: str | None = None + EmployeeType: str | None = None + Facility: str | None = None + FacilityCode: str | None = None + FirstName: str | None = None + Institution: str | None = None + LastName: str | None = None + ManagerEmail: str | None = None + ManagerEmployeeNumber: str | None = None + ManagerFirstName: str | None = None + ManagerLastName: str | None = None + TermDate: str | None = None + TimeStamp: str | None = None class ActiveDirectoryUser(pydantic.BaseModel): - sAMAccountName: Optional[str] = None - distinguishedName: Optional[str] = None - displayName: Optional[str] = None - employeeID: Optional[str] = None - mail: Optional[str] = None - description: Optional[str] = None - userPrincipalName: Optional[str] = None - pwdLastSet: Optional[str] = None - userAccountControl: Optional[str] = None - lockoutTime: Optional[str] = None - set_passwd: Optional[bool] = None - locked: Optional[bool] = None - was_locked: Optional[bool] = None + sAMAccountName: str | None = None + distinguishedName: str | None = None + displayName: str | None = None + employeeID: str | None = None + mail: str | None = None + description: str | None = None + userPrincipalName: str | None = None + pwdLastSet: str | None = None + userAccountControl: str | None = None + lockoutTime: str | None = None + set_passwd: bool | None = None + locked: bool | None = None + was_locked: bool | None = None class ActiveDirectoryUserGroups(pydantic.BaseModel): - sAMAccountName: Optional[str] = None - distinguishedName: Optional[str] = None - member: Optional[list[str]] = None - memberOf: Optional[list[str]] = None + sAMAccountName: str | None = None + distinguishedName: str | None = None + member: list[str] | None = None + memberOf: list[str] | None = None class Person(pydantic.BaseModel): @@ -64,17 +63,17 @@ class Person(pydantic.BaseModel): lastname: str email: str username: str - bnl_id: Optional[str] - bnl_employee: Optional[bool] = None - institution: Optional[str] = None - orcid: Optional[str] = None - globus_username: Optional[str] = None - pass_unique_id: Optional[str] = None - account_locked: Optional[bool] = None - cyber_agreement_signed: Optional[datetime.datetime] = None - facility_code: Optional[str] = None - facility_name: Optional[str] = None - citizenship: Optional[str] = None + bnl_id: str | None + bnl_employee: bool | None = None + institution: str | None = None + orcid: str | None = None + globus_username: str | None = None + pass_unique_id: str | None = None + account_locked: bool | None = None + cyber_agreement_signed: datetime.datetime | None = None + facility_code: str | None = None + facility_name: str | None = None + citizenship: str | None = None class PersonSummary(pydantic.BaseModel): @@ -88,76 +87,76 @@ class PersonSummary(pydantic.BaseModel): class DataAdmins(pydantic.BaseModel): nsls2_dataadmin: bool = False lbms_dataadmin: bool = False - dataadmin: Optional[list] = None + dataadmin: list | None = None class DataSessionAccess(pydantic.BaseModel): - facility_all_access: List[str] = None - beamline_all_access: List[str] = None - data_sessions: List[str] = None + facility_all_access: list[str] = None + beamline_all_access: list[str] = None + data_sessions: list[str] = None class UnixInfo(pydantic.BaseModel): - uid: Optional[str] = None - uidNumber: Optional[str] = None - gidNumber: Optional[str] = None - homeDirectory: Optional[str] = None - loginShell: Optional[str] = None + uid: str | None = None + uidNumber: str | None = None + gidNumber: str | None = None + homeDirectory: str | None = None + loginShell: str | None = None class IdentityInfo(pydantic.BaseModel): - displayName: Optional[str] = None - email: Optional[str] = None - department: Optional[str] = None - manager: Optional[str] = None - unix: Optional[UnixInfo] = None + displayName: str | None = None + email: str | None = None + department: str | None = None + manager: str | None = None + unix: UnixInfo | None = None class AccountInfo(pydantic.BaseModel): - accountExpires: Optional[str] = None - badPasswordTime: Optional[str] = None + accountExpires: str | None = None + badPasswordTime: str | None = None badPwdCount: int = 0 - pwdLastSet: Optional[str] = None - lastLogon: Optional[str] = None - userAccountControlFlags: List[str] = pydantic.Field(default_factory=list) - userPrincipalName: Optional[str] = None + pwdLastSet: str | None = None + lastLogon: str | None = None + userAccountControlFlags: list[str] = pydantic.Field(default_factory=list) + userPrincipalName: str | None = None logonCount: int = 0 - sAMAccountName: Optional[str] = None - sAMAccountType: Optional[str] = None - lastLogoff: Optional[str] = None - uSNCreated: Optional[int] = None - uSNChanged: Optional[int] = None + sAMAccountName: str | None = None + sAMAccountType: str | None = None + lastLogoff: str | None = None + uSNCreated: int | None = None + uSNChanged: int | None = None class DirectoryInfo(pydantic.BaseModel): - objectGUID: Optional[str] = None - objectSid: Optional[str] = None - primaryGroupID: Optional[str] = None - distinguishedName: Optional[str] = None - whenCreated: Optional[str] = None - whenChanged: Optional[str] = None + objectGUID: str | None = None + objectSid: str | None = None + primaryGroupID: str | None = None + distinguishedName: str | None = None + whenCreated: str | None = None + whenChanged: str | None = None class AttributesInfo(pydantic.BaseModel): - sn: Optional[str] = None - givenName: Optional[str] = None - description: Optional[str] = None - gecos: Optional[str] = None - street: Optional[str] = None - codePage: Optional[str] = None - countryCode: Optional[str] = None - instanceType: Optional[str] = None - objectClass: List[str] = pydantic.Field(default_factory=list) + sn: str | None = None + givenName: str | None = None + description: str | None = None + gecos: str | None = None + street: str | None = None + codePage: str | None = None + countryCode: str | None = None + instanceType: str | None = None + objectClass: list[str] = pydantic.Field(default_factory=list) class LDAPUserResponse(pydantic.BaseModel): """Complete LDAP user data from direct LDAP query""" - dn: Optional[str] = None + dn: str | None = None status: str = "Read" - readTime: Optional[str] = None - identity: Optional[IdentityInfo] = None - account: Optional[AccountInfo] = None - directory: Optional[DirectoryInfo] = None - groups: List[str] = pydantic.Field(default_factory=list) - attributes: Optional[AttributesInfo] = None + readTime: str | None = None + identity: IdentityInfo | None = None + account: AccountInfo | None = None + directory: DirectoryInfo | None = None + groups: list[str] = pydantic.Field(default_factory=list) + attributes: AttributesInfo | None = None diff --git a/src/nsls2api/api/models/proposal_model.py b/src/nsls2api/api/models/proposal_model.py index 7285edae..bf242b16 100644 --- a/src/nsls2api/api/models/proposal_model.py +++ b/src/nsls2api/api/models/proposal_model.py @@ -1,5 +1,4 @@ import datetime -from typing import Optional import pydantic @@ -11,7 +10,7 @@ class UsernamesList(pydantic.BaseModel): usernames: list[str] groupname: str - proposal_id: Optional[str] + proposal_id: str | None count: int model_config = { @@ -52,7 +51,7 @@ class RecentProposal(pydantic.BaseModel): proposal_id: str title: str updated: datetime.datetime - instruments: Optional[list[str]] + instruments: list[str] | None class RecentProposalsList(pydantic.BaseModel): @@ -134,21 +133,21 @@ class ProposalFullDetailsList(pydantic.BaseModel): class ProposalDiagnostics(pydantic.BaseModel): proposal_id: str - proposal_type: Optional[str] - pi: Optional[User] - users: Optional[list[User]] + proposal_type: str | None + pi: User | None + users: list[User] | None title: str - data_session: Optional[str] - beamlines: Optional[list[str]] - cycles: Optional[list[str]] - safs: Optional[list[str]] + data_session: str | None + beamlines: list[str] | None + cycles: list[str] | None + safs: list[str] | None updated: datetime.datetime class ProposalChangeResultsList(pydantic.BaseModel): successful_count: int - successful_proposals: Optional[list[str]] - failed_proposals: Optional[list[str]] + successful_proposals: list[str] | None + failed_proposals: list[str] | None class ProposalsToChangeList(pydantic.BaseModel): diff --git a/src/nsls2api/api/models/stats_model.py b/src/nsls2api/api/models/stats_model.py index 5f1ea300..89aec73f 100644 --- a/src/nsls2api/api/models/stats_model.py +++ b/src/nsls2api/api/models/stats_model.py @@ -1,4 +1,3 @@ -from typing import Optional import pydantic @@ -15,8 +14,8 @@ class StatsModel(pydantic.BaseModel): commissioning_proposal_count: int nsls2_data_health: bool lbms_data_health: bool - nsls2_proposals_per_cycle: Optional[list[ProposalsPerCycleModel]] - lbms_proposals_per_cycle: Optional[list[ProposalsPerCycleModel]] + nsls2_proposals_per_cycle: list[ProposalsPerCycleModel] | None + lbms_proposals_per_cycle: list[ProposalsPerCycleModel] | None class AboutModel(pydantic.BaseModel): diff --git a/src/nsls2api/api/v1/admin_api.py b/src/nsls2api/api/v1/admin_api.py index 688e1fc9..1d18a16a 100644 --- a/src/nsls2api/api/v1/admin_api.py +++ b/src/nsls2api/api/v1/admin_api.py @@ -1,4 +1,4 @@ -from typing import Annotated, Optional +from typing import Annotated import fastapi from fastapi import Depends, HTTPException, Query @@ -66,7 +66,7 @@ async def generate_user_apikey(username: str, usertype: ApiUserType = ApiUserTyp @router.post("/admin/proposal/generate-test") async def generate_fake_proposal( add_specific_user: str | None = None, -) -> Optional[SingleProposal]: +) -> SingleProposal | None: proposal = await proposal_service.generate_fake_test_proposal( FacilityName.nsls2, add_specific_user ) @@ -97,7 +97,7 @@ async def update_user_role(username: str, role: ApiUserRole) -> ApiUserResponseM ) user.role = role - await user.save() # noqa + await user.save() response = ApiUserResponseModel( id=user.id, diff --git a/src/nsls2api/api/v1/jobs_api.py b/src/nsls2api/api/v1/jobs_api.py index 2e27006b..e5abb722 100644 --- a/src/nsls2api/api/v1/jobs_api.py +++ b/src/nsls2api/api/v1/jobs_api.py @@ -1,4 +1,3 @@ -from typing import Optional import bson import fastapi @@ -155,7 +154,7 @@ async def sync_cycles(facility: FacilityName = FacilityName.nsls2): async def sync_update_cycles( request: fastapi.Request, facility: FacilityName = FacilityName.nsls2, - cycle: Optional[str] = None, + cycle: str | None = None, ): sync_params = JobSyncParameters(facility=facility, sync_source=JobSyncSource.PASS) diff --git a/src/nsls2api/api/v1/proposal_api.py b/src/nsls2api/api/v1/proposal_api.py index e39a27d2..fae7fa48 100644 --- a/src/nsls2api/api/v1/proposal_api.py +++ b/src/nsls2api/api/v1/proposal_api.py @@ -9,13 +9,13 @@ CommissioningProposalsList, ProposalDirectoriesList, ProposalFullDetailsList, + ProposalIdDataSessionList, ProposalUser, ProposalUserList, RecentProposal, RecentProposalsList, SingleProposal, UsernamesList, - ProposalIdDataSessionList, ) from nsls2api.infrastructure.logging import logger from nsls2api.infrastructure.security import get_current_user, validate_admin_role @@ -382,6 +382,6 @@ async def create_slack_channels_for_proposal( ] proposal.slack_channels = slack_channels proposal.last_updated = datetime.datetime.now() - await proposal.save() # noqa - we don't need to specify any args here + await proposal.save() return channels diff --git a/src/nsls2api/api/v1/user_api.py b/src/nsls2api/api/v1/user_api.py index e9cda89a..39f54085 100644 --- a/src/nsls2api/api/v1/user_api.py +++ b/src/nsls2api/api/v1/user_api.py @@ -1,7 +1,7 @@ import asyncio import fastapi -from fastapi import HTTPException, Request, Header +from fastapi import Header, HTTPException from nsls2api.api.models.person_model import DataSessionAccess, LDAPUserResponse, Person from nsls2api.infrastructure.security import get_settings diff --git a/src/nsls2api/cli/auth.py b/src/nsls2api/cli/auth.py index a8d47030..da8c2b3d 100644 --- a/src/nsls2api/cli/auth.py +++ b/src/nsls2api/cli/auth.py @@ -1,5 +1,4 @@ import getpass -from typing import Optional, Tuple import httpx import typer @@ -19,7 +18,7 @@ def auth_callback(ctx: typer.Context): pass # No need to call anything manually -def verify_token(token: str) -> Tuple[bool, Optional[str]]: +def verify_token(token: str) -> tuple[bool, str | None]: """ Verify if a token is valid by making an API call. Returns a tuple of (is_valid, username or error_message) diff --git a/src/nsls2api/cli/cli.py b/src/nsls2api/cli/cli.py index 157a6408..3f3096e6 100644 --- a/src/nsls2api/cli/cli.py +++ b/src/nsls2api/cli/cli.py @@ -1,5 +1,4 @@ import sys -from typing import Optional import typer from rich import box @@ -118,7 +117,7 @@ def show_usage_tips(): @app.callback() def main( ctx: typer.Context, - version: Optional[bool] = typer.Option( + version: bool | None = typer.Option( None, "--version", "-V", @@ -147,7 +146,7 @@ def run(): try: app() except Exception as e: - console.print(f"[error]Error: {str(e)}") + console.print(f"[error]Error: {e!s}") sys.exit(1) diff --git a/src/nsls2api/cli/settings.py b/src/nsls2api/cli/settings.py index 47af9efa..f0c2d7aa 100644 --- a/src/nsls2api/cli/settings.py +++ b/src/nsls2api/cli/settings.py @@ -2,7 +2,7 @@ import os from enum import Enum from pathlib import Path -from typing import Any, Optional +from typing import Any class ApiEnvironment(str, Enum): @@ -36,7 +36,7 @@ def read(cls) -> configparser.ConfigParser: return config @classmethod - def get_value(cls, section: str, key: str) -> Optional[str]: + def get_value(cls, section: str, key: str) -> str | None: """Get a value from the configuration""" try: config = cls.read() @@ -86,7 +86,7 @@ def get_base_url() -> str: return url if url else ApiEnvironment.PRODUCTION.value -def get_token() -> Optional[str]: +def get_token() -> str | None: """Get the API token""" return Config.get_value("api", ConfigKey.TOKEN) @@ -103,5 +103,4 @@ def remove_token() -> None: class ConfigError(Exception): """Configuration related errors""" - - pass + # No additional methods or attributes are needed for this class. diff --git a/src/nsls2api/cli/utils/api.py b/src/nsls2api/cli/utils/api.py index 74475f13..cc5bcf71 100644 --- a/src/nsls2api/cli/utils/api.py +++ b/src/nsls2api/cli/utils/api.py @@ -1,4 +1,3 @@ -from typing import Optional import httpx from rich.panel import Panel @@ -9,7 +8,7 @@ def call_nsls2api_endpoint( endpoint: str, method: str = "GET", data: dict = None -) -> Optional[httpx.Response]: +) -> httpx.Response | None: """ Call the NSLS-II API endpoint and return the response. """ diff --git a/src/nsls2api/exception_handlers.py b/src/nsls2api/exception_handlers.py index 53434d02..fa82f809 100644 --- a/src/nsls2api/exception_handlers.py +++ b/src/nsls2api/exception_handlers.py @@ -1,7 +1,6 @@ from asgi_correlation_id import correlation_id from fastapi import HTTPException, Request from fastapi.exception_handlers import http_exception_handler -from fastapi.responses import JSONResponse from nsls2api.main import app diff --git a/src/nsls2api/infrastructure/config.py b/src/nsls2api/infrastructure/config.py index 8e2cfd36..684bb621 100644 --- a/src/nsls2api/infrastructure/config.py +++ b/src/nsls2api/infrastructure/config.py @@ -80,7 +80,7 @@ class Settings(BaseSettings): ldap_bind_password: str = Field(default="", alias="LDAP_BIND_PASSWORD") -@lru_cache() +@lru_cache def get_settings() -> Settings: """ Retrieve the settings dictionary. @@ -88,7 +88,7 @@ def get_settings() -> Settings: :returns: The dictionary of current settings. """ - logger.info(f"Settings file: {str(Path(__file__).parent.parent / '.env')}") + logger.info(f"Settings file: {Path(__file__).parent.parent / '.env'!s}") if os.environ.get("PYTEST_VERSION") is not None: PROJ_SRC_PATH = Path(__file__).parent.parent diff --git a/src/nsls2api/infrastructure/mongodb_setup.py b/src/nsls2api/infrastructure/mongodb_setup.py index a2cabd31..aa27e68e 100644 --- a/src/nsls2api/infrastructure/mongodb_setup.py +++ b/src/nsls2api/infrastructure/mongodb_setup.py @@ -1,4 +1,3 @@ -import asyncio import beanie import click diff --git a/src/nsls2api/infrastructure/security.py b/src/nsls2api/infrastructure/security.py index 11b2bf1c..74d9ee3c 100644 --- a/src/nsls2api/infrastructure/security.py +++ b/src/nsls2api/infrastructure/security.py @@ -2,7 +2,6 @@ import datetime import enum import secrets -from typing import Optional from beanie import Link, WriteRules from fastapi import Depends, HTTPException, Request, Security, status @@ -115,7 +114,7 @@ async def lookup_api_key(token: str) -> ApiKey: return apikey -async def verify_api_key(token: str) -> Optional[ApiKey]: +async def verify_api_key(token: str) -> ApiKey | None: """ Verifies the validity of an API key. @@ -165,7 +164,7 @@ async def validate_admin_role( request: Request, api_key: str = Depends(get_api_key), settings: BaseSettings = Depends(get_settings), -) -> Optional[Link[ApiUser]]: +) -> Link[ApiUser] | None: if api_key is not None: try: valid_key = await verify_api_key(api_key) @@ -230,8 +229,7 @@ def default_apikey_expiration(months: int = 6) -> datetime.date: # Calculate the day day = date_now.day last_day_of_month = calendar.monthrange(year, month)[1] - if day > last_day_of_month: - day = last_day_of_month + day = min(day, last_day_of_month) new_date = datetime.date(year, month, day) return new_date diff --git a/src/nsls2api/models/apikeys.py b/src/nsls2api/models/apikeys.py index 0321aa9f..cffca9cf 100644 --- a/src/nsls2api/models/apikeys.py +++ b/src/nsls2api/models/apikeys.py @@ -1,6 +1,5 @@ import datetime from enum import StrEnum -from typing import Optional from uuid import UUID, uuid4 import beanie @@ -34,7 +33,7 @@ class ApiUser(beanie.Document): id: UUID = Field(default_factory=uuid4) username: str type: ApiUserType - role: Optional[ApiUserRole] = ApiUserRole.user + role: ApiUserRole | None = ApiUserRole.user created_on: datetime.datetime = pydantic.Field( default_factory=datetime.datetime.now ) @@ -72,10 +71,10 @@ class ApiKey(beanie.Document): username: str first_eight: pydantic.constr(min_length=8, max_length=8) hashed_key: str - note: Optional[str] = "" + note: str | None = "" # scopes: Optional[list[str]] = pydantic.Field(..., example=["inherit"]) valid: bool = True - expires_after: Optional[datetime.datetime] = None + expires_after: datetime.datetime | None = None created_on: datetime.datetime = pydantic.Field( default_factory=datetime.datetime.now ) diff --git a/src/nsls2api/models/beamlines.py b/src/nsls2api/models/beamlines.py index d1454181..8a5591c2 100644 --- a/src/nsls2api/models/beamlines.py +++ b/src/nsls2api/models/beamlines.py @@ -1,6 +1,5 @@ import datetime from enum import StrEnum -from typing import Optional import beanie import pydantic @@ -95,10 +94,10 @@ class DetectorList(pydantic.BaseModel): class BeamlineService(pydantic.BaseModel): name: str - used_in_production: Optional[bool] = None - host: Optional[str] = None - port: Optional[int] = None - uri: Optional[str] = None + used_in_production: bool | None = None + host: str | None = None + port: int | None = None + uri: str | None = None class ServicesOnly(pydantic.BaseModel): @@ -111,19 +110,19 @@ class Settings: class ServiceAccounts(pydantic.BaseModel): - ioc: Optional[str] - workflow: Optional[str] - bluesky: Optional[str] - epics_services: Optional[str] - operator: Optional[str] - lsdc: Optional[str] = None + ioc: str | None + workflow: str | None + bluesky: str | None + epics_services: str | None + operator: str | None + lsdc: str | None = None class Settings: keep_nulls = False class ServiceAccountsView(pydantic.BaseModel): - service_accounts: Optional[ServiceAccounts] + service_accounts: ServiceAccounts | None class WorkflowServiceAccountView(pydantic.BaseModel): @@ -176,7 +175,7 @@ class Settings: class DataRootDirectoryView(pydantic.BaseModel): - data_root: Optional[str] = None + data_root: str | None = None class Settings: projection = {"data_root": "$custom_root_directory"} @@ -190,7 +189,7 @@ class Settings: class SlackBeamlineBotUserIdView(pydantic.BaseModel): - slack_beamline_bot_user_id: Optional[str] = None + slack_beamline_bot_user_id: str | None = None class Settings: projection = {"slack_beamline_bot_user_id": "$slack_beamline_bot_user_id"} @@ -198,29 +197,29 @@ class Settings: class EndStation(pydantic.BaseModel): name: str - service_accounts: Optional[ServiceAccounts] = None + service_accounts: ServiceAccounts | None = None class Beamline(beanie.Document): name: str - long_name: Optional[str] - alternative_name: Optional[str] + long_name: str | None + alternative_name: str | None port: str - network_locations: Optional[list[str]] = [] - pass_name: Optional[str] - pass_id: Optional[str] - nsls2_redhat_satellite_location_name: Optional[list[str]] = [] + network_locations: list[str] | None = [] + pass_name: str | None + pass_id: str | None + nsls2_redhat_satellite_location_name: list[str] | None = [] service_accounts: ServiceAccounts | None = None - endstations: Optional[list[EndStation]] = [] - slack_channel_managers: Optional[list[str]] = [] - slack_beamline_bot_user_id: Optional[str] = None - slack_autocreate_channels: Optional[bool] = False - data_admins: Optional[list[str]] = [] - custom_data_admin_group: Optional[str] = None - github_org: Optional[str] = None - data_root: Optional[str] = None - services: Optional[list[BeamlineService]] = [] - detectors: Optional[list[Detector]] = [] + endstations: list[EndStation] | None = [] + slack_channel_managers: list[str] | None = [] + slack_beamline_bot_user_id: str | None = None + slack_autocreate_channels: bool | None = False + data_admins: list[str] | None = [] + custom_data_admin_group: str | None = None + github_org: str | None = None + data_root: str | None = None + services: list[BeamlineService] | None = [] + detectors: list[Detector] | None = [] created_on: datetime.datetime = pydantic.Field( default_factory=datetime.datetime.now ) diff --git a/src/nsls2api/models/cycles.py b/src/nsls2api/models/cycles.py index 566b0c4b..04cffa77 100644 --- a/src/nsls2api/models/cycles.py +++ b/src/nsls2api/models/cycles.py @@ -1,5 +1,4 @@ import datetime -from typing import Optional import beanie import pydantic @@ -7,16 +6,16 @@ class Cycle(beanie.Document): name: str - accepting_proposals: Optional[bool] = False - is_current_operating_cycle: Optional[bool] = False - active: Optional[bool] = False - end_date: Optional[datetime.datetime] + accepting_proposals: bool | None = False + is_current_operating_cycle: bool | None = False + active: bool | None = False + end_date: datetime.datetime | None facility: str - pass_description: Optional[str] - pass_id: Optional[str] - start_date: Optional[datetime.datetime] + pass_description: str | None + pass_id: str | None + start_date: datetime.datetime | None year: str - proposals: Optional[list[str]] = [] + proposals: list[str] | None = [] created_on: datetime.datetime = pydantic.Field( default_factory=datetime.datetime.now ) diff --git a/src/nsls2api/models/facilities.py b/src/nsls2api/models/facilities.py index 90e15d30..64e2665d 100644 --- a/src/nsls2api/models/facilities.py +++ b/src/nsls2api/models/facilities.py @@ -1,5 +1,4 @@ import datetime -from typing import Optional import beanie import pydantic @@ -9,9 +8,9 @@ class Facility(beanie.Document): name: str facility_id: str fullname: str - pass_facility_id: Optional[str] = None - data_admins: Optional[list[str]] = [] - data_admin_group: Optional[str] = None + pass_facility_id: str | None = None + data_admins: list[str] | None = [] + data_admin_group: str | None = None created_on: datetime.datetime = pydantic.Field( default_factory=datetime.datetime.now ) diff --git a/src/nsls2api/models/jobs.py b/src/nsls2api/models/jobs.py index e0d20015..84d944fc 100644 --- a/src/nsls2api/models/jobs.py +++ b/src/nsls2api/models/jobs.py @@ -1,6 +1,5 @@ import datetime from enum import StrEnum -from typing import Optional import beanie import pydantic @@ -35,26 +34,26 @@ class JobActions(StrEnum): class JobSyncParameters(pydantic.BaseModel): - proposal_id: Optional[str] = None - facility: Optional[FacilityName] = None - year: Optional[int] = None - cycle: Optional[str] = None - proposal_type_id: Optional[str] = None - beamline: Optional[str] = None - sync_source: Optional[JobSyncSource] = JobSyncSource.PASS + proposal_id: str | None = None + facility: FacilityName | None = None + year: int | None = None + cycle: str | None = None + proposal_type_id: str | None = None + beamline: str | None = None + sync_source: JobSyncSource | None = JobSyncSource.PASS class BackgroundJob(beanie.Document): created_date: datetime.datetime = pydantic.Field( default_factory=datetime.datetime.now ) - started_date: Optional[datetime.datetime] = None - finished_date: Optional[datetime.datetime] = None + started_date: datetime.datetime | None = None + finished_date: datetime.datetime | None = None processing_status: str = JobStatus.awaiting is_finished: bool = False action: str - sync_parameters: Optional[JobSyncParameters] = None - log_message: Optional[str] = None + sync_parameters: JobSyncParameters | None = None + log_message: str | None = None class Settings: name = "jobs" diff --git a/src/nsls2api/models/pass_models.py b/src/nsls2api/models/pass_models.py index 448410b9..831c7ced 100644 --- a/src/nsls2api/models/pass_models.py +++ b/src/nsls2api/models/pass_models.py @@ -1,4 +1,3 @@ -from typing import Optional import pydantic from pydantic import ConfigDict @@ -11,20 +10,20 @@ class PassPerson(pydantic.BaseModel): model_config = ConfigDict(str_strip_whitespace=True) - Can_Edit: Optional[bool] = None - Can_Read: Optional[bool] = None - CoPI: Optional[bool] = None - On_Site: Optional[bool] = None - Pool_ID: Optional[int] = None - Proposal_ID: Optional[int] = None - User_ID: Optional[int] = None - Account: Optional[str] = None - BNL_ID: Optional[str] = None - Email: Optional[str] = None - First_Name: Optional[str] = None - Last_Name: Optional[str] = None - User_Facility_ID: Optional[str] = None - ORCID_ID: Optional[str] = None + Can_Edit: bool | None = None + Can_Read: bool | None = None + CoPI: bool | None = None + On_Site: bool | None = None + Pool_ID: int | None = None + Proposal_ID: int | None = None + User_ID: int | None = None + Account: str | None = None + BNL_ID: str | None = None + Email: str | None = None + First_Name: str | None = None + Last_Name: str | None = None + User_Facility_ID: str | None = None + ORCID_ID: str | None = None class PassAllocation(pydantic.BaseModel): @@ -32,27 +31,27 @@ class PassAllocation(pydantic.BaseModel): This class represents PASS's representation of an Allocation. """ - Expired: Optional[bool] = None - Expiration_Date: Optional[str] = None - Allocated_Proposal_Type_ID: Optional[int] = None - Created_Proposal_Type_ID: Optional[int] = None - Creator_User_ID: Optional[int] = None - Cycle_Request_ID: Optional[int] = None - Proposal_ID: Optional[int] = None - PI_User_ID: Optional[int] = None - PRP_Hours_Recommended: Optional[float] = None - Total_Hours_Requested: Optional[float] = None - Total_Hours_Awarded: Optional[float] = None - Allocated_Proposal_Type_Description: Optional[str] = None - Beamline_Description: Optional[str] = None - Comments: Optional[str] = None - Created_Proposal_Type_Description: Optional[str] = None - Cycle_Requested_Description: Optional[str] = None - Short_Name: Optional[str] = None - Title: Optional[str] = None - User_Facility_ID: Optional[str] = None - Creator: Optional[PassPerson] = None - PI: Optional[PassPerson] = None + Expired: bool | None = None + Expiration_Date: str | None = None + Allocated_Proposal_Type_ID: int | None = None + Created_Proposal_Type_ID: int | None = None + Creator_User_ID: int | None = None + Cycle_Request_ID: int | None = None + Proposal_ID: int | None = None + PI_User_ID: int | None = None + PRP_Hours_Recommended: float | None = None + Total_Hours_Requested: float | None = None + Total_Hours_Awarded: float | None = None + Allocated_Proposal_Type_Description: str | None = None + Beamline_Description: str | None = None + Comments: str | None = None + Created_Proposal_Type_Description: str | None = None + Cycle_Requested_Description: str | None = None + Short_Name: str | None = None + Title: str | None = None + User_Facility_ID: str | None = None + Creator: PassPerson | None = None + PI: PassPerson | None = None class PassCycle(pydantic.BaseModel): @@ -60,14 +59,14 @@ class PassCycle(pydantic.BaseModel): This class represents PASS's representation of a Cycle. """ - Active: Optional[bool] = None - ID: Optional[int] = None - Year: Optional[int] = None - Start_Date: Optional[str] = None - End_Date: Optional[str] = None - Name: Optional[str] = None - Description: Optional[str] = None - User_Facility_ID: Optional[str] = None + Active: bool | None = None + ID: int | None = None + Year: int | None = None + Start_Date: str | None = None + End_Date: str | None = None + Name: str | None = None + Description: str | None = None + User_Facility_ID: str | None = None class PassExperimenter(pydantic.BaseModel): @@ -77,23 +76,23 @@ class PassExperimenter(pydantic.BaseModel): model_config = ConfigDict(str_strip_whitespace=True) - Can_Edit: Optional[bool] = None - Can_Read: Optional[bool] = None - CoPI: Optional[bool] = None - On_Site: Optional[bool] = None - Remote_Access: Optional[bool] = None - Mail_In: Optional[bool] = None - Off_Site: Optional[bool] = None - Pool_ID: Optional[int] = None - Proposal_ID: Optional[int] = None - User_ID: Optional[int] = None - Account: Optional[str] = None - BNL_ID: Optional[str] = None - Email: Optional[str] = None - First_Name: Optional[str] = None - Last_Name: Optional[str] = None - User_Facility_ID: Optional[str] = None - ORCID_ID: Optional[str] = None + Can_Edit: bool | None = None + Can_Read: bool | None = None + CoPI: bool | None = None + On_Site: bool | None = None + Remote_Access: bool | None = None + Mail_In: bool | None = None + Off_Site: bool | None = None + Pool_ID: int | None = None + Proposal_ID: int | None = None + User_ID: int | None = None + Account: str | None = None + BNL_ID: str | None = None + Email: str | None = None + First_Name: str | None = None + Last_Name: str | None = None + User_Facility_ID: str | None = None + ORCID_ID: str | None = None class PassResource(pydantic.BaseModel): @@ -101,10 +100,10 @@ class PassResource(pydantic.BaseModel): This class represents PASS's representation of a Resource. """ - ID: Optional[int] = None - Description: Optional[str] = None - User_Facility_ID: Optional[str] = None - Short_Name: Optional[str] = None + ID: int | None = None + Description: str | None = None + User_Facility_ID: str | None = None + Short_Name: str | None = None class PassProposalType(pydantic.BaseModel): @@ -112,10 +111,10 @@ class PassProposalType(pydantic.BaseModel): This class represents PASS's representation of a ProposalType. """ - ID: Optional[int] = None - Code: Optional[str] = None - Description: Optional[str] = None - User_Facility_ID: Optional[str] = None + ID: int | None = None + Code: str | None = None + Description: str | None = None + User_Facility_ID: str | None = None class PassProposal(pydantic.BaseModel): @@ -123,19 +122,19 @@ class PassProposal(pydantic.BaseModel): This class represents PASS's representation of a Proposal. """ - Expired: Optional[bool] = None - Expiration_Date: Optional[str] = None - Creator_User_ID: Optional[int] = None - Proposal_ID: Optional[int] = None - Proposal_Type_ID: Optional[int] = None - PI_User_ID: Optional[int] = None - Proposal_Type_Description: Optional[str] = None - Title: Optional[str] = None - User_Facility_ID: Optional[str] = None - Creator: Optional[PassPerson] = None - PI: Optional[PassPerson] = None - Experimenters: Optional[list[PassExperimenter]] = [] - Resources: Optional[list[PassResource]] = [] + Expired: bool | None = None + Expiration_Date: str | None = None + Creator_User_ID: int | None = None + Proposal_ID: int | None = None + Proposal_Type_ID: int | None = None + PI_User_ID: int | None = None + Proposal_Type_Description: str | None = None + Title: str | None = None + User_Facility_ID: str | None = None + Creator: PassPerson | None = None + PI: PassPerson | None = None + Experimenters: list[PassExperimenter] | None = [] + Resources: list[PassResource] | None = [] class PassScheduledTimeSFTK(pydantic.BaseModel): @@ -143,16 +142,16 @@ class PassScheduledTimeSFTK(pydantic.BaseModel): This class represents PASS's representation of a ScheduledTimeSFTK. """ - ProposalID: Optional[int] = None - CycleRequestedID: Optional[int] = None - ResourceID: Optional[int] = None - UserFacilityID: Optional[str] = None - ExtSchedulerRecordID: Optional[str] = None - ScheduledHoursDuration: Optional[float] = None - StartTime: Optional[str] = None - StopTime: Optional[str] = None - AddedModifiedByUserID: Optional[int] = None - DateAddedModified: Optional[str] = None + ProposalID: int | None = None + CycleRequestedID: int | None = None + ResourceID: int | None = None + UserFacilityID: str | None = None + ExtSchedulerRecordID: str | None = None + ScheduledHoursDuration: float | None = None + StartTime: str | None = None + StopTime: str | None = None + AddedModifiedByUserID: int | None = None + DateAddedModified: str | None = None class PassSaf(pydantic.BaseModel): @@ -160,8 +159,8 @@ class PassSaf(pydantic.BaseModel): This class represents PASS's representation of a SAF. """ - SAF_ID: Optional[int] = None - Date_Expires: Optional[str] = None - Status: Optional[str] = None - Experimenters: Optional[list[PassExperimenter]] = [] - Resources: Optional[list[PassResource]] = [] + SAF_ID: int | None = None + Date_Expires: str | None = None + Status: str | None = None + Experimenters: list[PassExperimenter] | None = [] + Resources: list[PassResource] | None = [] diff --git a/src/nsls2api/models/proposal_types.py b/src/nsls2api/models/proposal_types.py index 3a34bcb7..210addd3 100644 --- a/src/nsls2api/models/proposal_types.py +++ b/src/nsls2api/models/proposal_types.py @@ -1,5 +1,4 @@ import datetime -from typing import Optional import beanie import pydantic @@ -8,9 +7,9 @@ class ProposalType(beanie.Document): code: str facility_id: str | None = None - description: Optional[str] = None - pass_id: Optional[str] = None - pass_description: Optional[str] = None + description: str | None = None + pass_id: str | None = None + pass_description: str | None = None created_on: datetime.datetime = pydantic.Field( default_factory=datetime.datetime.now ) diff --git a/src/nsls2api/models/proposals.py b/src/nsls2api/models/proposals.py index e6a8eff1..108ff978 100644 --- a/src/nsls2api/models/proposals.py +++ b/src/nsls2api/models/proposals.py @@ -1,5 +1,4 @@ import datetime -from typing import List, Optional import beanie import pydantic @@ -12,31 +11,31 @@ class SafetyForm(pydantic.BaseModel): saf_id: str status: str - instruments: Optional[list[str]] + instruments: list[str] | None class User(pydantic.BaseModel): - first_name: Optional[str] = None - last_name: Optional[str] = None + first_name: str | None = None + last_name: str | None = None email: str - bnl_id: Optional[str] = None - username: Optional[str] = None + bnl_id: str | None = None + username: str | None = None is_pi: bool = False - orcid: Optional[str] = None + orcid: str | None = None # -- Shared Base -- class ProposalBase(pydantic.BaseModel): proposal_id: str data_session: str - title: Optional[str] = None - type: Optional[str] = None - pass_type_id: Optional[str] = None - instruments: Optional[List[str]] = [] - cycles: Optional[List[str]] = [] - users: Optional[List[User]] = [] - safs: Optional[List[SafetyForm]] = [] - slack_channels: Optional[List[SlackChannel]] = [] + title: str | None = None + type: str | None = None + pass_type_id: str | None = None + instruments: list[str] | None = [] + cycles: list[str] | None = [] + users: list[User] | None = [] + safs: list[SafetyForm] | None = [] + slack_channels: list[SlackChannel] | None = [] created_on: datetime.datetime = pydantic.Field( default_factory=datetime.datetime.now ) diff --git a/src/nsls2api/scratchpad_main.py b/src/nsls2api/scratchpad_main.py index 7ac8cb8d..e6ee6e5d 100644 --- a/src/nsls2api/scratchpad_main.py +++ b/src/nsls2api/scratchpad_main.py @@ -1,7 +1,7 @@ # This file is just a place to play and test stuff while I am messing about # This is not intended to be a long lived or permanent file. -from typing import Annotated, Union +from typing import Annotated from fastapi import Depends, FastAPI @@ -9,7 +9,7 @@ async def common_parameters( - q: Union[str, None] = None, skip: int = 0, limit: int = 100 + q: str | None = None, skip: int = 0, limit: int = 100 ): return {"q": q, "skip": skip, "limit": limit} diff --git a/src/nsls2api/services/background_service.py b/src/nsls2api/services/background_service.py index 75a6e773..8331450b 100644 --- a/src/nsls2api/services/background_service.py +++ b/src/nsls2api/services/background_service.py @@ -1,7 +1,6 @@ import asyncio import datetime import traceback -from typing import Optional import bson @@ -32,7 +31,7 @@ async def pending_jobs(limit=1_000) -> list[BackgroundJob]: return [] -async def start_job(job_id: bson.ObjectId) -> Optional[BackgroundJob]: +async def start_job(job_id: bson.ObjectId) -> BackgroundJob | None: job = await job_by_id(job_id) if not job: raise Exception(f"No job with ID {job_id} found.") @@ -51,7 +50,7 @@ async def start_job(job_id: bson.ObjectId) -> Optional[BackgroundJob]: async def complete_job( job_id: bson.ObjectId, processing_status: JobStatus, log_message: str = None -) -> Optional[BackgroundJob]: +) -> BackgroundJob | None: job = await job_by_id(job_id) if not job: raise Exception(f"No job with ID {job_id} found.") @@ -70,12 +69,12 @@ async def complete_job( return job -async def job_by_id(job_id: bson.ObjectId) -> Optional[BackgroundJob]: +async def job_by_id(job_id: bson.ObjectId) -> BackgroundJob | None: return await BackgroundJob.find_one(BackgroundJob.id == job_id) async def is_job_finished(job_id: bson.ObjectId) -> bool: - job: Optional[BackgroundJob] = await job_by_id(job_id) + job: BackgroundJob | None = await job_by_id(job_id) if not job: return False diff --git a/src/nsls2api/services/beamline_service.py b/src/nsls2api/services/beamline_service.py index aa2b22c0..e0b83b24 100644 --- a/src/nsls2api/services/beamline_service.py +++ b/src/nsls2api/services/beamline_service.py @@ -1,6 +1,5 @@ import datetime from pathlib import Path -from typing import Optional from beanie.odm.operators.find.array import ElemMatch from beanie.odm.operators.find.comparison import In @@ -36,7 +35,7 @@ async def beamline_count() -> int: return await Beamline.count() -async def beamline_by_name(name: str) -> Optional[Beamline]: +async def beamline_by_name(name: str) -> Beamline | None: """ Find and return a beamline by its name. @@ -58,7 +57,7 @@ async def all_beamlines() -> list[Beamline]: return beamlines -async def beamline_by_pass_id(pass_id: str) -> Optional[Beamline]: +async def beamline_by_pass_id(pass_id: str) -> Beamline | None: """ Find and return a beamline by its PASS ID. @@ -69,7 +68,7 @@ async def beamline_by_pass_id(pass_id: str) -> Optional[Beamline]: return beamline -async def all_services(name: str) -> Optional[ServicesOnly]: +async def all_services(name: str) -> ServicesOnly | None: beamline_services = await Beamline.find_one(Beamline.name == name.upper()).project( ServicesOnly ) @@ -92,7 +91,7 @@ async def add_detector( granularity: DirectoryGranularity, description: str, manufacturer: str, -) -> Optional[Detector]: +) -> Detector | None: """ Add a new detector to a beamline. @@ -142,7 +141,7 @@ async def add_detector( async def delete_detector( beamline_name: str, detector_name: str, -) -> Optional[Detector]: +) -> Detector | None: """ Delete a detector from a beamline. @@ -181,7 +180,7 @@ async def delete_detector( return deleted_detector -async def service_accounts(name: str) -> Optional[ServiceAccounts]: +async def service_accounts(name: str) -> ServiceAccounts | None: accounts = await Beamline.find_one(Beamline.name == name.upper()).project( ServiceAccountsView ) @@ -265,7 +264,7 @@ async def epics_services_username(name: str) -> str: return epics_services_account.username -async def lsdc_username(name: str) -> Optional[str]: +async def lsdc_username(name: str) -> str | None: lsdc_account = await Beamline.find_one(Beamline.name == name.upper()).project( LsdcServiceAccountView ) @@ -276,7 +275,7 @@ async def lsdc_username(name: str) -> Optional[str]: return lsdc_account.username -async def data_roles_by_user(username: str) -> Optional[list[str]]: +async def data_roles_by_user(username: str) -> list[str] | None: beamlines = await Beamline.find(In(Beamline.data_admins, [username])).to_list() beamline_names = [b.name.lower() for b in beamlines if b.name is not None] return beamline_names @@ -447,7 +446,7 @@ async def uses_synchweb(beamline_name: str) -> bool: return False -async def slack_channel_managers(beamline_name: str) -> Optional[list[str]]: +async def slack_channel_managers(beamline_name: str) -> list[str] | None: """ Retrieves the Slack user IDs of the channel managers for a given beamline. @@ -467,7 +466,7 @@ async def slack_channel_managers(beamline_name: str) -> Optional[list[str]]: return beamline.slack_channel_managers -async def slack_beamline_bot_user_id(beamline_name: str) -> Optional[str]: +async def slack_beamline_bot_user_id(beamline_name: str) -> str | None: """ Retrieves the Slack user ID of the beamline bot for a given beamline. diff --git a/src/nsls2api/services/bnlpeople_service.py b/src/nsls2api/services/bnlpeople_service.py index ba22534a..20b7a71f 100644 --- a/src/nsls2api/services/bnlpeople_service.py +++ b/src/nsls2api/services/bnlpeople_service.py @@ -1,4 +1,3 @@ -from typing import List, Optional from nsls2api.api.models.person_model import BNLPerson from nsls2api.infrastructure.logging import logger @@ -20,7 +19,7 @@ async def get_all_people(): return people -async def get_person_by_username(username: str) -> Optional[BNLPerson]: +async def get_person_by_username(username: str) -> BNLPerson | None: url = f"{base_url}/api/BNLPeople?accountName={username}" person = await _call_bnlpeople_webservice(url) if len(person) == 0 or len(person) > 1: @@ -30,7 +29,7 @@ async def get_person_by_username(username: str) -> Optional[BNLPerson]: return BNLPerson(**person[0]) -async def get_username_by_id(lifenumber: str) -> Optional[str]: +async def get_username_by_id(lifenumber: str) -> str | None: if lifenumber is None: return None @@ -59,7 +58,7 @@ async def get_username_by_id(lifenumber: str) -> Optional[str]: return None -async def get_person_by_id(lifenumber: str) -> Optional[BNLPerson]: +async def get_person_by_id(lifenumber: str) -> BNLPerson | None: if lifenumber is None: return None @@ -73,7 +72,7 @@ async def get_person_by_id(lifenumber: str) -> Optional[BNLPerson]: return BNLPerson(**person[0]) -async def get_person_by_email(email: str) -> Optional[BNLPerson]: +async def get_person_by_email(email: str) -> BNLPerson | None: url = f"{base_url}/api/BNLPeople?email={email}" person = await _call_bnlpeople_webservice(url) if len(person) == 0 or len(person) > 1: @@ -85,7 +84,7 @@ async def get_person_by_email(email: str) -> Optional[BNLPerson]: async def get_people_by_department( department_code: str, -) -> Optional[List[BNLPerson]]: +) -> list[BNLPerson] | None: url = f"{base_url}/api/BNLPeople?departmentCode={department_code}" people = await _call_bnlpeople_webservice(url) if len(people) == 0: diff --git a/src/nsls2api/services/facility_service.py b/src/nsls2api/services/facility_service.py index 3bc69667..d7dec005 100644 --- a/src/nsls2api/services/facility_service.py +++ b/src/nsls2api/services/facility_service.py @@ -1,7 +1,6 @@ import datetime from contextlib import asynccontextmanager from dataclasses import dataclass -from typing import Optional from beanie.odm.operators.find.comparison import In from beanie.odm.operators.update.general import Set @@ -14,8 +13,8 @@ class CycleOperationError(Exception): """Base exception for cycle operations.""" + # No additional methods or attributes are needed for this class. - pass class CycleNotFoundError(CycleOperationError): @@ -42,7 +41,7 @@ def __init__(self, facility: str, cycle: str, reason: str): class CycleVerificationError(CycleOperationError): """Raised when cycle verification fails after an update.""" - def __init__(self, facility: str, expected_cycle: str, actual_cycle: Optional[str]): + def __init__(self, facility: str, expected_cycle: str, actual_cycle: str | None): self.facility = facility self.expected_cycle = expected_cycle self.actual_cycle = actual_cycle @@ -71,7 +70,7 @@ async def all_facilities() -> list[Facility]: return await Facility.find().to_list() -async def facility_cycles(facility: str) -> Optional[list[str]]: +async def facility_cycles(facility: str) -> list[str] | None: """ Facility Cycles @@ -92,7 +91,7 @@ async def facility_cycles(facility: str) -> Optional[list[str]]: async def facility_cycle_by_date( facility: FacilityName, date: datetime.datetime -) -> Optional[Cycle]: +) -> Cycle | None: """ Find the cycle for a facility that contains the given date. @@ -108,7 +107,7 @@ async def facility_cycle_by_date( return cycle if cycle else None -async def facility_by_pass_id(pass_user_facility_id: str) -> Optional[Facility]: +async def facility_by_pass_id(pass_user_facility_id: str) -> Facility | None: """ Facility by PASS ID @@ -120,7 +119,7 @@ async def facility_by_pass_id(pass_user_facility_id: str) -> Optional[Facility]: return await Facility.find_one(Facility.pass_facility_id == pass_user_facility_id) -async def pass_id_for_facility(facility_id: str) -> Optional[str]: +async def pass_id_for_facility(facility_id: str) -> str | None: """ PASS ID for Facility @@ -134,13 +133,13 @@ async def pass_id_for_facility(facility_id: str) -> Optional[str]: return facility.pass_facility_id if facility else None -async def data_roles_by_user(username: str) -> Optional[list[str]]: +async def data_roles_by_user(username: str) -> list[str] | None: facilities = await Facility.find(In(Facility.data_admins, [username])).to_list() facility_names = [f.facility_id for f in facilities if f.facility_id is not None] return facility_names -async def data_admin_group(facility_name: str) -> Optional[str]: +async def data_admin_group(facility_name: str) -> str | None: """ Retrieves the data admin group for a given facility name. @@ -188,7 +187,7 @@ async def update_data_admins(facility_id: str, data_admins: list[str]): ) -async def current_operating_cycle(facility_name: str) -> Optional[str]: +async def current_operating_cycle(facility_name: str) -> str | None: """ Current Operating Cycle @@ -199,7 +198,7 @@ async def current_operating_cycle(facility_name: str) -> Optional[str]: """ cycle = await Cycle.find_one( Cycle.facility == facility_name, - Cycle.is_current_operating_cycle == True, # noqa: E712 + Cycle.is_current_operating_cycle == True, # noqa: E712 # Operator is used by Beanie query ) return cycle.name if cycle else None @@ -207,8 +206,8 @@ async def current_operating_cycle(facility_name: str) -> Optional[str]: @dataclass class CycleChangeState: - new_cycle: Optional[Cycle] = None - previous_cycle: Optional[Cycle] = None + new_cycle: Cycle | None = None + previous_cycle: Cycle | None = None is_successful: bool = False @@ -243,7 +242,7 @@ async def cycle_change_context(facility: str, cycle: str): # Get the current active cycle state.previous_cycle = await Cycle.find_one( Cycle.facility == facility, - Cycle.is_current_operating_cycle == True, # noqa: E712 + Cycle.is_current_operating_cycle == True, # noqa: E712 # Operator is used by Beanie query ) yield state @@ -313,7 +312,7 @@ async def set_current_operating_cycle(facility_name: str, cycle: str) -> str: async def cycle_year( cycle_name: str, facility_name: FacilityName = FacilityName.nsls2 -) -> Optional[str]: +) -> str | None: """ Cycle Year diff --git a/src/nsls2api/services/n2sn_service.py b/src/nsls2api/services/n2sn_service.py index de499025..18746812 100644 --- a/src/nsls2api/services/n2sn_service.py +++ b/src/nsls2api/services/n2sn_service.py @@ -1,4 +1,3 @@ -from typing import Optional from N2SNUserTools.ldap import ADObjects @@ -12,7 +11,7 @@ settings = get_settings() -async def get_groups_by_username(username: str) -> Optional[ActiveDirectoryUserGroups]: +async def get_groups_by_username(username: str) -> ActiveDirectoryUserGroups | None: """ :param username: The username for which you want to retrieve the groups. :return: An instance of ActiveDirectoryUserGroups that contains information about the groups the user belongs to. Returns None if the user is not found or if there are multiple users with the same username. @@ -31,7 +30,7 @@ async def get_groups_by_username(username: str) -> Optional[ActiveDirectoryUserG return ActiveDirectoryUserGroups(**user_details[0]) -async def get_user_by_username(username: str) -> Optional[ActiveDirectoryUser]: +async def get_user_by_username(username: str) -> ActiveDirectoryUser | None: """ Get a user by their username. @@ -55,7 +54,7 @@ async def get_user_by_username(username: str) -> Optional[ActiveDirectoryUser]: return ActiveDirectoryUser(**user_details[0]) -async def get_user_by_id(bnl_id: str) -> Optional[ActiveDirectoryUser]: +async def get_user_by_id(bnl_id: str) -> ActiveDirectoryUser | None: """ :param bnl_id: The BNL ID of the user to retrieve :return: An ActiveDirectoryUser object representing the user's details @@ -74,7 +73,7 @@ async def get_user_by_id(bnl_id: str) -> Optional[ActiveDirectoryUser]: return ActiveDirectoryUser(**user_details[0]) -async def get_username_by_id(bnl_id: str) -> Optional[str]: +async def get_username_by_id(bnl_id: str) -> str | None: ad_user: ActiveDirectoryUser = await get_user_by_id(bnl_id) if ad_user is None: return None diff --git a/src/nsls2api/services/pass_service.py b/src/nsls2api/services/pass_service.py index c7e78055..e2afc080 100644 --- a/src/nsls2api/services/pass_service.py +++ b/src/nsls2api/services/pass_service.py @@ -1,4 +1,3 @@ -from typing import Optional from pydantic import ValidationError @@ -36,7 +35,7 @@ async def _call_pass_webservice(url: str): async def get_proposal( proposal_id: str, facility: FacilityName = FacilityName.nsls2 -) -> Optional[PassProposal]: +) -> PassProposal | None: pass_facility = await facility_service.pass_id_for_facility(facility) if not pass_facility: @@ -65,7 +64,7 @@ async def get_proposal( async def get_proposal_types( facility: FacilityName = FacilityName.nsls2, -) -> Optional[list[PassProposalType]]: +) -> list[PassProposalType] | None: pass_facility = await facility_service.pass_id_for_facility(facility) if not pass_facility: @@ -95,7 +94,7 @@ async def get_proposal_types( async def get_commissioning_proposal_type( facility: FacilityName = FacilityName.nsls2, -) -> Optional[ProposalType]: +) -> ProposalType | None: match facility: case FacilityName.nsls2: # The PASS ID for NSLS-II commissioning proposals is 300005 @@ -124,7 +123,7 @@ async def get_all_commissioning_proposal_type_ids() -> list[str]: async def get_saf_from_proposal( proposal_id: str, facility: FacilityName = FacilityName.nsls2 -) -> Optional[list[PassSaf]]: +) -> list[PassSaf] | None: pass_facility = await facility_service.pass_id_for_facility(facility) if not pass_facility: @@ -155,7 +154,7 @@ async def get_saf_from_proposal( async def get_commissioning_proposals_by_year( year: str, facility_name: FacilityName = FacilityName.nsls2 -) -> Optional[list[PassProposal]]: +) -> list[PassProposal] | None: pass_facility = await facility_service.pass_id_for_facility(facility_name) if not pass_facility: error_message: str = f"Facility {facility_name} does not have a PASS ID." @@ -173,11 +172,11 @@ async def get_commissioning_proposals_by_year( PassProposal(**commissioning_proposal) ) except ValidationError as error: - error_message = f"Error validating commissioning proposal data received from PASS for year {str(year)} at {facility_name} facility." + error_message = f"Error validating commissioning proposal data received from PASS for year {year!s} at {facility_name} facility." logger.error(error_message) raise PassException(error_message) from error except Exception as error: - error_message = f"Error retrieving commissioning proposal information from PASS for year {str(year)} at {facility_name} facility." + error_message = f"Error retrieving commissioning proposal information from PASS for year {year!s} at {facility_name} facility." logger.exception(error_message) raise PassException(error_message) from error @@ -192,7 +191,7 @@ async def get_pass_resources(): async def get_cycles( facility: FacilityName = FacilityName.nsls2, -) -> Optional[list[PassCycle]]: +) -> list[PassCycle] | None: pass_facility = await facility_service.pass_id_for_facility(facility) if not pass_facility: @@ -223,7 +222,7 @@ async def get_cycles( async def get_proposals_allocated_by_cycle( cycle_name: str, facility: FacilityName = FacilityName.nsls2 -) -> Optional[list[PassAllocation]]: +) -> list[PassAllocation] | None: pass_facility = await facility_service.pass_id_for_facility(facility) if not pass_facility: error_message: str = f"Facility {facility} does not have a PASS ID." @@ -258,7 +257,7 @@ async def get_proposals_allocated_by_cycle( async def get_proposals_allocated( facility: FacilityName = FacilityName.nsls2, -) -> Optional[list[PassAllocation]]: +) -> list[PassAllocation] | None: pass_facility = await facility_service.pass_id_for_facility(facility) if not pass_facility: diff --git a/src/nsls2api/services/person_service.py b/src/nsls2api/services/person_service.py index ea511f34..ee6c4f5a 100644 --- a/src/nsls2api/services/person_service.py +++ b/src/nsls2api/services/person_service.py @@ -1,5 +1,4 @@ # Helper and utility functions for people/users -from typing import Optional from nsls2api.api.models.person_model import ( ActiveDirectoryUser, @@ -17,7 +16,7 @@ from nsls2api.services.pass_service import get_proposals_by_person -async def summary_details_by_username(username: str) -> Optional[PersonSummary]: +async def summary_details_by_username(username: str) -> PersonSummary | None: diag_person = await diagnostic_details_by_username(username) # Now lets return a Person that only contains a limited person = PersonSummary( @@ -30,7 +29,7 @@ async def summary_details_by_username(username: str) -> Optional[PersonSummary]: return person -async def diagnostic_details_by_username(username: str) -> Optional[Person]: +async def diagnostic_details_by_username(username: str) -> Person | None: try: bnl_person = await bnlpeople_service.get_person_by_username(username) ad_person: ActiveDirectoryUser = await n2sn_service.get_user_by_username( diff --git a/src/nsls2api/services/proposal_service.py b/src/nsls2api/services/proposal_service.py index 8a5c57e2..17fa544b 100644 --- a/src/nsls2api/services/proposal_service.py +++ b/src/nsls2api/services/proposal_service.py @@ -1,7 +1,6 @@ import datetime import random from pathlib import Path -from typing import Optional from beanie.odm.operators.find.array import ElemMatch from beanie.operators import And, In, Or, RegEx, Text @@ -15,8 +14,8 @@ ProposalChangeResultsList, ProposalDiagnostics, ProposalFullDetails, - ProposalsToChangeList, ProposalIdDataSession, + ProposalsToChangeList, ) from nsls2api.infrastructure.logging import logger from nsls2api.models.cycles import Cycle @@ -206,7 +205,7 @@ def generate_data_session_for_proposal(proposal_id: str) -> str: Returns: str: The generated data session name. """ - return f"pass-{str(proposal_id)}" + return f"pass-{proposal_id!s}" async def get_beamline_specific_slack_channel_for_proposal( @@ -310,7 +309,7 @@ async def proposal_by_saf_id(saf_id: str) -> Proposal: # Get a list of proposals that match the search criteria -async def search_proposals(search_text: str) -> Optional[list[Proposal]]: +async def search_proposals(search_text: str) -> list[Proposal] | None: query = Text(search=search_text, case_sensitive=False) if len(search_text) < 3: @@ -350,7 +349,7 @@ async def fetch_proposals( page_size: int = 10, page: int = 1, include_directories: bool = False, -) -> Optional[list[ProposalFullDetails]]: +) -> list[ProposalFullDetails] | None: query = [] saf_status_upper: list[str] = [] @@ -463,7 +462,7 @@ async def fetch_data_sessions( async def proposal_type_description_from_pass_type_id( pass_type_id: int, -) -> Optional[str]: +) -> str | None: proposal_type = await ProposalType.find_one( ProposalType.pass_id == str(pass_type_id) ) @@ -475,27 +474,27 @@ async def proposal_type_description_from_pass_type_id( return proposal_type.description -async def data_session_for_proposal(proposal_id: str) -> Optional[str]: +async def data_session_for_proposal(proposal_id: str) -> str | None: proposal = await Proposal.find_one(Proposal.proposal_id == str(proposal_id)) return proposal.data_session -async def beamlines_for_proposal(proposal_id: str) -> Optional[list[str]]: +async def beamlines_for_proposal(proposal_id: str) -> list[str] | None: proposal = await proposal_by_id(proposal_id) return proposal.instruments -async def cycles_for_proposal(proposal_id: str) -> Optional[list[str]]: +async def cycles_for_proposal(proposal_id: str) -> list[str] | None: proposal = await proposal_by_id(proposal_id) return proposal.cycles -async def slack_channels_for_proposal(proposal_id: str) -> Optional[list[SlackChannel]]: +async def slack_channels_for_proposal(proposal_id: str) -> list[SlackChannel] | None: proposal = await proposal_by_id(proposal_id) return proposal.slack_channels -async def fetch_users_on_proposal(proposal_id: str) -> Optional[list[User]]: +async def fetch_users_on_proposal(proposal_id: str) -> list[User] | None: """ Fetches the users associated with a given proposal. @@ -511,7 +510,7 @@ async def fetch_users_on_proposal(proposal_id: str) -> Optional[list[User]]: async def fetch_usernames_from_proposal( proposal_id: str, -) -> Optional[list[str]]: +) -> list[str] | None: proposal = await proposal_by_id(proposal_id) if proposal is None: @@ -523,7 +522,7 @@ async def fetch_usernames_from_proposal( async def fetch_emails_from_proposal( proposal_id: str, -) -> Optional[list[str]]: +) -> list[str] | None: proposal = await proposal_by_id(proposal_id) if proposal is None: @@ -533,7 +532,7 @@ async def fetch_emails_from_proposal( return emails -async def safs_from_proposal(proposal_id: str) -> Optional[list[str]]: +async def safs_from_proposal(proposal_id: str) -> list[str] | None: proposal = await proposal_by_id(proposal_id) safs = [s.saf_id for s in proposal.safs if s.saf_id is not None] @@ -541,7 +540,7 @@ async def safs_from_proposal(proposal_id: str) -> Optional[list[str]]: return safs -async def pi_from_proposal(proposal_id: str) -> Optional[list[User]]: +async def pi_from_proposal(proposal_id: str) -> list[User] | None: proposal = await proposal_by_id(proposal_id) pi = [u for u in proposal.users if u.is_pi] @@ -637,19 +636,19 @@ async def directories(proposal_id: str): if proposal.data_session is None: error_text = ( - f"Proposal {str(proposal.proposal_id)} does not contain a data_session." + f"Proposal {proposal.proposal_id!s} does not contain a data_session." ) logger.error(error_text) error_msg.append(error_text) if not await has_valid_cycle(proposal) and not await is_commissioning(proposal): - error_text = f"Proposal {str(proposal.proposal_id)} does not contain any cycle information." + error_text = f"Proposal {proposal.proposal_id!s} does not contain any cycle information." logger.error(error_text) error_msg.append(error_text) if len(proposal.instruments) == 0: error_text = ( - f"Proposal {str(proposal.proposal_id)} does not contain any beamlines." + f"Proposal {proposal.proposal_id!s} does not contain any beamlines." ) logger.error(error_text) error_msg.append(error_text) @@ -712,7 +711,7 @@ async def directories(proposal_id: str): return directory_list -async def diagnostic_details_by_id(proposal_id: str) -> Optional[ProposalDiagnostics]: +async def diagnostic_details_by_id(proposal_id: str) -> ProposalDiagnostics | None: proposal = await proposal_by_id(proposal_id) if proposal is None: @@ -748,7 +747,7 @@ async def generate_fake_proposal_id() -> int: async def generate_fake_test_proposal( facility_name: FacilityName = FacilityName.nsls2, add_specific_user=None -) -> Optional[Proposal]: +) -> Proposal | None: """ Generates a fake test proposal. diff --git a/src/nsls2api/services/slack_service.py b/src/nsls2api/services/slack_service.py index cc0cce75..f0e037de 100644 --- a/src/nsls2api/services/slack_service.py +++ b/src/nsls2api/services/slack_service.py @@ -1,5 +1,4 @@ from datetime import datetime -from typing import Optional from slack_sdk import WebClient from slack_sdk.errors import SlackApiError @@ -155,7 +154,7 @@ def get_bot_details() -> SlackBot: ) -def get_user_info(user_id: str) -> Optional[SlackUser]: +def get_user_info(user_id: str) -> SlackUser | None: """ Retrieves the details of a Slack User. @@ -369,7 +368,7 @@ def lookup_user_by_email(email: str) -> SlackPerson | None: return None -def get_userid_by_username(username: str) -> Optional[str]: +def get_userid_by_username(username: str) -> str | None: """ Looks up the slack user_id associated with the given username. @@ -404,7 +403,7 @@ def get_userid_by_username(username: str) -> Optional[str]: return None # Not found -def invite_newuser_to_channel(channel: str, email: str) -> Optional[SlackPerson]: +def invite_newuser_to_channel(channel: str, email: str) -> SlackPerson | None: """ Invites a user to the workspace/channel. Args: diff --git a/src/nsls2api/tests/services/test_proposal_service.py b/src/nsls2api/tests/services/test_proposal_service.py index b4a0ccd3..da327cc1 100644 --- a/src/nsls2api/tests/services/test_proposal_service.py +++ b/src/nsls2api/tests/services/test_proposal_service.py @@ -2,8 +2,7 @@ from httpx import ASGITransport, AsyncClient from nsls2api.main import app - -from nsls2api.models.proposals import Proposal, User, SafetyForm +from nsls2api.models.proposals import Proposal, SafetyForm, User from nsls2api.services import proposal_service test_proposal_id = "314159" diff --git a/src/nsls2api/viewmodels/diagnostics/proposal_viewmodel.py b/src/nsls2api/viewmodels/diagnostics/proposal_viewmodel.py index 1760fbbd..7b6b6de9 100644 --- a/src/nsls2api/viewmodels/diagnostics/proposal_viewmodel.py +++ b/src/nsls2api/viewmodels/diagnostics/proposal_viewmodel.py @@ -1,4 +1,3 @@ -from typing import Optional from starlette.requests import Request @@ -12,8 +11,8 @@ def __init__(self, proposal_id: str, request: Request): super().__init__(proposal_id, request) self.proposal_id = proposal_id - self.proposal: Optional[ProposalDiagnostics] = None - self.diagnostic_message: Optional[str] = None + self.proposal: ProposalDiagnostics | None = None + self.diagnostic_message: str | None = None async def load(self): try: diff --git a/src/nsls2api/viewmodels/diagnostics/user_viewmodel.py b/src/nsls2api/viewmodels/diagnostics/user_viewmodel.py index 352c1c99..6128bb0c 100644 --- a/src/nsls2api/viewmodels/diagnostics/user_viewmodel.py +++ b/src/nsls2api/viewmodels/diagnostics/user_viewmodel.py @@ -1,4 +1,3 @@ -from typing import Optional from starlette.requests import Request @@ -12,8 +11,8 @@ def __init__(self, username: str, request: Request): super().__init__(request) self.username = username - self.person: Optional[Person] = None - self.diagnostic_message: Optional[str] = None + self.person: Person | None = None + self.diagnostic_message: str | None = None async def load(self): try: diff --git a/src/nsls2api/viewmodels/proposals/details_viewmodel.py b/src/nsls2api/viewmodels/proposals/details_viewmodel.py index 6064c322..265c8967 100644 --- a/src/nsls2api/viewmodels/proposals/details_viewmodel.py +++ b/src/nsls2api/viewmodels/proposals/details_viewmodel.py @@ -1,4 +1,3 @@ -from typing import Optional from starlette.requests import Request @@ -12,7 +11,7 @@ def __init__(self, proposal_id: str, request: Request): super().__init__(request) self.proposal_id = proposal_id - self.proposal: Optional[Proposal] = None + self.proposal: Proposal | None = None async def load(self): self.proposal = await proposal_service.proposal_by_id(self.proposal_id) diff --git a/src/nsls2api/viewmodels/proposals/search_viewmodel.py b/src/nsls2api/viewmodels/proposals/search_viewmodel.py index cb94861b..911aa574 100644 --- a/src/nsls2api/viewmodels/proposals/search_viewmodel.py +++ b/src/nsls2api/viewmodels/proposals/search_viewmodel.py @@ -1,4 +1,3 @@ -from typing import Optional from fastapi import Request @@ -11,7 +10,7 @@ class SearchViewModel(ViewModelBase): def __init__(self, request: Request): super().__init__(request) - self.proposals: Optional[list[Proposal]] = [] + self.proposals: list[Proposal] | None = [] self.request = request # self.search_text: str = request diff --git a/src/nsls2api/viewmodels/shared/viewmodelbase.py b/src/nsls2api/viewmodels/shared/viewmodelbase.py index 6bc30310..03c245af 100644 --- a/src/nsls2api/viewmodels/shared/viewmodelbase.py +++ b/src/nsls2api/viewmodels/shared/viewmodelbase.py @@ -1,4 +1,3 @@ -from typing import Optional from starlette.requests import Request @@ -8,7 +7,7 @@ def __init__(self, request: Request): self.request: Request = request self.is_htmx_request = request.headers.get("HX-Request") - self.error: Optional[str] = None + self.error: str | None = None self.view_model = self.to_dict() def to_dict(self) -> dict: diff --git a/src/nsls2api/views/diagnostics.py b/src/nsls2api/views/diagnostics.py index 98ff71ef..bbd16904 100644 --- a/src/nsls2api/views/diagnostics.py +++ b/src/nsls2api/views/diagnostics.py @@ -37,7 +37,7 @@ async def diag_proposal(proposal_id: str, request: Request): try: await vm.load() except Exception as e: - print(f"Exception: {str(e)}") + print(f"Exception: {e!s}") raise HTTPException(status_code=status.HTTP_404_NOT_FOUND, detail=str(e)) # if there was a problem in getting the information for a user then the error will not be None. From ef36fa8f3acd20b599aa5248b2f3105837831f74 Mon Sep 17 00:00:00 2001 From: Padraic Shafer Date: Sat, 8 Aug 2026 10:45:34 -0700 Subject: [PATCH 3/4] Reformat multiline nesting --- src/nsls2api/services/beamline_service.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/nsls2api/services/beamline_service.py b/src/nsls2api/services/beamline_service.py index e0b83b24..eda1b5b4 100644 --- a/src/nsls2api/services/beamline_service.py +++ b/src/nsls2api/services/beamline_service.py @@ -76,7 +76,9 @@ async def all_services(name: str) -> ServicesOnly | None: async def detectors(name: str) -> list[Detector]: - beamline_detectors = await Beamline.find_one(Beamline.name == name.upper()).project( + beamline_detectors = await Beamline.find_one( + Beamline.name == name.upper() + ).project( DetectorView ) if beamline_detectors is None: From e30a618b1ed4b6cfcae3696b51f4ecf4ca19f256 Mon Sep 17 00:00:00 2001 From: Padraic Shafer Date: Sat, 8 Aug 2026 10:32:35 -0700 Subject: [PATCH 4/4] Apply ruff check unsafe auto-fix --- src/nsls2api/cli/utils/api.py | 2 +- src/nsls2api/infrastructure/security.py | 2 +- src/nsls2api/services/background_service.py | 2 +- src/nsls2api/services/helpers.py | 6 +++--- src/nsls2api/services/ldap_service.py | 2 +- src/nsls2api/services/n2sn_service.py | 2 +- 6 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/nsls2api/cli/utils/api.py b/src/nsls2api/cli/utils/api.py index cc5bcf71..a802f66d 100644 --- a/src/nsls2api/cli/utils/api.py +++ b/src/nsls2api/cli/utils/api.py @@ -7,7 +7,7 @@ def call_nsls2api_endpoint( - endpoint: str, method: str = "GET", data: dict = None + endpoint: str, method: str = "GET", data: dict | None = None ) -> httpx.Response | None: """ Call the NSLS-II API endpoint and return the response. diff --git a/src/nsls2api/infrastructure/security.py b/src/nsls2api/infrastructure/security.py index 74d9ee3c..f5ce3b7e 100644 --- a/src/nsls2api/infrastructure/security.py +++ b/src/nsls2api/infrastructure/security.py @@ -80,7 +80,7 @@ async def generate_api_key(username: str, usertype=ApiUserType.user): except Exception as e: logger.exception(e) - raise e + raise async def set_user_role(username: str, role: ApiUserRole): diff --git a/src/nsls2api/services/background_service.py b/src/nsls2api/services/background_service.py index 8331450b..69e7af4e 100644 --- a/src/nsls2api/services/background_service.py +++ b/src/nsls2api/services/background_service.py @@ -49,7 +49,7 @@ async def start_job(job_id: bson.ObjectId) -> BackgroundJob | None: async def complete_job( - job_id: bson.ObjectId, processing_status: JobStatus, log_message: str = None + job_id: bson.ObjectId, processing_status: JobStatus, log_message: str | None = None ) -> BackgroundJob | None: job = await job_by_id(job_id) if not job: diff --git a/src/nsls2api/services/helpers.py b/src/nsls2api/services/helpers.py index f8e02bae..b03863eb 100644 --- a/src/nsls2api/services/helpers.py +++ b/src/nsls2api/services/helpers.py @@ -41,8 +41,8 @@ def __call__(self): async def _call_async_webservice( url: str, - auth: tuple = None, - headers: dict = None, + auth: tuple | None = None, + headers: dict | None = None, ) -> Response: transport = None @@ -66,7 +66,7 @@ async def _call_async_webservice( async def _call_async_webservice_with_client( - url: str, auth: tuple = None, headers: dict = None, client: httpx.AsyncClient = None + url: str, auth: tuple | None = None, headers: dict | None = None, client: httpx.AsyncClient = None ) -> Response: if client is None: # Then just use the general method that creates a client each time diff --git a/src/nsls2api/services/ldap_service.py b/src/nsls2api/services/ldap_service.py index 55ff1575..b94d6b7f 100644 --- a/src/nsls2api/services/ldap_service.py +++ b/src/nsls2api/services/ldap_service.py @@ -40,7 +40,7 @@ def get_user_info(upn, ldap_server, ldap_base_dn, ldap_bind_user, bind_password) return None entry = conn.entries[0] - user = dict() + user = {} for attribute in entry.entry_attributes: value = entry[attribute].value if attribute in ("objectGUID", "objectSid"): diff --git a/src/nsls2api/services/n2sn_service.py b/src/nsls2api/services/n2sn_service.py index 18746812..9fa026fb 100644 --- a/src/nsls2api/services/n2sn_service.py +++ b/src/nsls2api/services/n2sn_service.py @@ -116,6 +116,6 @@ async def is_user_in_group(username: str, group: str): _user_found = False users = await get_users_in_group(group) _user_found: bool = any( - [user for user in users if user["sAMAccountName"] == username] + user for user in users if user["sAMAccountName"] == username ) return _user_found