UncountablePythonSDK 0.0.113__py3-none-any.whl → 0.0.115__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of UncountablePythonSDK might be problematic. Click here for more details.

Files changed (36) hide show
  1. examples/integration-server/jobs/materials_auto/concurrent_cron.py +11 -0
  2. examples/integration-server/jobs/materials_auto/example_http.py +35 -0
  3. examples/integration-server/jobs/materials_auto/profile.yaml +25 -0
  4. pkgs/argument_parser/argument_parser.py +8 -3
  5. pkgs/type_spec/builder.py +8 -2
  6. pkgs/type_spec/non_discriminated_union_exceptions.py +14 -0
  7. pkgs/type_spec/parts/base.py.prepart +5 -8
  8. pkgs/type_spec/ui_entry_actions/generate_ui_entry_actions.py +18 -5
  9. pkgs/type_spec/value_spec/__main__.py +2 -2
  10. uncountable/core/environment.py +1 -1
  11. uncountable/integration/http_server/__init__.py +5 -0
  12. uncountable/integration/http_server/types.py +67 -0
  13. uncountable/integration/job.py +129 -5
  14. uncountable/integration/queue_runner/job_scheduler.py +10 -2
  15. uncountable/integration/server.py +2 -2
  16. uncountable/integration/telemetry.py +1 -1
  17. uncountable/integration/webhook_server/entrypoint.py +37 -112
  18. uncountable/types/__init__.py +6 -0
  19. uncountable/types/api/entity/export_entities.py +46 -0
  20. uncountable/types/api/entity/lookup_entity.py +15 -1
  21. uncountable/types/api/recipes/create_mix_order.py +44 -0
  22. uncountable/types/api/recipes/set_recipe_outputs.py +1 -0
  23. uncountable/types/async_batch_processor.py +34 -0
  24. uncountable/types/async_batch_t.py +1 -0
  25. uncountable/types/base_t.py +5 -8
  26. uncountable/types/client_base.py +49 -0
  27. uncountable/types/entity_t.py +3 -1
  28. uncountable/types/exports.py +8 -0
  29. uncountable/types/exports_t.py +33 -0
  30. uncountable/types/integration_server_t.py +2 -0
  31. uncountable/types/job_definition.py +2 -0
  32. uncountable/types/job_definition_t.py +26 -2
  33. {uncountablepythonsdk-0.0.113.dist-info → uncountablepythonsdk-0.0.115.dist-info}/METADATA +1 -1
  34. {uncountablepythonsdk-0.0.113.dist-info → uncountablepythonsdk-0.0.115.dist-info}/RECORD +36 -27
  35. {uncountablepythonsdk-0.0.113.dist-info → uncountablepythonsdk-0.0.115.dist-info}/WHEEL +1 -1
  36. {uncountablepythonsdk-0.0.113.dist-info → uncountablepythonsdk-0.0.115.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,11 @@
1
+ import time
2
+
3
+ from uncountable.integration.job import CronJob, JobArguments, register_job
4
+ from uncountable.types.job_definition_t import JobResult
5
+
6
+
7
+ @register_job
8
+ class MyConcurrentCronJob(CronJob):
9
+ def run(self, args: JobArguments) -> JobResult:
10
+ time.sleep(10)
11
+ return JobResult(success=True)
@@ -0,0 +1,35 @@
1
+ from dataclasses import dataclass
2
+
3
+ from uncountable.integration.http_server import (
4
+ GenericHttpRequest,
5
+ GenericHttpResponse,
6
+ )
7
+ from uncountable.integration.job import CustomHttpJob, register_job
8
+ from uncountable.types import job_definition_t
9
+
10
+
11
+ @dataclass(kw_only=True)
12
+ class ExampleWebhookPayload:
13
+ id: int
14
+ message: str
15
+
16
+
17
+ @register_job
18
+ class HttpExample(CustomHttpJob):
19
+ @staticmethod
20
+ def validate_request(
21
+ *,
22
+ request: GenericHttpRequest,
23
+ job_definition: job_definition_t.HttpJobDefinitionBase,
24
+ profile_meta: job_definition_t.ProfileMetadata,
25
+ ) -> None:
26
+ return None
27
+
28
+ @staticmethod
29
+ def handle_request(
30
+ *,
31
+ request: GenericHttpRequest,
32
+ job_definition: job_definition_t.HttpJobDefinitionBase,
33
+ profile_meta: job_definition_t.ProfileMetadata,
34
+ ) -> GenericHttpResponse:
35
+ return GenericHttpResponse(response="OK", status_code=200)
@@ -22,6 +22,25 @@ jobs:
22
22
  - admin:
23
23
  type: ref_name
24
24
  ref_name: admin
25
+ - id: concurrent_cron_1
26
+ enabled: true
27
+ type: cron
28
+ name: MyConcurrentCron - 1
29
+ cron_spec: "* * * * *"
30
+ executor:
31
+ type: script
32
+ import_path: concurrent_cron
33
+ subqueue_name: subqueue_1
34
+ - id: concurrent_cron_2
35
+ enabled: true
36
+ type: cron
37
+ name: MyConcurrentCron - 2
38
+ cron_spec: "* * * * *"
39
+ executor:
40
+ type: script
41
+ import_path: concurrent_cron
42
+ subqueue_name: subqueue_2
43
+
25
44
 
26
45
  - id: example_wh1
27
46
  type: webhook
@@ -41,6 +60,12 @@ jobs:
41
60
  executor:
42
61
  type: script
43
62
  import_path: example_wh
63
+ - id: example_http
64
+ type: custom_http
65
+ name: Custom HTTP
66
+ executor:
67
+ type: script
68
+ import_path: example_http
44
69
  - id: example_runsheet_wh
45
70
  type: webhook
46
71
  name: Runsheet Webhook
@@ -170,9 +170,15 @@ def _invoke_membership_parser(
170
170
 
171
171
  def _build_parser_discriminated_union(
172
172
  context: ParserContext,
173
- discriminator: str,
173
+ discriminator_raw: str,
174
174
  discriminator_map: dict[str, ParserFunction[T]],
175
175
  ) -> ParserFunction[T]:
176
+ discriminator = (
177
+ snake_to_camel_case(discriminator_raw)
178
+ if context.options.from_camel_case
179
+ else discriminator_raw
180
+ )
181
+
176
182
  def parse(value: typing.Any) -> typing.Any:
177
183
  if context.options.allow_direct_type and dataclasses.is_dataclass(value):
178
184
  discriminant = getattr(value, discriminator)
@@ -414,8 +420,7 @@ def _build_parser_dataclass(
414
420
  cur_parser = context.cache.get(parsed_type)
415
421
  if cur_parser is not None:
416
422
  return cur_parser
417
-
418
- type_hints = typing.get_type_hints(parsed_type)
423
+ type_hints = typing.get_type_hints(parsed_type, include_extras=True)
419
424
  dc_field_parsers: list[
420
425
  tuple[
421
426
  dataclasses.Field[typing.Any],
pkgs/type_spec/builder.py CHANGED
@@ -14,6 +14,7 @@ from typing import Any, Self
14
14
 
15
15
  from . import util
16
16
  from .cross_output_links import CrossOutputPaths
17
+ from .non_discriminated_union_exceptions import NON_DISCRIMINATED_UNION_EXCEPTIONS
17
18
  from .util import parse_type_str, unused
18
19
 
19
20
  RawDict = dict[Any, Any]
@@ -636,6 +637,11 @@ class SpecTypeDefnUnion(SpecTypeDefn):
636
637
  self.discriminator_map[discriminant] = sub_type
637
638
 
638
639
  builder.pop_where()
640
+ elif (
641
+ f"{self.namespace.name}.{self.name}"
642
+ not in NON_DISCRIMINATED_UNION_EXCEPTIONS
643
+ ):
644
+ raise Exception(f"union requires a discriminator: {self.name}")
639
645
 
640
646
  def get_referenced_types(self) -> list[SpecType]:
641
647
  return self.types
@@ -960,8 +966,8 @@ class SpecEndpoint:
960
966
  elif isinstance(is_sdk, str):
961
967
  try:
962
968
  is_sdk = EndpointEmitType(is_sdk)
963
- except ValueError:
964
- raise ValueError(f"Invalid value for is_sdk: {is_sdk}")
969
+ except ValueError as e:
970
+ raise ValueError(f"Invalid value for is_sdk: {is_sdk}") from e
965
971
 
966
972
  assert isinstance(is_sdk, EndpointEmitType)
967
973
 
@@ -0,0 +1,14 @@
1
+ NON_DISCRIMINATED_UNION_EXCEPTIONS = [
2
+ "generate_tool_parameters.UnionWithoutDiscrim",
3
+ "output_calculation_entities.ConditionParameterFilterCondition",
4
+ "output_parameters.AnalyticalMethodParameterOptions",
5
+ "output_parameters.AnalyticalMethodLinkedOptionValue",
6
+ "recipes_redirect.RecipesRedirectResult",
7
+ "value_spec.ResolvedPathAll",
8
+ "weighted_sum.WeightedSumEntities",
9
+ "workflows.WorkflowTotalDisplay",
10
+ "type_info.TypeFormActionConstraint",
11
+ "structured_loading.CompatibleFilterNode",
12
+ "structured_display_element.DisplayElementColumn",
13
+ "field_values.FieldValue",
14
+ ]
@@ -58,15 +58,12 @@ def is_pure_json_value(value: ExtJsonValue) -> bool:
58
58
  return True
59
59
 
60
60
  if isinstance(value, list):
61
- for item in value:
62
- if not is_pure_json_value(item):
63
- return False
64
- return True
61
+ return all(is_pure_json_value(item) for item in value)
65
62
 
66
63
  if isinstance(value, dict):
67
- for key, item in value.items():
68
- if not is_pure_json_value(key) or not is_pure_json_value(item):
69
- return False
70
- return True
64
+ return all(
65
+ is_pure_json_value(key) and is_pure_json_value(item)
66
+ for key, item in value.items()
67
+ )
71
68
 
72
69
  return False
@@ -1,13 +1,14 @@
1
1
  import json
2
+ import re
2
3
  from dataclasses import dataclass
3
4
  from io import StringIO
4
5
  from pathlib import Path
5
6
  from typing import assert_never
6
7
 
7
8
  from main.base.types import (
9
+ base_t,
8
10
  ui_entry_actions_t,
9
11
  )
10
- from pkgs.argument_parser import snake_to_camel_case
11
12
  from pkgs.serialization_util import serialize_for_api
12
13
  from pkgs.type_spec import emit_typescript_util
13
14
  from pkgs.type_spec.builder import (
@@ -45,8 +46,8 @@ def ui_entry_variable_to_type_spec_type(
45
46
  match variable:
46
47
  case ui_entry_actions_t.UiEntryActionVariableString():
47
48
  return BaseTypeName.s_string
48
- case ui_entry_actions_t.UiEntryActionVariableEntityFields():
49
- return "entity.Entity"
49
+ case ui_entry_actions_t.UiEntryActionVariableSingleEntity():
50
+ return "ObjectId"
50
51
  case _:
51
52
  assert_never(variable)
52
53
 
@@ -157,6 +158,15 @@ def emit_entry_action_definition(
157
158
  )
158
159
 
159
160
 
161
+ def _validate_input(input: ui_entry_actions_t.UiEntryActionVariable) -> None:
162
+ if "_" in input.vs_var_name:
163
+ raise ValueError(f"Expected camelCase for variable {input.vs_var_name}")
164
+ if not re.fullmatch(base_t.REF_NAME_STRICT_REGEX, input.vs_var_name):
165
+ raise ValueError(
166
+ f"Variable {input.vs_var_name} has invalid syntax. See REF_NAME_STRICT_REGEX"
167
+ )
168
+
169
+
160
170
  def emit_query_index(
161
171
  ctx: emit_typescript_util.EmitTypescriptContext,
162
172
  defn_infos: list[EntryActionTypeInfo],
@@ -182,6 +192,7 @@ def emit_query_index(
182
192
  "type": BaseTypeName.s_object,
183
193
  "properties": {
184
194
  "value_spec_var": {"type": "String"},
195
+ "type": {"type": "ui_entry_actions.UiEntryActionDataType"},
185
196
  "variable": {"type": "ui_entry_actions.UiEntryActionVariable"},
186
197
  },
187
198
  },
@@ -215,10 +226,12 @@ def emit_query_index(
215
226
  for scope, defn in definitions.items():
216
227
  inputs = []
217
228
  outputs = []
218
- for name, input in defn.inputs.items():
229
+ for input in defn.inputs.values():
230
+ _validate_input(input)
219
231
  inputs.append(
220
232
  serialize_for_api({
221
- "value_spec_var": snake_to_camel_case(name),
233
+ "value_spec_var": input.vs_var_name,
234
+ "type": input.type,
222
235
  "variable": input,
223
236
  })
224
237
  )
@@ -104,8 +104,8 @@ def parse_function_signature(text: str) -> ParsedFunctionSignature:
104
104
 
105
105
  type_str = source.extract_type()
106
106
  ref_name = arg_group.group(1)
107
- is_missing = arg_group.group(2) == "?"
108
- is_repeating = arg_group.group(2) == "+"
107
+ # is_missing = arg_group.group(2) == "?"
108
+ # is_repeating = arg_group.group(2) == "+"
109
109
  type_path = parse_type_str(type_str)
110
110
 
111
111
  match arg_group.group(3):
@@ -18,7 +18,7 @@ def get_server_env() -> str | None:
18
18
  return os.environ.get("UNC_SERVER_ENV")
19
19
 
20
20
 
21
- def get_webhook_server_port() -> int:
21
+ def get_http_server_port() -> int:
22
22
  return int(os.environ.get("UNC_WEBHOOK_SERVER_PORT", "5001"))
23
23
 
24
24
 
@@ -0,0 +1,5 @@
1
+ # CLOSED MODULE
2
+
3
+ from .types import GenericHttpRequest as GenericHttpRequest
4
+ from .types import GenericHttpResponse as GenericHttpResponse
5
+ from .types import HttpException as HttpException
@@ -0,0 +1,67 @@
1
+ import base64
2
+ import functools
3
+ from dataclasses import dataclass
4
+
5
+ from flask.wrappers import Response
6
+
7
+
8
+ class HttpException(Exception):
9
+ error_code: int
10
+ message: str
11
+
12
+ def __init__(self, *, error_code: int, message: str) -> None:
13
+ self.error_code = error_code
14
+ self.message = message
15
+
16
+ @staticmethod
17
+ def payload_failed_signature() -> "HttpException":
18
+ return HttpException(
19
+ error_code=401, message="webhook payload did not match signature"
20
+ )
21
+
22
+ @staticmethod
23
+ def no_signature_passed() -> "HttpException":
24
+ return HttpException(error_code=400, message="missing signature")
25
+
26
+ @staticmethod
27
+ def body_parse_error() -> "HttpException":
28
+ return HttpException(error_code=400, message="body parse error")
29
+
30
+ @staticmethod
31
+ def unknown_error() -> "HttpException":
32
+ return HttpException(error_code=500, message="internal server error")
33
+
34
+ @staticmethod
35
+ def configuration_error(
36
+ message: str = "internal configuration error",
37
+ ) -> "HttpException":
38
+ return HttpException(error_code=500, message=message)
39
+
40
+ def __str__(self) -> str:
41
+ return f"[{self.error_code}]: {self.message}"
42
+
43
+ def make_error_response(self) -> Response:
44
+ return Response(
45
+ status=self.error_code, response={"error": {"message": str(self)}}
46
+ )
47
+
48
+
49
+ @dataclass(kw_only=True, frozen=True)
50
+ class GenericHttpRequest:
51
+ body_base64: str
52
+ headers: dict[str, str]
53
+
54
+ @functools.cached_property
55
+ def body_bytes(self) -> bytes:
56
+ return base64.b64decode(self.body_base64)
57
+
58
+ @functools.cached_property
59
+ def body_text(self) -> str:
60
+ return self.body_bytes.decode()
61
+
62
+
63
+ @dataclass(kw_only=True)
64
+ class GenericHttpResponse:
65
+ response: str
66
+ status_code: int
67
+ headers: dict[str, str] | None = None
@@ -1,15 +1,43 @@
1
1
  import functools
2
+ import hmac
2
3
  import typing
3
4
  from abc import ABC, abstractmethod
4
5
  from dataclasses import dataclass
5
6
 
7
+ import simplejson
8
+
6
9
  from pkgs.argument_parser import CachedParser
10
+ from pkgs.serialization_util import serialize_for_api
7
11
  from uncountable.core.async_batch import AsyncBatchProcessor
8
12
  from uncountable.core.client import Client
13
+ from uncountable.core.environment import get_local_admin_server_port
9
14
  from uncountable.core.file_upload import FileUpload
15
+ from uncountable.integration.http_server import (
16
+ GenericHttpRequest,
17
+ GenericHttpResponse,
18
+ HttpException,
19
+ )
20
+ from uncountable.integration.queue_runner.command_server.command_client import (
21
+ send_job_queue_message,
22
+ )
23
+ from uncountable.integration.queue_runner.command_server.types import (
24
+ CommandServerException,
25
+ )
26
+ from uncountable.integration.secret_retrieval.retrieve_secret import retrieve_secret
10
27
  from uncountable.integration.telemetry import JobLogger
11
- from uncountable.types import base_t, entity_t, webhook_job_t
12
- from uncountable.types.job_definition_t import JobDefinition, JobResult, ProfileMetadata
28
+ from uncountable.types import (
29
+ base_t,
30
+ entity_t,
31
+ job_definition_t,
32
+ queued_job_t,
33
+ webhook_job_t,
34
+ )
35
+ from uncountable.types.job_definition_t import (
36
+ HttpJobDefinitionBase,
37
+ JobDefinition,
38
+ JobResult,
39
+ ProfileMetadata,
40
+ )
13
41
 
14
42
 
15
43
  @dataclass(kw_only=True)
@@ -26,9 +54,6 @@ class JobArguments:
26
54
  CronJobArguments = JobArguments
27
55
 
28
56
 
29
- PT = typing.TypeVar("PT")
30
-
31
-
32
57
  class Job[PT](ABC):
33
58
  _unc_job_registered: bool = False
34
59
 
@@ -63,6 +88,51 @@ class CronJob(Job):
63
88
  WPT = typing.TypeVar("WPT")
64
89
 
65
90
 
91
+ @dataclass(kw_only=True)
92
+ class WebhookResponse:
93
+ pass
94
+
95
+
96
+ class CustomHttpJob(Job[GenericHttpRequest]):
97
+ @property
98
+ def payload_type(self) -> type[GenericHttpRequest]:
99
+ return GenericHttpRequest
100
+
101
+ @staticmethod
102
+ @abstractmethod
103
+ def validate_request(
104
+ *,
105
+ request: GenericHttpRequest,
106
+ job_definition: HttpJobDefinitionBase,
107
+ profile_meta: ProfileMetadata,
108
+ ) -> None:
109
+ """
110
+ Validate that the request is valid. If the request is invalid, raise an
111
+ exception.
112
+ """
113
+ ...
114
+
115
+ @staticmethod
116
+ @abstractmethod
117
+ def handle_request(
118
+ *,
119
+ request: GenericHttpRequest,
120
+ job_definition: HttpJobDefinitionBase,
121
+ profile_meta: ProfileMetadata,
122
+ ) -> GenericHttpResponse:
123
+ """
124
+ Handle the request synchronously. Normally this should just enqueue a job
125
+ and return immediately (see WebhookJob as an example).
126
+ """
127
+ ...
128
+
129
+ def run_outer(self, args: JobArguments) -> JobResult:
130
+ args.logger.log_warning(
131
+ message=f"Unexpected call to run_outer for CustomHttpJob: {args.job_definition.id}"
132
+ )
133
+ return JobResult(success=False)
134
+
135
+
66
136
  class WebhookJob[WPT](Job[webhook_job_t.WebhookEventPayload]):
67
137
  @property
68
138
  def payload_type(self) -> type[webhook_job_t.WebhookEventPayload]:
@@ -72,6 +142,60 @@ class WebhookJob[WPT](Job[webhook_job_t.WebhookEventPayload]):
72
142
  @abstractmethod
73
143
  def webhook_payload_type(self) -> type[WPT]: ...
74
144
 
145
+ @staticmethod
146
+ def validate_request(
147
+ *,
148
+ request: GenericHttpRequest,
149
+ job_definition: job_definition_t.HttpJobDefinitionBase,
150
+ profile_meta: ProfileMetadata,
151
+ ) -> None:
152
+ assert isinstance(job_definition, job_definition_t.WebhookJobDefinition)
153
+ signature_key = retrieve_secret(
154
+ profile_metadata=profile_meta,
155
+ secret_retrieval=job_definition.signature_key_secret,
156
+ )
157
+ passed_signature = request.headers.get("Uncountable-Webhook-Signature")
158
+ if passed_signature is None:
159
+ raise HttpException.no_signature_passed()
160
+
161
+ request_body_signature = hmac.new(
162
+ signature_key.encode("utf-8"), msg=request.body_bytes, digestmod="sha256"
163
+ ).hexdigest()
164
+
165
+ if request_body_signature != passed_signature:
166
+ raise HttpException.payload_failed_signature()
167
+
168
+ @staticmethod
169
+ def handle_request(
170
+ *,
171
+ request: GenericHttpRequest,
172
+ job_definition: job_definition_t.HttpJobDefinitionBase,
173
+ profile_meta: ProfileMetadata, # noqa: ARG004
174
+ ) -> GenericHttpResponse:
175
+ try:
176
+ request_body = simplejson.loads(request.body_text)
177
+ webhook_payload = typing.cast(base_t.JsonValue, request_body)
178
+ except (simplejson.JSONDecodeError, ValueError) as e:
179
+ raise HttpException.body_parse_error() from e
180
+
181
+ try:
182
+ send_job_queue_message(
183
+ job_ref_name=job_definition.id,
184
+ payload=queued_job_t.QueuedJobPayload(
185
+ invocation_context=queued_job_t.InvocationContextWebhook(
186
+ webhook_payload=webhook_payload
187
+ )
188
+ ),
189
+ port=get_local_admin_server_port(),
190
+ )
191
+ except CommandServerException as e:
192
+ raise HttpException.unknown_error() from e
193
+
194
+ return GenericHttpResponse(
195
+ response=simplejson.dumps(serialize_for_api(WebhookResponse())),
196
+ status_code=200,
197
+ )
198
+
75
199
  def run_outer(self, args: JobArguments) -> JobResult:
76
200
  webhook_body = self.get_payload(args.payload)
77
201
  inner_payload = CachedParser(self.webhook_payload_type).parse_api(
@@ -1,4 +1,5 @@
1
1
  import asyncio
2
+ import sys
2
3
  import typing
3
4
  from concurrent.futures import ProcessPoolExecutor
4
5
  from dataclasses import dataclass
@@ -34,6 +35,10 @@ class JobListenerKey:
34
35
  def _get_job_worker_key(
35
36
  job_definition: job_definition_t.JobDefinition, profile_name: str
36
37
  ) -> JobListenerKey:
38
+ if job_definition.subqueue_name is not None:
39
+ return JobListenerKey(
40
+ profile_name=profile_name, subqueue_name=job_definition.subqueue_name
41
+ )
37
42
  return JobListenerKey(profile_name=profile_name)
38
43
 
39
44
 
@@ -41,9 +46,12 @@ def on_worker_crash(
41
46
  worker_key: JobListenerKey,
42
47
  ) -> typing.Callable[[asyncio.Task], None]:
43
48
  def hook(task: asyncio.Task) -> None:
44
- raise Exception(
45
- f"worker {worker_key.profile_name}_{worker_key.subqueue_name} crashed unexpectedly"
49
+ Logger(get_current_span()).log_exception(
50
+ Exception(
51
+ f"worker {worker_key.profile_name}_{worker_key.subqueue_name} crashed unexpectedly"
52
+ )
46
53
  )
54
+ sys.exit(1)
47
55
 
48
56
  return hook
49
57
 
@@ -16,7 +16,7 @@ from uncountable.integration.telemetry import Logger
16
16
  from uncountable.types import base_t, job_definition_t
17
17
  from uncountable.types.job_definition_t import (
18
18
  CronJobDefinition,
19
- WebhookJobDefinition,
19
+ HttpJobDefinitionBase,
20
20
  )
21
21
 
22
22
  _MAX_APSCHEDULER_CONCURRENT_JOBS = 1
@@ -86,7 +86,7 @@ class IntegrationServer:
86
86
  misfire_grace_time=None,
87
87
  **job_opts,
88
88
  )
89
- case WebhookJobDefinition():
89
+ case HttpJobDefinitionBase():
90
90
  pass
91
91
  case _:
92
92
  assert_never(job_defn)
@@ -177,7 +177,7 @@ class JobLogger(Logger):
177
177
  patched_attributes["job.definition.cron_spec"] = (
178
178
  self.job_definition.cron_spec
179
179
  )
180
- case job_definition_t.WebhookJobDefinition():
180
+ case job_definition_t.HttpJobDefinitionBase():
181
181
  pass
182
182
  case _:
183
183
  assert_never(self.job_definition)