44import requests
55from fastapi import HTTPException , status
66from lims_utils .logging import app_logger
7- from sqlalchemy import update
7+ from sqlalchemy import func , select , update
88
99from ..models .containers import ContainerExternal
1010from ..models .inner_db .tables import (
3131}
3232
3333
34+ # TODO: possibly replace this with middleware, or httpx client instances
35+ class ExternalRequest :
36+ @staticmethod
37+ def request (
38+ token ,
39+ base_url = Config .ispyb_api .url ,
40+ * args ,
41+ ** kwargs ,
42+ ):
43+ """Wrapper for request object. Since the URL is validated before any
44+ auth actions happen, we cannot wrap this in a custom auth implementation,
45+ we must do all the preparation work before the actual request."""
46+
47+ kwargs ["url" ] = f"{ base_url } { kwargs ['url' ]} "
48+ kwargs ["method" ] = kwargs .get ("method" , "GET" )
49+ kwargs ["headers" ] = {"Authorization" : f"Bearer { token } " }
50+
51+ return requests .request (** kwargs )
52+
53+
54+ def _get_resource_from_ispyb (token : str , url : str ):
55+ response = ExternalRequest .request (token , url = url )
56+
57+ if response .status_code != 200 :
58+ app_logger .error (
59+ (
60+ f"Failed getting session information from ISPyB at URL { url } , service returned "
61+ f"{ response .status_code } : { response .text } "
62+ )
63+ )
64+
65+ raise HTTPException (
66+ status_code = status .HTTP_424_FAILED_DEPENDENCY ,
67+ detail = "Received invalid response from upstream service" ,
68+ )
69+
70+ return response .json ()
71+
72+
3473class ExternalObject :
3574 """Object representing a link to the ISPyB instance of the object"""
3675
3776 item_body : OrmBaseModel = OrmBaseModel ()
3877 external_link_prefix = ""
3978 external_key = ""
4079 url = ""
80+ to_exclude : set [str ] = set ()
4181
4282 def __init__ (
4383 self ,
84+ token : str ,
4485 item : AvailableTable ,
4586 item_id : int | str | None ,
4687 root_id : int | None = None ,
@@ -66,6 +107,36 @@ def __init__(
66107 self .url = f"/shipments/{ item_id } /dewars"
67108 self .external_link_prefix = "/dewars/"
68109 self .item_body = TopLevelContainerExternal .model_validate (item )
110+
111+ shipment = inner_db .session .execute (
112+ select (
113+ func .concat (Shipment .proposalCode , Shipment .proposalNumber ).label ("proposal" ),
114+ Shipment .visitNumber ,
115+ ).filter (Shipment .id == item .shipmentId )
116+ ).one ()
117+
118+ # When creating the dewar in ISPyB, since ISPyB has no concept of shipments belonging to sessions,
119+ # dewars have to be assigned to sessions instead, and this is done through the firstExperimentId
120+ # column, which despite the cryptic name, points to the BLSession table.
121+ if item .externalId is None :
122+ session = _get_resource_from_ispyb (
123+ token ,
124+ f"/proposals/{ shipment .proposal } /sessions/{ shipment .visitNumber } " ,
125+ )
126+ self .item_body .firstExperimentId = session ["sessionId" ]
127+ else :
128+ self .to_exclude = {"firstExperimentId" }
129+
130+ # We store the dewar's facility code, but not the numeric dewar registry ID that ISPyB also expects.
131+ # Even though the alphanumeric code is a primary key in the DewarRegistry table, the dewar table still
132+ # expects a numeric dewarRegistryId which is used in some systems.
133+ # Since the facility code can be changed by the user, we need to update this even if it was already
134+ # pushed to ISPyB
135+ dewar_reg = _get_resource_from_ispyb (
136+ token , f"/proposals/{ shipment .proposal } /dewar-registry/{ item .code } "
137+ )
138+
139+ self .item_body .dewarRegistryId = dewar_reg ["dewarRegistryId" ]
69140 self .external_key = "dewarId"
70141 case Sample ():
71142 if item_id is None :
@@ -80,26 +151,6 @@ def __init__(
80151 raise NotImplementedError ()
81152
82153
83- # TODO: possibly replace this with middleware, or httpx client instances
84- class ExternalRequest :
85- @staticmethod
86- def request (
87- token ,
88- base_url = Config .ispyb_api .url ,
89- * args ,
90- ** kwargs ,
91- ):
92- """Wrapper for request object. Since the URL is validated before any
93- auth actions happen, we cannot wrap this in a custom auth implementation,
94- we must do all the preparation work before the actual request."""
95-
96- kwargs ["url" ] = f"{ base_url } { kwargs ['url' ]} "
97- kwargs ["method" ] = kwargs .get ("method" , "GET" )
98- kwargs ["headers" ] = {"Authorization" : f"Bearer { token } " }
99-
100- return requests .request (** kwargs )
101-
102-
103154class Expeye :
104155 @classmethod
105156 def upsert (
@@ -119,7 +170,7 @@ def upsert(
119170 Returns:
120171 External link and external ID"""
121172
122- ext_obj = ExternalObject (item , parent_id , root_id )
173+ ext_obj = ExternalObject (token , item , parent_id , root_id )
123174 method = "POST"
124175
125176 if item .externalId :
@@ -130,7 +181,7 @@ def upsert(
130181 token ,
131182 method = method ,
132183 url = ext_obj .url ,
133- json = ext_obj .item_body .model_dump (mode = "json" ),
184+ json = ext_obj .item_body .model_dump (mode = "json" , exclude = ext_obj . to_exclude ),
134185 )
135186
136187 if response .status_code not in [201 , 200 ]:
0 commit comments