Skip to content

Commit 522fd8a

Browse files
committed
Allow codegens to return a science image to be used for a request
1 parent be8e92f commit 522fd8a

File tree

3 files changed

+64
-7
lines changed

3 files changed

+64
-7
lines changed

servicex_codegen/code_generator.py

+10-4
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Copyright (c) 2022 , IRIS-HEP
1+
# Copyright (c) 2022-2025, IRIS-HEP
22
# All rights reserved.
33
#
44
# Redistribution and use in source and binary forms, with or without
@@ -32,9 +32,15 @@
3232
# modification, are permitted provided that the following conditions are met:
3333
#
3434
from abc import ABC, abstractmethod
35-
from collections import namedtuple
35+
from dataclasses import dataclass
36+
from typing import Optional
3637

37-
GeneratedFileResult = namedtuple('GeneratedFileResult', 'hash output_dir')
38+
39+
@dataclass
40+
class GeneratedFileResult:
41+
hash: str
42+
output_dir: str
43+
image: Optional[str] = None
3844

3945

4046
class GenerateCodeException(BaseException):
@@ -47,5 +53,5 @@ def __init__(self, message: str):
4753
class CodeGenerator(ABC):
4854

4955
@abstractmethod
50-
def generate_code(self, query, cache_path: str):
56+
def generate_code(self, query, cache_path: str) -> GeneratedFileResult:
5157
pass

servicex_codegen/post_operation.py

+4-2
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Copyright (c) 2022 , IRIS-HEP
1+
# Copyright (c) 2022-2025, IRIS-HEP
22
# All rights reserved.
33
#
44
# Redistribution and use in source and binary forms, with or without
@@ -97,7 +97,9 @@ def post(self):
9797
zip_data = self.stream_generated_code(generated_code_result)
9898
# code gen transformer returns the default transformer image mentioned in
9999
# the config file
100-
transformer_image = current_app.config['TRANSFORMER_SCIENCE_IMAGE']
100+
transformer_image = (generated_code_result.image
101+
if generated_code_result.image is not None
102+
else current_app.config['TRANSFORMER_SCIENCE_IMAGE'])
101103

102104
# MultipartEncoder library takes multiple types of data fields and merge
103105
# them into a multipart mime data type

tests/test_post_operation.py

+50-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
# Copyright (c) 2019, IRIS-HEP
1+
# Copyright (c) 2019-2025, IRIS-HEP
22
# All rights reserved.
33
#
44
# Redistribution and use in source and binary forms, with or without
@@ -76,6 +76,7 @@ def test_post_good_query_with_params(self, mocker):
7676

7777
select_stmt = "(call ResultTTree (call Select (call SelectMany (call EventDataset (list 'localds://did_01')" # noqa: E501
7878

79+
# Note we will ignore any image sent in the request!
7980
response = client.post("/servicex/generated-code", json={
8081
"transformer_image": "sslhep/servicex_func_adl_xaod_transformer:develop",
8182
"code": select_stmt
@@ -92,6 +93,7 @@ def test_post_good_query_with_params(self, mocker):
9293
print("Zip File: ", zip_file)
9394

9495
assert response.status_code == 200
96+
assert transformer_image == 'foo/bar:latest'
9597
check_zip_file(zip_file, 2)
9698
# Capture the temporary directory that was generated
9799
cache_dir = mock_ast_translator.generate_code.call_args[1]['cache_path']
@@ -137,6 +139,53 @@ def test_post_good_query_without_params(self, mocker):
137139
print("Zip File: ", zip_file)
138140

139141
assert response.status_code == 200
142+
assert transformer_image == 'sslhep/servicex_func_adl_xaod_transformer:develop'
143+
check_zip_file(zip_file, 2)
144+
# Capture the temporary directory that was generated
145+
cache_dir = mock_ast_translator.generate_code.call_args[1]['cache_path']
146+
mock_ast_translator.generate_code.assert_called_with(select_stmt,
147+
cache_path=cache_dir)
148+
149+
def test_post_good_query_with_params_and_image(self, mocker):
150+
"""Produce code for a simple good query"""
151+
152+
with TemporaryDirectory() as tempdir, \
153+
open(os.path.join(tempdir, "baz.txt"), 'w'), \
154+
open(os.path.join(tempdir, "foo.txt"), 'w'):
155+
156+
mock_ast_translator = mocker.Mock()
157+
mock_ast_translator.generate_code = mocker.Mock(
158+
return_value=GeneratedFileResult(hash="1234", output_dir=tempdir,
159+
image='sslhep/servicex_func_adl_xaod_transformer:develop')
160+
)
161+
162+
config = {
163+
'TARGET_BACKEND': 'uproot',
164+
'TRANSFORMER_SCIENCE_IMAGE': "foo/bar:latest"
165+
}
166+
app = create_app(config, provided_translator=mock_ast_translator)
167+
client = app.test_client()
168+
169+
print(app.config)
170+
171+
select_stmt = "(call ResultTTree (call Select (call SelectMany (call EventDataset (list 'localds://did_01')" # noqa: E501
172+
173+
response = client.post("/servicex/generated-code", json={
174+
"code": select_stmt
175+
})
176+
177+
boundary = response.data[2:34].decode('utf-8')
178+
content_type = f"multipart/form-data; boundary={boundary}"
179+
decoder_parts = decoder.MultipartDecoder(response.data, content_type)
180+
181+
transformer_image = str(decoder_parts.parts[0].content, 'utf-8')
182+
zip_file = decoder_parts.parts[3].content
183+
184+
print("Transformer Image: ", transformer_image)
185+
print("Zip File: ", zip_file)
186+
187+
assert response.status_code == 200
188+
assert transformer_image == 'sslhep/servicex_func_adl_xaod_transformer:develop'
140189
check_zip_file(zip_file, 2)
141190
# Capture the temporary directory that was generated
142191
cache_dir = mock_ast_translator.generate_code.call_args[1]['cache_path']

0 commit comments

Comments
 (0)