/ mlflow / webhooks / types.py
types.py
  1  """Type definitions for MLflow webhook payloads.
  2  
  3  This module contains class definitions for all webhook event payloads
  4  that are sent when various model registry events occur.
  5  """
  6  
  7  from typing import Literal, TypeAlias, TypedDict
  8  
  9  from mlflow.entities.webhook import WebhookAction, WebhookEntity, WebhookEvent
 10  
 11  
 12  class RegisteredModelCreatedPayload(TypedDict):
 13      """Payload sent when a new registered model is created.
 14  
 15      Example payload:
 16  
 17      .. code-block:: python
 18  
 19          {
 20              "name": "example_model",
 21              "tags": {"example_key": "example_value"},
 22              "description": "An example registered model",
 23          }
 24  
 25      """
 26  
 27      name: str
 28      """The name of the registered model."""
 29      tags: dict[str, str]
 30      """Tags associated with the registered model."""
 31      description: str | None
 32      """Description of the registered model."""
 33  
 34      @classmethod
 35      def example(cls) -> "RegisteredModelCreatedPayload":
 36          return cls(
 37              name="example_model",
 38              tags={"example_key": "example_value"},
 39              description="An example registered model",
 40          )
 41  
 42  
 43  class ModelVersionCreatedPayload(TypedDict):
 44      """Payload sent when a new model version is created.
 45  
 46      Example payload:
 47  
 48      .. code-block:: python
 49  
 50          {
 51              "name": "example_model",
 52              "version": "1",
 53              "source": "models:/123",
 54              "run_id": "abcd1234abcd5678",
 55              "tags": {"example_key": "example_value"},
 56              "description": "An example model version",
 57          }
 58  
 59      """
 60  
 61      name: str
 62      """The name of the registered model."""
 63      version: str
 64      """The version of the model."""
 65      source: str
 66      """The source URI of the model version."""
 67      run_id: str | None
 68      """The run ID associated with the model version, if applicable."""
 69      tags: dict[str, str]
 70      """Tags associated with the model version."""
 71      description: str | None
 72      """Description of the model version."""
 73  
 74      @classmethod
 75      def example(cls) -> "ModelVersionCreatedPayload":
 76          return cls(
 77              name="example_model",
 78              version="1",
 79              source="models:/123",
 80              run_id="abcd1234abcd5678",
 81              tags={"example_key": "example_value"},
 82              description="An example model version",
 83          )
 84  
 85  
 86  class ModelVersionTagSetPayload(TypedDict):
 87      """Payload sent when a tag is set on a model version.
 88  
 89      Example payload:
 90  
 91      .. code-block:: python
 92  
 93          {
 94              "name": "example_model",
 95              "version": "1",
 96              "key": "example_key",
 97              "value": "example_value",
 98          }
 99  
100      """
101  
102      name: str
103      """The name of the registered model."""
104      version: str
105      """The version of the model."""
106      key: str
107      """The tag key being set."""
108      value: str
109      """The tag value being set."""
110  
111      @classmethod
112      def example(cls) -> "ModelVersionTagSetPayload":
113          return cls(
114              name="example_model",
115              version="1",
116              key="example_key",
117              value="example_value",
118          )
119  
120  
121  class ModelVersionTagDeletedPayload(TypedDict):
122      """Payload sent when a tag is deleted from a model version.
123  
124      Example payload:
125  
126      .. code-block:: python
127  
128          {
129              "name": "example_model",
130              "version": "1",
131              "key": "example_key",
132          }
133  
134      """
135  
136      name: str
137      """The name of the registered model."""
138      version: str
139      """The version of the model."""
140      key: str
141      """The tag key being deleted."""
142  
143      @classmethod
144      def example(cls) -> "ModelVersionTagDeletedPayload":
145          return cls(
146              name="example_model",
147              version="1",
148              key="example_key",
149          )
150  
151  
152  class ModelVersionAliasCreatedPayload(TypedDict):
153      """
154      Payload sent when an alias is created for a model version.
155  
156      Example payload:
157  
158      .. code-block:: python
159  
160          {
161              "name": "example_model",
162              "alias": "example_alias",
163              "version": "1",
164          }
165  
166      """
167  
168      name: str
169      """The name of the registered model."""
170      alias: str
171      """The alias being created."""
172      version: str
173      """The version of the model the alias is being assigned to."""
174  
175      @classmethod
176      def example(cls) -> "ModelVersionAliasCreatedPayload":
177          return cls(
178              name="example_model",
179              alias="example_alias",
180              version="1",
181          )
182  
183  
184  class ModelVersionAliasDeletedPayload(TypedDict):
185      """Payload sent when an alias is deleted from a model version.
186  
187      Example payload:
188  
189      .. code-block:: python
190  
191          {
192              "name": "example_model",
193              "alias": "example_alias",
194          }
195  
196      """
197  
198      name: str
199      """The name of the registered model."""
200      alias: str
201      """The alias being deleted."""
202  
203      @classmethod
204      def example(cls) -> "ModelVersionAliasDeletedPayload":
205          return cls(
206              name="example_model",
207              alias="example_alias",
208          )
209  
210  
211  class PromptCreatedPayload(TypedDict):
212      """Payload sent when a new prompt is created.
213  
214      Example payload:
215  
216      .. code-block:: python
217  
218          {
219              "name": "example_prompt",
220              "tags": {"example_key": "example_value"},
221              "description": "An example prompt",
222          }
223  
224      """
225  
226      name: str
227      """The name of the prompt."""
228      tags: dict[str, str]
229      """Tags associated with the prompt."""
230      description: str | None
231      """Description of the prompt."""
232  
233      @classmethod
234      def example(cls) -> "PromptCreatedPayload":
235          return cls(
236              name="example_prompt",
237              tags={"example_key": "example_value"},
238              description="An example prompt",
239          )
240  
241  
242  class PromptVersionCreatedPayload(TypedDict):
243      """Payload sent when a new prompt version is created.
244  
245      Example payload:
246  
247      .. code-block:: python
248  
249          {
250              "name": "example_prompt",
251              "version": "1",
252              "template": "Hello {{name}}!",
253              "tags": {"example_key": "example_value"},
254              "description": "An example prompt version",
255          }
256  
257      """
258  
259      name: str
260      """The name of the prompt."""
261      version: str
262      """The version of the prompt."""
263      template: str
264      """The template content of the prompt version."""
265      tags: dict[str, str]
266      """Tags associated with the prompt version."""
267      description: str | None
268      """Description of the prompt version."""
269  
270      @classmethod
271      def example(cls) -> "PromptVersionCreatedPayload":
272          return cls(
273              name="example_prompt",
274              version="1",
275              template="Hello {{name}}!",
276              tags={"example_key": "example_value"},
277              description="An example prompt version",
278          )
279  
280  
281  class PromptTagSetPayload(TypedDict):
282      """Payload sent when a tag is set on a prompt.
283  
284      Example payload:
285  
286      .. code-block:: python
287  
288          {
289              "name": "example_prompt",
290              "key": "example_key",
291              "value": "example_value",
292          }
293  
294      """
295  
296      name: str
297      """The name of the prompt."""
298      key: str
299      """The tag key being set."""
300      value: str
301      """The tag value being set."""
302  
303      @classmethod
304      def example(cls) -> "PromptTagSetPayload":
305          return cls(
306              name="example_prompt",
307              key="example_key",
308              value="example_value",
309          )
310  
311  
312  class PromptTagDeletedPayload(TypedDict):
313      """Payload sent when a tag is deleted from a prompt.
314  
315      Example payload:
316  
317      .. code-block:: python
318  
319          {
320              "name": "example_prompt",
321              "key": "example_key",
322          }
323  
324      """
325  
326      name: str
327      """The name of the prompt."""
328      key: str
329      """The tag key being deleted."""
330  
331      @classmethod
332      def example(cls) -> "PromptTagDeletedPayload":
333          return cls(
334              name="example_prompt",
335              key="example_key",
336          )
337  
338  
339  class PromptVersionTagSetPayload(TypedDict):
340      """Payload sent when a tag is set on a prompt version.
341  
342      Example payload:
343  
344      .. code-block:: python
345  
346          {
347              "name": "example_prompt",
348              "version": "1",
349              "key": "example_key",
350              "value": "example_value",
351          }
352  
353      """
354  
355      name: str
356      """The name of the prompt."""
357      version: str
358      """The version of the prompt."""
359      key: str
360      """The tag key being set."""
361      value: str
362      """The tag value being set."""
363  
364      @classmethod
365      def example(cls) -> "PromptVersionTagSetPayload":
366          return cls(
367              name="example_prompt",
368              version="1",
369              key="example_key",
370              value="example_value",
371          )
372  
373  
374  class PromptVersionTagDeletedPayload(TypedDict):
375      """Payload sent when a tag is deleted from a prompt version.
376  
377      Example payload:
378  
379      .. code-block:: python
380  
381          {
382              "name": "example_prompt",
383              "version": "1",
384              "key": "example_key",
385          }
386  
387      """
388  
389      name: str
390      """The name of the prompt."""
391      version: str
392      """The version of the prompt."""
393      key: str
394      """The tag key being deleted."""
395  
396      @classmethod
397      def example(cls) -> "PromptVersionTagDeletedPayload":
398          return cls(
399              name="example_prompt",
400              version="1",
401              key="example_key",
402          )
403  
404  
405  class PromptAliasCreatedPayload(TypedDict):
406      """Payload sent when an alias is created for a prompt version.
407  
408      Example payload:
409  
410      .. code-block:: python
411  
412          {
413              "name": "example_prompt",
414              "alias": "example_alias",
415              "version": "1",
416          }
417  
418      """
419  
420      name: str
421      """The name of the prompt."""
422      alias: str
423      """The alias being created."""
424      version: str
425      """The version of the prompt the alias is being assigned to."""
426  
427      @classmethod
428      def example(cls) -> "PromptAliasCreatedPayload":
429          return cls(
430              name="example_prompt",
431              alias="example_alias",
432              version="1",
433          )
434  
435  
436  class PromptAliasDeletedPayload(TypedDict):
437      """Payload sent when an alias is deleted from a prompt.
438  
439      Example payload:
440  
441      .. code-block:: python
442  
443          {
444              "name": "example_prompt",
445              "alias": "example_alias",
446          }
447  
448      """
449  
450      name: str
451      """The name of the prompt."""
452      alias: str
453      """The alias being deleted."""
454  
455      @classmethod
456      def example(cls) -> "PromptAliasDeletedPayload":
457          return cls(
458              name="example_prompt",
459              alias="example_alias",
460          )
461  
462  
463  class BudgetPolicyExceededPayload(TypedDict):
464      """Payload sent when a budget policy limit is exceeded.
465  
466      Example payload:
467  
468      .. code-block:: python
469  
470          {
471              "budget_policy_id": "bp-abc123",
472              "budget_unit": "USD",
473              "budget_amount": 100.0,
474              "current_spend": 105.50,
475              "duration_unit": "MONTHS",
476              "duration_value": 1,
477              "target_scope": "WORKSPACE",
478              "workspace": "default",
479              "window_start": 1704067200000,
480          }
481  
482      """
483  
484      budget_policy_id: str
485      """The unique identifier of the budget policy."""
486      budget_unit: Literal["USD"]
487      """The budget measurement unit (e.g. USD)."""
488      budget_amount: float
489      """The budget limit amount."""
490      current_spend: float
491      """The current cumulative spend when the limit was exceeded."""
492      duration_unit: Literal["MINUTES", "HOURS", "DAYS", "MONTHS"]
493      """The duration unit (MINUTES, HOURS, DAYS, MONTHS)."""
494      duration_value: int
495      """The duration value."""
496      target_scope: Literal["GLOBAL", "WORKSPACE"]
497      """The target scope (GLOBAL or WORKSPACE)."""
498      workspace: str
499      """The workspace this budget applies to."""
500      window_start: int
501      """The start timestamp (milliseconds) of the current budget window."""
502  
503      @classmethod
504      def example(cls) -> "BudgetPolicyExceededPayload":
505          return cls(
506              budget_policy_id="bp-abc123",
507              budget_unit="USD",
508              budget_amount=100.0,
509              current_spend=105.50,
510              duration_unit="MONTHS",
511              duration_value=1,
512              target_scope="WORKSPACE",
513              workspace="default",
514              window_start=1704067200000,
515          )
516  
517  
518  WebhookPayload: TypeAlias = (
519      RegisteredModelCreatedPayload
520      | ModelVersionCreatedPayload
521      | ModelVersionTagSetPayload
522      | ModelVersionTagDeletedPayload
523      | ModelVersionAliasCreatedPayload
524      | ModelVersionAliasDeletedPayload
525      | PromptCreatedPayload
526      | PromptVersionCreatedPayload
527      | PromptTagSetPayload
528      | PromptTagDeletedPayload
529      | PromptVersionTagSetPayload
530      | PromptVersionTagDeletedPayload
531      | PromptAliasCreatedPayload
532      | PromptAliasDeletedPayload
533      | BudgetPolicyExceededPayload
534  )
535  
536  # Mapping of (entity, action) tuples to their corresponding payload classes
537  EVENT_TO_PAYLOAD_CLASS: dict[tuple[WebhookEntity, WebhookAction], type[WebhookPayload]] = {
538      (WebhookEntity.REGISTERED_MODEL, WebhookAction.CREATED): RegisteredModelCreatedPayload,
539      (WebhookEntity.MODEL_VERSION, WebhookAction.CREATED): ModelVersionCreatedPayload,
540      (WebhookEntity.MODEL_VERSION_TAG, WebhookAction.SET): ModelVersionTagSetPayload,
541      (WebhookEntity.MODEL_VERSION_TAG, WebhookAction.DELETED): ModelVersionTagDeletedPayload,
542      (WebhookEntity.MODEL_VERSION_ALIAS, WebhookAction.CREATED): ModelVersionAliasCreatedPayload,
543      (WebhookEntity.MODEL_VERSION_ALIAS, WebhookAction.DELETED): ModelVersionAliasDeletedPayload,
544      (WebhookEntity.PROMPT, WebhookAction.CREATED): PromptCreatedPayload,
545      (WebhookEntity.PROMPT_VERSION, WebhookAction.CREATED): PromptVersionCreatedPayload,
546      (WebhookEntity.PROMPT_TAG, WebhookAction.SET): PromptTagSetPayload,
547      (WebhookEntity.PROMPT_TAG, WebhookAction.DELETED): PromptTagDeletedPayload,
548      (WebhookEntity.PROMPT_VERSION_TAG, WebhookAction.SET): PromptVersionTagSetPayload,
549      (WebhookEntity.PROMPT_VERSION_TAG, WebhookAction.DELETED): PromptVersionTagDeletedPayload,
550      (WebhookEntity.PROMPT_ALIAS, WebhookAction.CREATED): PromptAliasCreatedPayload,
551      (WebhookEntity.PROMPT_ALIAS, WebhookAction.DELETED): PromptAliasDeletedPayload,
552      (WebhookEntity.BUDGET_POLICY, WebhookAction.EXCEEDED): BudgetPolicyExceededPayload,
553  }
554  
555  
556  def get_example_payload_for_event(event: WebhookEvent) -> WebhookPayload:
557      """Get an example payload for the given webhook event type.
558  
559      Args:
560          event: The webhook event instance
561  
562      Returns:
563          Example payload for the event type
564  
565      Raises:
566          ValueError: If the event type is unknown
567      """
568      event_key = (event.entity, event.action)
569      if payload_class := EVENT_TO_PAYLOAD_CLASS.get(event_key):
570          return payload_class.example()
571  
572      raise ValueError(f"Unknown event type: {event.entity}.{event.action}")
573  
574  
575  def get_payload_class_for_event(event: WebhookEvent) -> type[WebhookPayload] | None:
576      """Get the payload class for the given webhook event type.
577  
578      Args:
579          event: The webhook event instance
580  
581      Returns:
582          Payload class for the event type, or None if unknown
583      """
584      return EVENT_TO_PAYLOAD_CLASS.get((event.entity, event.action))