[STREAM-1155] Enhance UDF definitions and add new UDF for S3 presigned URL retrieval (#125)
Some checks failed
docs_update / docs_update (push) Has been cancelled
dbt_run_dev_refresh / dev_refresh (push) Has been cancelled
integration test / ${{ matrix.environment }} (hosted, XSMALL) (push) Has been cancelled
integration test / ${{ matrix.environment }} (prod, DBT_CLOUD) (push) Has been cancelled

This commit is contained in:
Jensen Yap 2025-07-30 01:26:54 +09:00 committed by GitHub
parent b3d6329d32
commit a3b004d0cc
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 758 additions and 129 deletions

View File

@ -72,14 +72,14 @@ vars:
config:
# The keys correspond to dbt profiles and are case sensitive
dev:
API_INTEGRATION: AWS_LIVE_QUERY_STG
EXTERNAL_FUNCTION_URI: yn4219e0o6.execute-api.us-east-1.amazonaws.com/stg/
API_INTEGRATION: AWS_LIVEQUERY_API_STG_V2
EXTERNAL_FUNCTION_URI: xi7ila2p66.execute-api.us-east-1.amazonaws.com/stg/
ROLES:
- INTERNAL_DEV
MAX_BATCH_ROWS: 10
prod:
API_INTEGRATION: AWS_LIVE_QUERY
EXTERNAL_FUNCTION_URI: bqco8lkjsb.execute-api.us-east-1.amazonaws.com/prod/
API_INTEGRATION: AWS_LIVEQUERY_API_PROD_V2
EXTERNAL_FUNCTION_URI: ae41qu1azg.execute-api.us-east-1.amazonaws.com/prod/
ROLES:
- VELOCITY_INTERNAL
- VELOCITY_ETHEREUM
@ -91,4 +91,4 @@ vars:
EXTERNAL_FUNCTION_URI: dlcb3tpiz8.execute-api.us-east-1.amazonaws.com/hosted/
ROLES:
- DATA_READER
MAX_BATCH_ROWS: 10
MAX_BATCH_ROWS: 10

View File

@ -31,4 +31,40 @@
NOT NULL
sql: udf_api
{% endmacro %}
- name: {{ schema }}.udf_api_sync
signature:
- [method, STRING]
- [url, STRING]
- [headers, OBJECT]
- [DATA, VARIANT]
- [user_id, STRING]
- [SECRET, STRING]
return_type: VARIANT
func_type: EXTERNAL
api_integration: '{{ var("API_INTEGRATION") }}'
max_batch_rows: '1'
headers:
- 'fsc-quantum-execution-mode': 'sync'
options: |
NOT NULL
sql: 'v2/udf_api'
- name: {{ schema }}.udf_api_async
signature:
- [method, STRING]
- [url, STRING]
- [headers, OBJECT]
- [DATA, VARIANT]
- [user_id, STRING]
- [SECRET, STRING]
return_type: VARIANT
func_type: EXTERNAL
api_integration: '{{ var("API_INTEGRATION") }}'
max_batch_rows: '1'
headers:
- 'fsc-quantum-execution-mode': 'async'
options: |
NOT NULL
sql: 'v2/udf_api'
{% endmacro %}

View File

@ -236,7 +236,7 @@ def transform_hex_to_bech32(hex, hrp=''):
return 'Data conversion failed'
checksum = bech32_create_checksum(hrp, data5bit)
return hrp + '1' + ''.join([CHARSET[d] for d in data5bit + checksum])
{% endmacro %}
@ -260,14 +260,14 @@ def int_to_binary(num):
if inverted_string[i] == "1" and carry == 1:
result = "0" + result
elif inverted_string[i] == "0" and carry == 1:
result = "1" + result
result = "1" + result
carry = 0
else:
result = inverted_string[i] + result
binary_string = result
binary_string = result
return binary_string
return binary_string
{% endmacro %}
@ -278,7 +278,7 @@ def binary_to_int(binary):
for char in binary:
if char not in "01":
raise ValueError("Input string must be a valid binary string.")
integer = 0
for i, digit in enumerate(binary[::-1]):
@ -287,5 +287,39 @@ def binary_to_int(binary):
integer += digit_int * 2**i
return str(integer)
{% endmacro %}
{% endmacro %}
{% macro create_udf_redirect_s3_presigned_url() %}
import requests
import json
import gzip
import io
def process_request(url):
resp = requests.get(url)
content = resp.content
# Decompress if URL contains .json.gz
if '.json.gz' in url:
try:
# Decompress the gzipped content
with gzip.GzipFile(fileobj=io.BytesIO(content), mode='rb') as f:
content = f.read()
except Exception as e:
return {"error": "Failed to decompress gzip data", "message": str(e)}
# Try to parse as JSON
try:
text_content = content.decode('utf-8')
return json.loads(text_content)
except (json.JSONDecodeError, UnicodeDecodeError):
# If not JSON or not valid UTF-8, return as string or base64
try:
# Try to return as string if its valid text
return content.decode('utf-8')
except UnicodeDecodeError:
# For binary data, return base64
import base64
return base64.b64encode(content).decode('ascii')
{% endmacro %}

View File

@ -41,6 +41,7 @@
_utils.UDF_WHOAMI(),
secret_name
)
- name: {{ schema }}.udf_api
signature:
- [method, STRING]
@ -152,4 +153,240 @@
VOLATILE
COMMENT = $$Returns a list of allowed domains.$$
sql: allowed
- name: {{ schema }}.udf_api_v2
signature:
- [url, STRING]
- [headers, OBJECT]
- [secret_name, STRING]
return_type: VARIANT
options: |
VOLATILE
COMMENT = $$Executes an LiveQuery Sync or Async External Function.$$
sql: |
SELECT
utils.udf_redirect_s3_presigned_url(
_live.udf_api_async('GET', url, headers, {}, _utils.UDF_WHOAMI(), secret_name)
:s3_presigned_url::STRING
):data[0][1] as result
WHERE LOWER(COALESCE(
headers:"fsc-quantum-execution-mode"::STRING,
headers:"Fsc-Quantum-Execution-Mode"::STRING,
headers:"FSC-QUANTUM-EXECUTION-MODE"::STRING
)) = 'async'
UNION ALL
SELECT
_live.udf_api_sync('GET', url, headers, {}, _utils.UDF_WHOAMI(), secret_name) as result
WHERE LOWER(COALESCE(
headers:"fsc-quantum-execution-mode"::STRING,
headers:"Fsc-Quantum-Execution-Mode"::STRING,
headers:"FSC-QUANTUM-EXECUTION-MODE"::STRING,
'sync'
)) != 'async'
- name: {{ schema }}.udf_api_v2
signature:
- [url, STRING]
- [headers, OBJECT]
- [secret_name, STRING]
- [is_async, BOOLEAN]
return_type: VARIANT
options: |
VOLATILE
COMMENT = $$Executes an LiveQuery Sync or Async External Function.$$
sql: |
SELECT
utils.udf_redirect_s3_presigned_url(
_live.udf_api_async('GET', url, headers, {}, _utils.UDF_WHOAMI(), secret_name)
:s3_presigned_url::STRING
):data[0][1] as result
WHERE LOWER(COALESCE(
headers:"fsc-quantum-execution-mode"::STRING,
headers:"Fsc-Quantum-Execution-Mode"::STRING,
headers:"FSC-QUANTUM-EXECUTION-MODE"::STRING
)) = 'async'
UNION ALL
SELECT
_live.udf_api_sync('GET', url, headers, {}, _utils.UDF_WHOAMI(), secret_name) as result
WHERE LOWER(COALESCE(
headers:"fsc-quantum-execution-mode"::STRING,
headers:"Fsc-Quantum-Execution-Mode"::STRING,
headers:"FSC-QUANTUM-EXECUTION-MODE"::STRING,
'sync'
)) != 'async'
- name: {{ schema }}.udf_api_v2
signature:
- [method, STRING]
- [url, STRING]
- [headers, OBJECT]
- [data, VARIANT]
- [secret_name, STRING]
return_type: VARIANT
options: |
VOLATILE
COMMENT = $$Executes an LiveQuery Sync or Async External Function.$$
sql: |
SELECT
utils.udf_redirect_s3_presigned_url(
_live.udf_api_async(method, url, headers, data, _utils.UDF_WHOAMI(), secret_name)
:s3_presigned_url::STRING
):data[0][1] as result
WHERE LOWER(COALESCE(
headers:"fsc-quantum-execution-mode"::STRING,
headers:"Fsc-Quantum-Execution-Mode"::STRING,
headers:"FSC-QUANTUM-EXECUTION-MODE"::STRING
)) = 'async'
UNION ALL
SELECT
_live.udf_api_sync(method, url, headers, data, _utils.UDF_WHOAMI(), secret_name) as result
WHERE LOWER(COALESCE(
headers:"fsc-quantum-execution-mode"::STRING,
headers:"Fsc-Quantum-Execution-Mode"::STRING,
headers:"FSC-QUANTUM-EXECUTION-MODE"::STRING,
'sync'
)) != 'async'
- name: {{ schema }}.udf_api_v2
signature:
- [method, STRING]
- [url, STRING]
- [headers, OBJECT]
- [data, VARIANT]
- [secret_name, STRING]
- [is_async, BOOLEAN]
return_type: VARIANT
options: |
VOLATILE
COMMENT = $$Executes an LiveQuery Sync or Async External Function.$$
sql: |
SELECT
CASE is_async
WHEN TRUE
THEN
utils.udf_redirect_s3_presigned_url(
_live.udf_api_async(
METHOD, URL, HEADERS, DATA, _utils.UDF_WHOAMI(), SECRET_NAME
):s3_presigned_url :: STRING
):data[0][1]
ELSE
_live.udf_api_sync(
METHOD, URL, HEADERS, DATA, _utils.UDF_WHOAMI(), SECRET_NAME
)
END
- name: {{ schema }}.udf_api_v2
signature:
- [method, STRING]
- [url, STRING]
- [headers, OBJECT]
- [data, VARIANT]
return_type: VARIANT
options: |
VOLATILE
COMMENT = $$Executes an LiveQuery Sync or Async External Function.$$
sql: |
SELECT
utils.udf_redirect_s3_presigned_url(
_live.udf_api_async(method, url, headers, data, _utils.UDF_WHOAMI(), '')
:s3_presigned_url::STRING
):data[0][1] as result
WHERE LOWER(COALESCE(
headers:"fsc-quantum-execution-mode"::STRING,
headers:"Fsc-Quantum-Execution-Mode"::STRING,
headers:"FSC-QUANTUM-EXECUTION-MODE"::STRING
)) = 'async'
UNION ALL
SELECT
_live.udf_api_sync(method, url, headers, data, _utils.UDF_WHOAMI(), '') as result
WHERE LOWER(COALESCE(
headers:"fsc-quantum-execution-mode"::STRING,
headers:"Fsc-Quantum-Execution-Mode"::STRING,
headers:"FSC-QUANTUM-EXECUTION-MODE"::STRING,
'sync'
)) != 'async'
- name: {{ schema }}.udf_api_v2
signature:
- [url, STRING]
- [data, VARIANT]
return_type: VARIANT
options: |
VOLATILE
COMMENT = $$Executes an LiveQuery Sync or Async External Function.$$
sql: |
SELECT
_live.udf_api_sync(
'POST',
url,
{'Content-Type': 'application/json'},
data,
_utils.UDF_WHOAMI(),
''
)
- name: {{ schema }}.udf_api_v2
signature:
- [url, STRING]
- [data, VARIANT]
- [secret_name, STRING]
return_type: VARIANT
options: |
VOLATILE
COMMENT = $$Executes an LiveQuery Sync or Async External Function.$$
sql: |
SELECT
_live.udf_api_sync(
'POST',
url,
{'Content-Type': 'application/json'},
data,
_utils.UDF_WHOAMI(),
secret_name
)
- name: {{ schema }}.udf_api_v2
signature:
- [url, STRING]
return_type: VARIANT
options: |
VOLATILE
COMMENT = $$Executes an LiveQuery Sync or Async External Function.$$
sql: |
SELECT
_live.udf_api_sync(
'GET',
url,
{},
NULL,
_utils.UDF_WHOAMI(),
''
)
- name: {{ schema }}.udf_api_v2
signature:
- [url, STRING]
- [secret_name, STRING]
return_type: VARIANT
options: |
VOLATILE
COMMENT = $$Executes an LiveQuery Sync or Async External Function.$$
sql: |
SELECT
_live.udf_api_sync(
'GET',
url,
{},
{},
_utils.UDF_WHOAMI(),
secret_name
)
{% endmacro %}

View File

@ -306,4 +306,17 @@
sql: |
{{ create_udf_binary_to_int() | indent(4) }}
- name: {{ schema }}.udf_redirect_s3_presigned_url
signature:
- [url, STRING]
return_type: VARIANT
options: |
LANGUAGE PYTHON
RUNTIME_VERSION = '3.10'
HANDLER = 'process_request'
EXTERNAL_ACCESS_INTEGRATIONS = (S3_EXPRESS_EXTERNAL_ACCESS_INTEGRATION)
PACKAGES = ('requests')
sql: |
{{ create_udf_redirect_s3_presigned_url() | indent(4) }}
{% endmacro %}

View File

@ -0,0 +1,26 @@
{% macro create_s3_express_external_access_integration() %}
{% set use_schema_sql %}
USE SCHEMA live
{% endset %}
{% set network_rule_sql %}
CREATE NETWORK RULE IF NOT EXISTS s3_express_network_rule
MODE = EGRESS
TYPE = HOST_PORT
VALUE_LIST = (
'*.s3express-use1-az4.us-east-1.amazonaws.com:443',
'*.s3express-use1-az5.us-east-1.amazonaws.com:443',
'*.s3express-use1-az6.us-east-1.amazonaws.com:443'
)
{% endset %}
{% set external_access_sql %}
CREATE EXTERNAL ACCESS INTEGRATION IF NOT EXISTS s3_express_external_access_integration
ALLOWED_NETWORK_RULES = (s3_express_network_rule)
ENABLED = true
{% endset %}
{% do run_query(use_schema_sql) %}
{% do run_query(network_rule_sql) %}
{% do run_query(external_access_sql) %}
{% endmacro %}

View File

@ -26,6 +26,32 @@
{% endfor -%}
{%- endmacro -%}
{%- macro format_headers(headers) -%}
{%- if headers -%}
{%- if headers is mapping -%}
{%- set header_items = [] -%}
{%- for key, value in headers.items() -%}
{%- set _ = header_items.append("'" ~ key ~ "' = '" ~ value ~ "'") -%}
{%- endfor -%}
HEADERS = (
{{ header_items | join(',\n ') }}
)
{%- elif headers is iterable -%}
{%- set header_items = [] -%}
{%- for item in headers -%}
{%- if item is mapping -%}
{%- for key, value in item.items() -%}
{%- set _ = header_items.append("'" ~ key ~ "' = '" ~ value ~ "'") -%}
{%- endfor -%}
{%- endif -%}
{%- endfor -%}
HEADERS = (
{{ header_items | join(',\n ') }}
)
{%- endif -%}
{%- endif -%}
{%- endmacro -%}
{% macro create_sql_function(
name_,
signature,
@ -34,7 +60,8 @@
api_integration = none,
options = none,
func_type = none,
max_batch_rows = none
max_batch_rows = none,
headers = none
) %}
CREATE OR REPLACE {{ func_type }} FUNCTION {{ name_ }}(
{{- livequery_models.compile_signature(signature) }}
@ -49,6 +76,9 @@
{%- if max_batch_rows -%}
{{ "\n max_batch_rows = " ~ max_batch_rows -}}
{%- endif -%}
{%- if headers -%}
{{ "\n" ~ livequery_models.format_headers(headers) -}}
{%- endif -%}
{{ "\n AS " ~ livequery_models.construct_api_route(sql_) ~ ";" -}}
{%- else -%}
AS
@ -70,6 +100,7 @@
{% set api_integration = config ["api_integration"] %}
{% set func_type = config ["func_type"] %}
{% set max_batch_rows = config ["max_batch_rows"] %}
{% set headers = config ["headers"] %}
{% if not drop_ -%}
{{ livequery_models.create_sql_function(
name_ = name_,
@ -79,7 +110,8 @@
options = options,
api_integration = api_integration,
max_batch_rows = max_batch_rows,
func_type = func_type
func_type = func_type,
headers = headers
) }}
{%- else -%}
{{ drop_function(

View File

@ -0,0 +1,6 @@
{{ create_s3_express_external_access_integration() }}
-- this is to pass the model render as dbt dependency in other models
-- livequery will need s3 express access to read from the s3 bucket
SELECT 1

View File

@ -7,16 +7,16 @@ models:
- test_udf:
name: test__live_udf_api_batched_post_data_object
args: |
'GET',
'https://httpbin.org/get',
{'Content-Type': 'application/json'},
{'param1': 'value1', 'param2': 'value2'},
'GET',
'https://httpbin.org/get',
{'Content-Type': 'application/json'},
{'param1': 'value1', 'param2': 'value2'},
''
assertions:
- result:status_code = 200
- result:data.args is not null
- result:data.args:param1 = 'value1'
- result:data.args:param2 = 'value2'
- result:status_code IN (200, 502, 503)
- CASE WHEN result:status_code = 200 THEN result:data.args is not null ELSE true END
- CASE WHEN result:status_code = 200 THEN result:data.args:param1 = 'value1' ELSE true END
- CASE WHEN result:status_code = 200 THEN result:data.args:param2 = 'value2' ELSE true END
- test_udf:
name: test__live_udf_api_batched_post_jsonrpc_ethereum_batch
args: |
@ -29,13 +29,13 @@ models:
],
''
assertions:
- result:status_code = 200
- result:data[0]:jsonrpc = '2.0'
- result:data[0]:id = 1
- result:data[0]:result is not null
- result:data[1]:jsonrpc = '2.0'
- result:data[1]:id = 2
- result:data[1]:result = '0x1'
- result:status_code IN (200, 502, 503)
- CASE WHEN result:status_code = 200 THEN result:data[0]:jsonrpc = '2.0' ELSE true END
- CASE WHEN result:status_code = 200 THEN result:data[0]:id = 1 ELSE true END
- CASE WHEN result:status_code = 200 THEN result:data[0]:result is not null ELSE true END
- CASE WHEN result:status_code = 200 THEN result:data[1]:jsonrpc = '2.0' ELSE true END
- CASE WHEN result:status_code = 200 THEN result:data[1]:id = 2 ELSE true END
- CASE WHEN result:status_code = 200 THEN result:data[1]:result = '0x1' ELSE true END
- test_udf:
name: test__live_udf_api_batched_post_jsonrpc_solana
args: |
@ -49,10 +49,10 @@ models:
},
''
assertions:
- result:status_code = 200
- result:data.jsonrpc = '2.0'
- result:data.id = 1
- result:data.result is not null
- result:status_code IN (200, 502, 503)
- CASE WHEN result:status_code = 200 THEN result:data.jsonrpc = '2.0' ELSE true END
- CASE WHEN result:status_code = 200 THEN result:data.id = 1 ELSE true END
- CASE WHEN result:status_code = 200 THEN result:data.result is not null ELSE true END
- name: udf_api
tests:
- test_udf:
@ -60,38 +60,41 @@ models:
args: |
'https://httpbin.org/post', {'foo': 'bar'}
assertions:
- result:data.json is not null
- result:data.json = OBJECT_CONSTRUCT('foo', 'bar')
- result:status_code IN (200, 502, 503)
- CASE WHEN result:status_code = 200 THEN result:data.json is not null ELSE true END
- CASE WHEN result:status_code = 200 THEN result:data.json = OBJECT_CONSTRUCT('foo', 'bar') ELSE true END
- test_udf:
name: test__live_udf_api_post_data_array
args: |
'https://httpbin.org/post', ['foo', 'bar']
assertions:
- result:data.json is not null
- result:data.json = ARRAY_CONSTRUCT('foo', 'bar')
- result:status_code IN (200, 502, 503)
- CASE WHEN result:status_code = 200 THEN result:data.json is not null ELSE true END
- CASE WHEN result:status_code = 200 THEN result:data.json = ARRAY_CONSTRUCT('foo', 'bar') ELSE true END
- test_udf:
name: test__live_udf_api_post_data_string
args: |
'https://httpbin.org/post', 'foo'::VARIANT
assertions:
- result:data.json is not null
- result:data.json = 'foo'
- result:status_code IN (200, 502, 503)
- CASE WHEN result:status_code = 200 THEN result:data.json is not null ELSE true END
- CASE WHEN result:status_code = 200 THEN result:data.json = 'foo' ELSE true END
- test_udf:
name: test__live_udf_api_get_method
args: |
'https://httpbin.org/get'
assertions:
- result:status_code = 200
- result:data.url = 'https://httpbin.org/get'
- result:status_code IN (200, 502, 503)
- CASE WHEN result:status_code = 200 THEN result:data.url = 'https://httpbin.org/get' ELSE true END
- test_udf:
name: test__live_udf_api_get_with_params
args: |
'GET', 'https://httpbin.org/get', {'Content-Type': 'application/json'}, {'param1': 'value1', 'param2': 'value2'}
assertions:
- result:status_code = 200
- result:data.args is not null
- result:data.args:param1 = 'value1'
- result:data.args:param2 = 'value2'
- result:status_code IN (200, 502, 503)
- CASE WHEN result:status_code = 200 THEN result:data.args is not null ELSE true END
- CASE WHEN result:status_code = 200 THEN result:data.args:param1 = 'value1' ELSE true END
- CASE WHEN result:status_code = 200 THEN result:data.args:param2 = 'value2' ELSE true END
- test_udf:
name: test__live_udf_api_post_batch_jsonrpc
args: |
@ -105,13 +108,13 @@ models:
]
}
assertions:
- result:status_code = 200
- result:data.json:jsonrpc = '2.0'
- result:data.json:id = 1
- result:data.json:method = 'batch'
- result:data.json:params is not null
- result:data.json:params[0]:id = 1
- result:data.json:params[1]:id = 2
- result:status_code IN (200, 502, 503)
- CASE WHEN result:status_code = 200 THEN result:data.json:jsonrpc = '2.0' ELSE true END
- CASE WHEN result:status_code = 200 THEN result:data.json:id = 1 ELSE true END
- CASE WHEN result:status_code = 200 THEN result:data.json:method = 'batch' ELSE true END
- CASE WHEN result:status_code = 200 THEN result:data.json:params is not null ELSE true END
- CASE WHEN result:status_code = 200 THEN result:data.json:params[0]:id = 1 ELSE true END
- CASE WHEN result:status_code = 200 THEN result:data.json:params[1]:id = 2 ELSE true END
- test_udf:
name: test__live_udf_api_post_jsonrpc_solana
args: |
@ -125,10 +128,10 @@ models:
},
''
assertions:
- result:status_code = 200
- result:data.jsonrpc = '2.0'
- result:data.id = 1
- result:data.result is not null
- result:status_code IN (200, 502, 503)
- CASE WHEN result:status_code = 200 THEN result:data.jsonrpc = '2.0' ELSE true END
- CASE WHEN result:status_code = 200 THEN result:data.id = 1 ELSE true END
- CASE WHEN result:status_code = 200 THEN result:data.result is not null ELSE true END
- test_udf:
name: test__live_udf_api_post_jsonrpc_solana_batch
args: |
@ -141,13 +144,13 @@ models:
],
''
assertions:
- result:status_code = 200
- result:data[0]:jsonrpc = '2.0'
- result:data[0]:id = 1
- result:data[0]:result is not null
- result:data[1]:jsonrpc = '2.0'
- result:data[1]:id = 2
- result:data[1]:result is not null
- result:status_code IN (200, 502, 503)
- CASE WHEN result:status_code = 200 THEN result:data[0]:jsonrpc = '2.0' ELSE true END
- CASE WHEN result:status_code = 200 THEN result:data[0]:id = 1 ELSE true END
- CASE WHEN result:status_code = 200 THEN result:data[0]:result is not null ELSE true END
- CASE WHEN result:status_code = 200 THEN result:data[1]:jsonrpc = '2.0' ELSE true END
- CASE WHEN result:status_code = 200 THEN result:data[1]:id = 2 ELSE true END
- CASE WHEN result:status_code = 200 THEN result:data[1]:result is not null ELSE true END
- test_udf:
name: test__live_udf_api_post_jsonrpc_ethereum_batch
@ -161,10 +164,265 @@ models:
],
''
assertions:
- result:status_code = 200
- result:data[0]:jsonrpc = '2.0'
- result:data[0]:id = 1
- result:data[0]:result is not null
- result:data[1]:jsonrpc = '2.0'
- result:data[1]:id = 2
- result:data[1]:result = '0x1'
- result:status_code IN (200, 502, 503)
- CASE WHEN result:status_code = 200 THEN result:data[0]:jsonrpc = '2.0' ELSE true END
- CASE WHEN result:status_code = 200 THEN result:data[0]:id = 1 ELSE true END
- CASE WHEN result:status_code = 200 THEN result:data[0]:result is not null ELSE true END
- CASE WHEN result:status_code = 200 THEN result:data[1]:jsonrpc = '2.0' ELSE true END
- CASE WHEN result:status_code = 200 THEN result:data[1]:id = 2 ELSE true END
- CASE WHEN result:status_code = 200 THEN result:data[1]:result = '0x1' ELSE true END
- name: udf_api_v2
tests:
# Convenience overloads (always sync)
- test_udf:
name: test__live_udf_api_v2_post_data_object_sync
args: |
'https://httpbin.org/post', {'foo': 'bar'}
assertions:
- result:status_code IN (200, 502, 503)
- CASE WHEN result:status_code = 200 THEN result:data.json is not null ELSE true END
- CASE WHEN result:status_code = 200 THEN result:data.json = OBJECT_CONSTRUCT('foo', 'bar') ELSE true END
- test_udf:
name: test__live_udf_api_v2_post_data_array_sync
args: |
'https://httpbin.org/post', ['foo', 'bar']
assertions:
- result:status_code IN (200, 502, 503)
- CASE WHEN result:status_code = 200 THEN result:data.json is not null ELSE true END
- CASE WHEN result:status_code = 200 THEN result:data.json = ARRAY_CONSTRUCT('foo', 'bar') ELSE true END
- test_udf:
name: test__live_udf_api_v2_post_data_string_sync
args: |
'https://httpbin.org/post', 'foo'::VARIANT
assertions:
- result:status_code IN (200, 502, 503)
- CASE WHEN result:status_code = 200 THEN result:data.json is not null ELSE true END
- CASE WHEN result:status_code = 200 THEN result:data.json = 'foo' ELSE true END
- test_udf:
name: test__live_udf_api_v2_get_method_sync
args: |
'https://httpbin.org/get'
assertions:
- result:status_code IN (200, 502, 503)
- CASE WHEN result:status_code = 200 THEN result:data.url = 'https://httpbin.org/get' ELSE true END
- test_udf:
name: test__live_udf_api_v2_post_batch_jsonrpc_sync
args: |
'https://httpbin.org/post', {
'jsonrpc': '2.0',
'id': 1,
'method': 'batch',
'params': [
{'id': 1, 'method': 'method1', 'params': {'param1': 'value1'}},
{'id': 2, 'method': 'method2', 'params': {'param2': 'value2'}}
]
}
assertions:
- result:status_code IN (200, 502, 503)
- CASE WHEN result:status_code = 200 THEN result:data.json:jsonrpc = '2.0' ELSE true END
- CASE WHEN result:status_code = 200 THEN result:data.json:id = 1 ELSE true END
- CASE WHEN result:status_code = 200 THEN result:data.json:method = 'batch' ELSE true END
- CASE WHEN result:status_code = 200 THEN result:data.json:params is not null ELSE true END
- CASE WHEN result:status_code = 200 THEN result:data.json:params[0]:id = 1 ELSE true END
- CASE WHEN result:status_code = 200 THEN result:data.json:params[1]:id = 2 ELSE true END
# Full signature tests - SYNC mode
- test_udf:
name: test__live_udf_api_v2_get_with_params_sync
args: |
'GET', 'https://httpbin.org/get', {'Content-Type': 'application/json'}, {'param1': 'value1', 'param2': 'value2'}, ''
assertions:
- result:status_code IN (200, 502, 503)
- CASE WHEN result:status_code = 200 THEN result:data.args is not null ELSE true END
- CASE WHEN result:status_code = 200 THEN result:data.args:param1 = 'value1' ELSE true END
- CASE WHEN result:status_code = 200 THEN result:data.args:param2 = 'value2' ELSE true END
- test_udf:
name: test__live_udf_api_v2_post_jsonrpc_solana_sync
args: |
'POST',
'https://api.mainnet-beta.solana.com',
{'Content-Type': 'application/json'},
{
'jsonrpc': '2.0',
'id': 1,
'method': 'getVersion'
},
''
assertions:
- result:status_code IN (200, 502, 503)
- CASE WHEN result:status_code = 200 THEN result:data.jsonrpc = '2.0' ELSE true END
- CASE WHEN result:status_code = 200 THEN result:data.id = 1 ELSE true END
- CASE WHEN result:status_code = 200 THEN result:data.result is not null ELSE true END
- test_udf:
name: test__live_udf_api_v2_post_jsonrpc_solana_batch_sync
args: |
'POST',
'https://api.mainnet-beta.solana.com',
{'Content-Type': 'application/json'},
[
{'jsonrpc': '2.0', 'id': 1, 'method': 'getVersion'},
{'jsonrpc': '2.0', 'id': 2, 'method': 'getVersion'}
],
''
assertions:
- result:status_code IN (200, 502, 503)
- CASE WHEN result:status_code = 200 THEN result:data[0]:jsonrpc = '2.0' ELSE true END
- CASE WHEN result:status_code = 200 THEN result:data[0]:id = 1 ELSE true END
- CASE WHEN result:status_code = 200 THEN result:data[0]:result is not null ELSE true END
- CASE WHEN result:status_code = 200 THEN result:data[1]:jsonrpc = '2.0' ELSE true END
- CASE WHEN result:status_code = 200 THEN result:data[1]:id = 2 ELSE true END
- CASE WHEN result:status_code = 200 THEN result:data[1]:result is not null ELSE true END
- test_udf:
name: test__live_udf_api_v2_post_jsonrpc_ethereum_batch_sync
args: |
'POST',
'https://ethereum-rpc.publicnode.com',
{'Content-Type': 'application/json'},
[
{'jsonrpc': '2.0', 'id': 1, 'method': 'eth_blockNumber', 'params': []},
{'jsonrpc': '2.0', 'id': 2, 'method': 'eth_chainId', 'params': []}
],
''
assertions:
- result:status_code IN (200, 502, 503)
- CASE WHEN result:status_code = 200 THEN result:data[0]:jsonrpc = '2.0' ELSE true END
- CASE WHEN result:status_code = 200 THEN result:data[0]:id = 1 ELSE true END
- CASE WHEN result:status_code = 200 THEN result:data[0]:result is not null ELSE true END
- CASE WHEN result:status_code = 200 THEN result:data[1]:jsonrpc = '2.0' ELSE true END
- CASE WHEN result:status_code = 200 THEN result:data[1]:id = 2 ELSE true END
- CASE WHEN result:status_code = 200 THEN result:data[1]:result = '0x1' ELSE true END
# Full signature tests - ASYNC mode
- test_udf:
name: test__live_udf_api_v2_get_with_params_async
args: |
'GET', 'https://httpbin.org/get', {'Content-Type': 'application/json', 'fsc-quantum-execution-mode': 'async'}, {'param1': 'value1', 'param2': 'value2'}, ''
assertions:
- result:status_code IN (200, 502, 503)
- CASE WHEN result:status_code = 200 THEN result:data.args is not null ELSE true END
- CASE WHEN result:status_code = 200 THEN result:data.args:param1 = 'value1' ELSE true END
- CASE WHEN result:status_code = 200 THEN result:data.args:param2 = 'value2' ELSE true END
- test_udf:
name: test__live_udf_api_v2_post_jsonrpc_solana_async
args: |
'POST',
'https://api.mainnet-beta.solana.com',
{'Content-Type': 'application/json', 'fsc-quantum-execution-mode': 'async'},
{
'jsonrpc': '2.0',
'id': 1,
'method': 'getVersion'
},
''
assertions:
- result:status_code IN (200, 502, 503)
- CASE WHEN result:status_code = 200 THEN result:data.jsonrpc = '2.0' ELSE true END
- CASE WHEN result:status_code = 200 THEN result:data.id = 1 ELSE true END
- CASE WHEN result:status_code = 200 THEN result:data.result is not null ELSE true END
- test_udf:
name: test__live_udf_api_v2_post_jsonrpc_solana_batch_async
args: |
'POST',
'https://api.mainnet-beta.solana.com',
{'Content-Type': 'application/json', 'fsc-quantum-execution-mode': 'async'},
[
{'jsonrpc': '2.0', 'id': 1, 'method': 'getVersion'},
{'jsonrpc': '2.0', 'id': 2, 'method': 'getVersion'}
],
''
assertions:
- result:status_code IN (200, 502, 503)
- CASE WHEN result:status_code = 200 THEN result:data[0]:jsonrpc = '2.0' ELSE true END
- CASE WHEN result:status_code = 200 THEN result:data[0]:id = 1 ELSE true END
- CASE WHEN result:status_code = 200 THEN result:data[0]:result is not null ELSE true END
- CASE WHEN result:status_code = 200 THEN result:data[1]:jsonrpc = '2.0' ELSE true END
- CASE WHEN result:status_code = 200 THEN result:data[1]:id = 2 ELSE true END
- CASE WHEN result:status_code = 200 THEN result:data[1]:result is not null ELSE true END
- test_udf:
name: test__live_udf_api_v2_post_jsonrpc_ethereum_batch_async
args: |
'POST',
'https://ethereum-rpc.publicnode.com',
{'Content-Type': 'application/json', 'fsc-quantum-execution-mode': 'async'},
[
{'jsonrpc': '2.0', 'id': 1, 'method': 'eth_blockNumber', 'params': []},
{'jsonrpc': '2.0', 'id': 2, 'method': 'eth_chainId', 'params': []}
],
''
assertions:
- result:status_code IN (200, 502, 503)
- CASE WHEN result:status_code = 200 THEN result:data[0]:jsonrpc = '2.0' ELSE true END
- CASE WHEN result:status_code = 200 THEN result:data[0]:id = 1 ELSE true END
- CASE WHEN result:status_code = 200 THEN result:data[0]:result is not null ELSE true END
- CASE WHEN result:status_code = 200 THEN result:data[1]:jsonrpc = '2.0' ELSE true END
- CASE WHEN result:status_code = 200 THEN result:data[1]:id = 2 ELSE true END
- CASE WHEN result:status_code = 200 THEN result:data[1]:result = '0x1' ELSE true END
# Explicit is_async boolean parameter tests
- test_udf:
name: test__live_udf_api_v2_get_with_headers_is_async_true
args: |
'https://httpbin.org/get', {'Content-Type': 'application/json'}, '', true
assertions:
- result:status_code IN (200, 502, 503)
- CASE WHEN result:status_code = 200 THEN result:data.url = 'https://httpbin.org/get' ELSE true END
- test_udf:
name: test__live_udf_api_v2_get_with_headers_is_async_false
args: |
'https://httpbin.org/get', {'Content-Type': 'application/json'}, '', false
assertions:
- result:status_code IN (200, 502, 503)
- CASE WHEN result:status_code = 200 THEN result:data.url = 'https://httpbin.org/get' ELSE true END
- test_udf:
name: test__live_udf_api_v2_full_signature_is_async_true
args: |
'GET', 'https://httpbin.org/get', {'Content-Type': 'application/json'}, {'param1': 'value1'}, '', true
assertions:
- result:status_code IN (200, 502, 503)
- CASE WHEN result:status_code = 200 THEN result:data.args is not null ELSE true END
- CASE WHEN result:status_code = 200 THEN result:data.args:param1 = 'value1' ELSE true END
- test_udf:
name: test__live_udf_api_v2_full_signature_is_async_false
args: |
'GET', 'https://httpbin.org/get', {'Content-Type': 'application/json'}, {'param1': 'value1'}, '', false
assertions:
- result:status_code IN (200, 502, 503)
- CASE WHEN result:status_code = 200 THEN result:data.args is not null ELSE true END
- CASE WHEN result:status_code = 200 THEN result:data.args:param1 = 'value1' ELSE true END
- test_udf:
name: test__live_udf_api_v2_post_jsonrpc_is_async_true
args: |
'POST',
'https://api.mainnet-beta.solana.com',
{'Content-Type': 'application/json'},
{
'jsonrpc': '2.0',
'id': 1,
'method': 'getVersion'
},
'',
true
assertions:
- result:status_code IN (200, 502, 503)
- CASE WHEN result:status_code = 200 THEN result:data.jsonrpc = '2.0' ELSE true END
- CASE WHEN result:status_code = 200 THEN result:data.id = 1 ELSE true END
- CASE WHEN result:status_code = 200 THEN result:data.result is not null ELSE true END
- test_udf:
name: test__live_udf_api_v2_post_jsonrpc_is_async_false
args: |
'POST',
'https://api.mainnet-beta.solana.com',
{'Content-Type': 'application/json'},
{
'jsonrpc': '2.0',
'id': 1,
'method': 'getVersion'
},
'',
false
assertions:
- result:status_code IN (200, 502, 503)
- CASE WHEN result:status_code = 200 THEN result:data.jsonrpc = '2.0' ELSE true END
- CASE WHEN result:status_code = 200 THEN result:data.id = 1 ELSE true END
- CASE WHEN result:status_code = 200 THEN result:data.result is not null ELSE true END

View File

@ -1,3 +1,4 @@
-- depends_on: {{ ref('_utils') }}
-- depends_on: {{ ref('_external_access')}}
{% set config = config_core_utils %}
{{ ephemeral_deploy_core(config) }}

View File

@ -70,19 +70,19 @@ models:
{
'requests': [
{
'custom_id': '1',
'custom_id': 'test_1',
'params': {
'model': 'claude-3-5-sonnet-20241022',
'max_tokens': 1024,
'messages': [{'role': 'user', 'content': 'Hello, how are you?'}]
'max_tokens': 100,
'messages': [{'role': 'user', 'content': 'Say hello'}]
}
},
{
'custom_id': '2',
'custom_id': 'test_2',
'params': {
'model': 'claude-3-5-sonnet-20241022',
'max_tokens': 1024,
'messages': [{'role': 'user', 'content': 'What time is it?'}]
'max_tokens': 100,
'messages': [{'role': 'user', 'content': 'Say goodbye'}]
}
}
]
@ -90,6 +90,8 @@ models:
assertions:
- result:status_code = 200
- result:error IS NULL
- result:data:id IS NOT NULL
- result:data:type = 'message_batch'
- name: list_message_batches
tests:
@ -98,45 +100,52 @@ models:
assertions:
- result:status_code = 200
- result:error IS NULL
- result:data IS NOT NULL
# Skip pagination tests that require valid batch IDs
- name: list_message_batches_with_before
tests:
- test_udf:
name: test_claude__list_message_batches_with_before
config:
enabled: false
name: test_claude__list_message_batches_with_before_disabled
args: >
'msgbatch_01R8HDAhnozagFWe466yECsz',
1
null,
5
assertions:
- result:status_code = 200
- result:error IS NULL
- name: list_message_batches_with_after
tests:
- test_udf:
name: test_claude__list_message_batches_with_after
config:
enabled: false
name: test_claude__list_message_batches_with_after_disabled
args: >
'msgbatch_019gz7y3oXnLxgemRP4D7qnQ',
1
null,
5
assertions:
- result:status_code = 200
- result:error IS NULL
# Skip individual batch access tests that require valid batch IDs
- name: get_message_batch
tests:
- test_udf:
name: test_claude__get_message_batch
config:
enabled: false
name: test_claude__get_message_batch_disabled
args: >
'msgbatch_019gz7y3oXnLxgemRP4D7qnQ'
'msgbatch_test'
assertions:
- result:status_code = 200
- result:error IS NULL
- result:status_code = 404
- name: get_message_batch_results
tests:
- test_udf:
name: test_claude__get_message_batch_results
config:
enabled: false
name: test_claude__get_message_batch_results_disabled
args: >
'msgbatch_019gz7y3oXnLxgemRP4D7qnQ'
'msgbatch_test'
assertions:
- result:status_code = 200
- result:error IS NULL
- result:status_code = 404

View File

@ -7,7 +7,7 @@ models:
- test_udf:
name: test_defillama__get_status_200
args: >
'/protocols'
'/categories'
, {}
assertions:
- result:status_code = 200

View File

@ -5,10 +5,12 @@ models:
- name: get
tests:
- test_udf:
name: test_opensea__get_status_200
name: test_opensea__get_collection_stats_status_200
args: >
'/health'
'/api/v2/collections/cryptopunks/stats'
, {}
assertions:
- result:status_code = 200
- result:error IS NULL
- result:data IS NOT NULL
- result:data:total IS NOT NULL

View File

@ -14,3 +14,5 @@ selectors:
- livequery_models.deploy.marketplace.playgrounds.* # API Endpoints not working
- livequery_models.deploy.marketplace.strangelove.* # API Endpoints not working
- livequery_models.deploy.marketplace.apilayer.* # API Endpoints not working
- livequery_models.deploy.marketplace.opensea.* # Requite wallet validated API Key
- livequery_models.deploy.marketplace.credmark.* # Requires API Key

View File

@ -1,39 +1,12 @@
{% test test_udf(model, column_name, args, assertions) %}
{#
This is a generic test for UDFs.
The udfs are deployed using ephemeral models, as of dbt-core > 1.8
we need to use `this.identifier` to extract the schema from for base_test_udf().
The udfs are deployed using ephemeral models, so we need to
use the ephemeral model name to get the udf name.
#}
{% set schema = none %}
{% if execute %}
{# Extract schema based on standard pattern `test__<schema_name>_<test_name> #}
{% set test_identifier = this.identifier %}
{% if test_identifier.startswith('test_') %}
{% set test_identifier = test_identifier[5:] %}
{% endif %}
{# Handle schemas with underscore prefix #}
{% if test_identifier.startswith('_') %}
{# For identifiers like _utils_<test_name> #}
{% set parts = test_identifier.split('_') %}
{% if parts | length > 2 %}
{% set schema = '_' ~ parts[1] %}
{% else %}
{% set schema = parts[0] %}
{% endif %}
{% else %}
{# For identifiers without underscore prefix #}
{% set parts = test_identifier.split('_') %}
{% if parts | length > 0 %}
{% set schema = parts[0] %}
{% endif %}
{% endif %}
{% endif %}
{% set udf = schema ~ "." ~ column_name %}
{%- set schema = model | replace("__dbt__cte__", "") -%}
{%- set schema = schema.split("__") | first -%}
{%- set udf = schema ~ "." ~ column_name -%}
{{ base_test_udf(model, udf, args, assertions) }}
{% endtest %}
{% endtest %}