lionagi 0.7.7__py3-none-any.whl → 0.8.0__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.
@@ -1,786 +1,82 @@
1
- # Copyright (c) 2023 - 2024, HaiyangLi <quantocean.li at gmail dot com>
2
- #
3
- # SPDX-License-Identifier: Apache-2.0
1
+ # forms/form.py
4
2
 
5
- import inspect
6
- from typing import Any, Literal, TypeVar
3
+ from typing import Any, Optional
7
4
 
8
- from pydantic import Field, model_validator
9
- from pydantic.fields import FieldInfo
10
- from pydantic_core import PydanticUndefined
11
- from typing_extensions import override
5
+ from pydantic import ConfigDict, Field, model_validator
6
+ from typing_extensions import Self
12
7
 
13
- from lionagi.utils import UNDEFINED, copy
14
-
15
- from ..models.note import Note
16
8
  from .base import BaseForm
17
- from .utils import get_input_output_fields
18
-
19
- T = TypeVar("T", bound=BaseForm)
9
+ from .flow import FlowDefinition
20
10
 
21
11
 
22
12
  class Form(BaseForm):
23
13
  """
24
- Base task form class extending BaseForm with task-specific functionality.
25
-
26
- This class builds on BaseForm to introduce concepts specific to task-based
27
- forms, including input fields, request fields, and task-related attributes
28
- It is designed for tasks that require filling in fields through an
29
- intelligent process, providing the necessary framework for managing and
30
- validating these fields.
31
-
32
- Key Concepts:
33
- - input_fields: Fields required to obtain the request fields.
34
- - request_fields: Fields that need to be filled by an intelligent process.
35
- - output_fields: Fields for presentation, which may include all, some, or
36
- none of the request fields. These can be conditionally modified if the
37
- form is not strict_form.
38
-
39
- The Form class provides methods for field management, validation, and
40
- task instruction generation. It supports both strict_form and flexible
41
- modes of operation, allowing for immutable fields and assignments when
42
- needed.
43
-
44
- Attributes:
45
- strict_form (bool): If True, form fields and assignment are immutable.
46
- guidance (str | dict[str, Any] | None): High-level task guidance that
47
- can be optimized by AI or provided as instructions.
48
- input_fields (list[str]): Fields needed to obtain the requested fields.
49
- request_fields (list[str]): Fields to be filled by an intelligent
50
- process.
51
- task (Any): The work to be done, including custom instructions.
52
- task_description (str | None): A detailed description of the task.
53
- init_input_kwargs (dict[str, Any]): Initial keyword arguments for
54
- input fields.
55
-
56
- Example:
57
- >>> form = Form(
58
- ... assignment="input1, input2 -> output",
59
- ... strict_form=True,
60
- ... guidance="Complete the task with the given inputs."
61
- ... )
62
- >>> form.fill_input_fields(input1="value1", input2="value2")
63
- >>> print(form.get_results())
14
+ A domain form that can handle either a simple 'a,b->c' assignment
15
+ or a multi-step flow if the assignment string has semicolons, etc.
64
16
  """
65
17
 
66
- strict_form: bool = Field(
67
- default=False,
68
- description="If True, form fields and assignment are immutable.",
69
- frozen=True,
70
- )
71
- guidance: str | dict[str, Any] | None = Field(
72
- default=None,
73
- description="High-level task guidance, optimizable by AI.",
74
- )
75
- input_fields: list[str] = Field(
76
- default_factory=list,
77
- description="Fields required to obtain the requested fields.",
78
- )
79
- request_fields: list[str] = Field(
80
- default_factory=list,
81
- description="Fields to be filled by an intelligent process.",
82
- )
83
- task: Any = Field(
84
- default_factory=str,
85
- description="Work to be done, including custom instructions.",
86
- )
87
- task_description: str | None = Field(
88
- default_factory=str,
89
- description="Detailed description of the task",
90
- )
91
- init_input_kwargs: dict[str, Any] = Field(
92
- default_factory=dict,
93
- exclude=True,
94
- )
95
-
96
- def check_is_completed(
97
- self,
98
- handle_how: Literal["return_missing", "raise"] = "raise",
99
- ) -> list[str] | None:
100
- """Check if all required fields are completed.
18
+ model_config = ConfigDict(extra="allow", arbitrary_types_allowed=True)
101
19
 
102
- Args:
103
- handle_how: How to handle incomplete fields.
104
-
105
- Returns:
106
- List of incomplete fields if handle_how is "return_missing",
107
- None otherwise.
108
-
109
- Raises:
110
- ValueError: If required fields are incomplete and handle_how
111
- is "raise".
112
- """
113
- if self.strict_form and self.has_processed:
114
- return
115
- return super().check_is_completed(handle_how)
20
+ flow_definition: FlowDefinition | None = None
21
+ # Possibly some extra fields, e.g. "guidance" or "task"
22
+ guidance: str | None = Field(default=None)
23
+ task: str | None = Field(default=None)
116
24
 
117
25
  @model_validator(mode="before")
118
- @classmethod
119
- def check_input_output_list_omitted(cls, data: Any) -> dict[str, Any]:
120
- """Validate and process the input data before model creation.
121
-
122
- Args:
123
- data: The input data for model creation.
124
-
125
- Returns:
126
- The validated and processed input data.
127
-
128
- Raises:
129
- ValueError: If the input data is invalid or missing required fields
26
+ def parse_assignment_into_flow(cls, values):
130
27
  """
131
- if isinstance(data, Note):
132
- data = data.to_dict()
133
-
134
- if not isinstance(data, dict):
135
- raise TypeError("Input data must be a dictionary.")
136
-
137
- if not data.get("assignment", None):
138
- if cls.model_fields["assignment"].get_default() is None:
139
- raise ValueError("Assignment is missing.")
140
- else:
141
- data["assignment"] = cls.model_fields[
142
- "assignment"
143
- ].get_default()
144
-
145
- if "input_fields" in data:
146
- raise ValueError("Explicit input fields are not allowed.")
147
-
148
- if "request_fields" in data:
149
- raise ValueError("Explicit request fields are not allowed.")
150
-
151
- if "task" in data:
152
- raise ValueError("Explicit task fields are not allowed.")
153
-
154
- fields = get_input_output_fields(data.get("assignment"))
155
- input_fields, request_fields = fields
156
-
157
- if not input_fields or input_fields == [""]:
158
- raise ValueError("Missing input fields.")
159
-
160
- elif not request_fields or request_fields == [""]:
161
- raise ValueError("Missing request fields.")
162
-
163
- data["input_fields"] = input_fields
164
- data["request_fields"] = request_fields
165
- data["output_fields"] = data.get("output_fields", request_fields)
166
- data["init_input_kwargs"] = {}
167
- data["strict_form"] = data.get("strict_form", False)
168
-
169
- for in_ in data["input_fields"]:
170
- data["init_input_kwargs"][in_] = (
171
- data.pop(in_, UNDEFINED)
172
- if in_ not in cls.model_fields
173
- else data.get(in_, UNDEFINED)
174
- )
175
-
176
- return data
28
+ If the 'assignment' has semicolons, assume multiple steps, parse into FlowDefinition.
29
+ If it's a single step or no semicolons, we remain in 'simple' mode.
30
+ """
31
+ assignment_str = values.get("assignment")
32
+ if assignment_str and ";" in assignment_str:
33
+ flow = FlowDefinition()
34
+ flow.parse_flow_string(assignment_str)
35
+ values["flow_definition"] = flow
36
+ return values
177
37
 
178
38
  @model_validator(mode="after")
179
- def check_input_output_fields(self) -> "Form":
39
+ def compute_output_fields(self) -> Self:
180
40
  """
181
- Validate and process input and output fields after model creation.
182
-
183
- Returns:
184
- The validated Form instance.
41
+ If in simple mode, we parse something like 'a,b->c' and set output_fields=[c].
42
+ If in multi-step mode, we set output_fields to the final produced fields of the flow.
185
43
  """
186
- for i in self.input_fields:
187
- if i in self.model_fields:
188
- self.init_input_kwargs[i] = getattr(self, i)
189
- else:
190
- self.add_field(
191
- i,
192
- value=self.init_input_kwargs.get(i, UNDEFINED),
193
- )
194
-
195
- for i in self.request_fields:
196
- if i not in self.all_fields:
197
- self.add_field(i)
198
-
44
+ if self.flow_definition:
45
+ # multi-step
46
+ produced = self.flow_definition.get_produced_fields()
47
+ if not self.output_fields:
48
+ self.output_fields = list(produced)
49
+ else:
50
+ # single-step
51
+ if self.assignment and "->" in self.assignment:
52
+ # parse the single arrow
53
+ ins_outs = self.assignment.split("->", 1)
54
+ outs_str = ins_outs[1]
55
+ outs = [x.strip() for x in outs_str.split(",") if x.strip()]
56
+ if not self.output_fields:
57
+ self.output_fields = outs
199
58
  return self
200
59
 
201
- @override
202
- @property
203
- def work_fields(self) -> list[str]:
204
- """Return a list of all fields involved in the task."""
205
- return self.input_fields + self.request_fields
206
-
207
- @override
208
- @property
209
- def required_fields(self) -> list[str]:
210
- """Return a list of all unique required fields."""
211
- return list(
212
- set(self.input_fields + self.request_fields + self.output_fields),
213
- )
214
-
215
- @property
216
- def validation_kwargs(self):
217
- """Get validation keyword arguments for each work field."""
218
- dict_ = {}
219
- for i in self.work_fields:
220
- dict_[i] = self.field_getattr(i, "validation_kwargs", {})
221
- return dict_
222
-
223
- @property
224
- def instruction_dict(self) -> dict[str, Any]:
225
- """Return a dictionary with task instruction information."""
226
- return {
227
- "context": self.instruction_context,
228
- "instruction": self.instruction_prompt,
229
- "request_fields": self.instruction_request_fields,
230
- }
231
-
232
- @property
233
- def instruction_context(self) -> str:
234
- """Generate a description of the form's input fields."""
235
- a = self.all_fields
236
- context = "### Input Fields:\n"
237
- for idx, i in enumerate(self.input_fields):
238
- context += f"Input No.{idx+1}: {i}\n"
239
- if getattr(a[i], "description", None):
240
- context += f" - description: {a[i].description}.\n"
241
- context += f" - value: {getattr(self, i)}.\n"
242
- return context
243
-
244
- @property
245
- def instruction_prompt(self) -> str:
246
- """Generate a task instruction prompt for the form."""
247
- a = self.all_fields
248
- prompt = ""
249
- if "guidance" in a:
250
- prompt += f"### Overall Guidance:\n{getattr(self, 'guidance')}.\n"
251
-
252
- in_fields = ", ".join(self.input_fields)
253
- out_fields = ", ".join(self.output_fields)
254
- prompt += "### Task Instructions:\n"
255
- prompt += f"1. Provided Input Fields: {in_fields}.\n"
256
- prompt += f"2. Requested Output Fields: {out_fields}.\n"
257
- prompt += f"3. Your task:\n{self.task}.\n"
258
-
259
- return prompt
260
-
261
- @property
262
- def instruction_request_fields(self) -> dict[str, str]:
263
- """Get descriptions of the form's requested fields."""
264
- a = self.all_fields
265
-
266
- context = "### Output Fields:\n"
267
- for idx, i in enumerate(self.request_fields):
268
- context += f"Input No.{idx+1}: {i}\n"
269
- if getattr(a[i], "description", None):
270
- context += f" - description: {a[i].description}.\n"
271
- if getattr(a[i], "examples", None):
272
- context += f" - examples: {a[i].examples}.\n"
273
-
274
- return context
275
-
276
- @override
277
- def update_field(
278
- self,
279
- field_name: str,
280
- value: Any = UNDEFINED,
281
- annotation: Any = UNDEFINED,
282
- field_obj: FieldInfo | Any = UNDEFINED,
283
- **kwargs: Any,
284
- ) -> None:
285
- """Update a field in the form.
286
-
287
- Extends the base update_field method to also update the
288
- init_input_kwargs dictionary.
289
-
290
- Args:
291
- field_name: The name of the field to update.
292
- value: The value to assign to the field.
293
- annotation: The type annotation for the field.
294
- field_obj: The field object containing metadata.
295
- **kwargs: Additional keyword arguments for field configuration.
60
+ def fill_fields(self, **kwargs) -> None:
296
61
  """
297
- super().update_field(
298
- field_name,
299
- value=value,
300
- annotation=annotation,
301
- field_obj=field_obj,
302
- **kwargs,
303
- )
304
- self._fill_init_input_kwargs(field_name)
305
-
306
- @override
307
- def __setattr__(self, field_name: str, value: Any) -> None:
308
- """Set an attribute of the form.
309
-
310
- This method enforces the strict_form attribute, preventing
311
- modifications to certain fields when strict_form mode is enabled.
312
-
313
- Args:
314
- field_name: The name of the attribute to set.
315
- value: The value to assign to the attribute.
316
-
317
- Raises:
318
- ValueError: If attempting to modify a restrict_formed field
319
- in strict_form mode.
62
+ A small helper: fill fields in this form by direct assignment.
63
+ Usually you'd do 'myform(field=val, field2=val2)', but sometimes you want partial updates.
320
64
  """
321
- if self.strict_form and field_name in {
322
- "assignment",
323
- "input_fields",
324
- "request_fields",
325
- }:
326
- raise ValueError(
327
- f"Cannot modify {field_name} in strict form mode."
328
- )
329
-
330
- if field_name in {"input_fields", "request_fields"}:
331
- raise ValueError("Cannot modify input or request fields.")
332
-
333
- if field_name in {"init_input_kwargs"}:
334
- raise ValueError(f"Cannot modify restricted field {field_name}.")
335
-
336
- super().__setattr__(field_name, value)
337
- self._fill_init_input_kwargs(field_name)
338
-
339
- def _fill_init_input_kwargs(self, field_name: str) -> None:
340
- """Update init_input_kwargs if the field is an input field."""
341
- if field_name in self.input_fields:
342
- self.init_input_kwargs[field_name] = getattr(self, field_name)
343
-
344
- def check_is_workable(
345
- self,
346
- handle_how: Literal["raise", "return_missing"] = "raise",
347
- ) -> list[str] | None:
348
- """Check if all input fields are filled and the form is workable.
349
-
350
- Args:
351
- handle_how: How to handle missing inputs.
65
+ for k, v in kwargs.items():
66
+ setattr(self, k, v)
352
67
 
353
- Returns:
354
- List of missing inputs if handle_how is "return_missing",
355
- None otherwise.
356
-
357
- Raises:
358
- ValueError: If input fields are missing and handle_how is
359
- "raise".
68
+ def to_instructions(self) -> dict[str, Any]:
360
69
  """
361
- if self.strict_form and self.has_processed:
362
- raise ValueError("Cannot modify processed form in strict mode.")
363
-
364
- missing_inputs = []
365
- invalid_values = [UNDEFINED, PydanticUndefined]
366
- if not self.none_as_valid_value:
367
- invalid_values.append(None)
368
-
369
- for i in self.input_fields:
370
- if getattr(self, i) in invalid_values:
371
- missing_inputs.append(i)
372
-
373
- if missing_inputs:
374
- if handle_how == "raise":
375
- raise ValueError(f"Incomplete input fields: {missing_inputs}")
376
- elif handle_how == "return_missing":
377
- return missing_inputs
378
-
379
- def is_completed(self) -> bool:
380
- """Check if the form has been completed."""
381
- try:
382
- self.check_is_completed(handle_how="raise")
383
- return True
384
- except Exception:
385
- return False
386
-
387
- def is_workable(self) -> bool:
388
- """Check if the form is workable."""
389
- try:
390
- self.check_is_workable(handle_how="raise")
391
- return True
392
- except Exception:
393
- return False
394
-
395
- def to_dict(self, *, valid_only: bool = False) -> dict[str, Any]:
396
- """Convert the form to a dictionary.
397
-
398
- Args:
399
- valid_only: Whether to include only valid fields in the output.
400
-
401
- Returns:
402
- A dictionary representation of the form.
403
- """
404
- _dict = super().to_dict()
405
- if not valid_only:
406
- return _dict
407
-
408
- disallow_values = [UNDEFINED, PydanticUndefined]
409
- if not self.none_as_valid_value:
410
- disallow_values.append(None)
411
- return {k: v for k, v in _dict.items() if v not in disallow_values}
412
-
413
- @override
414
- @classmethod
415
- def from_dict(cls, data: dict, **kwargs) -> T:
416
- """Create a Form instance from a dictionary.
417
-
418
- Args:
419
- data: The input data for creating the form.
420
- **kwargs: Additional keyword arguments.
421
-
422
- Returns:
423
- The created Form instance.
424
- """
425
- input_data = copy(data)
426
-
427
- input_data.pop("lion_class", None)
428
- input_data.pop("input_fields", None)
429
- input_data.pop("request_fields", None)
430
- task = input_data.pop("task", "")
431
-
432
- extra_fields = {}
433
- for k, v in list(input_data.items()):
434
- if k not in cls.model_fields:
435
- extra_fields[k] = input_data.pop(k)
436
- obj = cls.model_validate(input_data, **kwargs)
437
- obj.task = task
438
- for k, v in extra_fields.items():
439
- obj.update_field(field_name=k, value=v)
440
-
441
- metadata = copy(data.get("metadata", {}))
442
- last_updated = metadata.get("last_updated", None)
443
- if last_updated is not None:
444
- obj.metadata.set(["last_updated"], last_updated)
445
- else:
446
- obj.metadata.pop(["last_updated"], None)
447
- return obj
448
-
449
- def fill_input_fields(
450
- self,
451
- form: BaseForm | Any = None,
452
- **value_kwargs,
453
- ) -> None:
454
- """Fill the form's input fields with values.
455
-
456
- Args:
457
- form: A form to copy values from.
458
- **value_kwargs: Values to use for filling the input fields.
459
-
460
- Raises:
461
- TypeError: If the provided form is not a BaseForm instance.
70
+ Return a small dictionary that an LLM can read as an 'instruction context'.
462
71
  """
463
- if form is not None and not isinstance(form, BaseForm):
464
- raise TypeError("Provided form is not a BaseForm instance.")
465
-
466
- for i in self.input_fields:
467
- if self.none_as_valid_value:
468
- if getattr(self, i) is not UNDEFINED:
469
- continue
470
- value = value_kwargs.get(i, UNDEFINED)
471
- if value is UNDEFINED:
472
- value = copy(getattr(form, i, UNDEFINED))
473
- if value is not UNDEFINED:
474
- setattr(self, i, value)
475
- else:
476
- if getattr(self, i) in [UNDEFINED, None]:
477
- value = value_kwargs.get(i)
478
- if value in [UNDEFINED, None]:
479
- value = copy(getattr(form, i, UNDEFINED))
480
- if value not in [UNDEFINED, None]:
481
- setattr(self, i, value)
482
-
483
- def fill_request_fields(
484
- self,
485
- form: BaseForm = None,
486
- **value_kwargs,
487
- ) -> None:
488
- """Fill the form's request fields with values.
489
-
490
- Args:
491
- form: A form to copy values from.
492
- **value_kwargs: Values to use for filling the request fields.
493
-
494
- Raises:
495
- TypeError: If the provided form is not a BaseForm instance.
496
- """
497
- if form is not None and not isinstance(form, BaseForm):
498
- raise TypeError("Provided form is not a BaseForm instance.")
499
-
500
- for i in self.request_fields:
501
- if self.none_as_valid_value:
502
- if getattr(self, i) is not UNDEFINED:
503
- continue
504
- value = value_kwargs.get(i, UNDEFINED)
505
- if value is UNDEFINED:
506
- value = copy(getattr(form, i, UNDEFINED))
507
- if value is not UNDEFINED:
508
- setattr(self, i, value)
509
- else:
510
- if getattr(self, i) in [UNDEFINED, None]:
511
- value = value_kwargs.get(i)
512
- if value in [UNDEFINED, None]:
513
- value = copy(getattr(form, i, UNDEFINED))
514
- if value not in [UNDEFINED, None]:
515
- setattr(self, i, value)
516
-
517
- @classmethod
518
- def from_form(
519
- cls,
520
- form: BaseForm | type[BaseForm],
521
- guidance: str | dict[str, Any] | None = None,
522
- assignment: str | None = None,
523
- strict_form: bool = False,
524
- task_description: str | None = None,
525
- fill_inputs: bool = True,
526
- none_as_valid_value: bool = False,
527
- output_fields: list[str] | None = None,
528
- same_form_output_fields: bool = False,
529
- **input_value_kwargs,
530
- ) -> "Form":
531
- """Create a Form instance from another form.
532
-
533
- Args:
534
- form: The form to copy from.
535
- guidance: Guidance for the new form.
536
- assignment: The assignment for the new form.
537
- strict_form: Whether the new form should be strict_form.
538
- task_description: A description of the task.
539
- fill_inputs: Whether to fill input fields.
540
- none_as_valid_value: Whether to treat None as a valid value.
541
- output_fields: Output fields for the new form.
542
- same_form_output_fields: Whether to copy output fields from the
543
- original form.
544
- **input_value_kwargs: Values for filling the new form's input
545
- fields.
546
-
547
- Returns:
548
- The created Form instance.
549
-
550
- Raises:
551
- TypeError: If the provided form is not a BaseForm or its subclass.
552
- ValueError: If both output_fields and same_form_output_fields are
553
- provided.
554
- """
555
- if inspect.isclass(form):
556
- if not issubclass(form, BaseForm):
557
- raise TypeError("Provided form is not a BaseForm class.")
558
- form_fields = form.model_fields
559
- else:
560
- if not isinstance(form, BaseForm):
561
- raise TypeError("Provided form is not a BaseForm instance.")
562
- form_fields = form.all_fields
563
-
564
- if same_form_output_fields:
565
- if output_fields:
566
- raise ValueError(
567
- "Cannot provide output_fields and "
568
- "same_form_output_fields at the same time."
569
- )
570
- output_fields = copy(form.output_fields)
571
-
572
- if not assignment:
573
- if not getattr(form, "assignment", None):
574
- raise ValueError("Assignment is missing.")
575
- assignment = form.assignment
576
-
577
- obj = cls(
578
- guidance=guidance or getattr(form, "guidance", None),
579
- assignment=assignment,
580
- task_description=task_description
581
- or getattr(
582
- form,
583
- "task_description",
584
- "",
72
+ return {
73
+ "assignment": self.assignment,
74
+ "flow": (
75
+ self.flow_definition.model_dump()
76
+ if self.flow_definition
77
+ else None
585
78
  ),
586
- none_as_valid_value=none_as_valid_value
587
- or getattr(form, "none_as_valid_value", False),
588
- strict_form=strict_form or getattr(form, "strict_form", False),
589
- output_fields=output_fields,
590
- )
591
-
592
- for i in obj.work_fields:
593
- if i not in form_fields:
594
- raise ValueError(f"Invalid assignment field: {i}")
595
- obj.update_field(i, field_obj=form_fields[i])
596
-
597
- if fill_inputs:
598
- if inspect.isclass(form):
599
- obj.fill_input_fields(**input_value_kwargs)
600
- else:
601
- obj.fill_input_fields(form=form, **input_value_kwargs)
602
-
603
- return obj
604
-
605
- def remove_request_from_output(self) -> None:
606
- """Remove the request fields from the output fields."""
607
- for i in self.request_fields:
608
- if i in self.output_fields:
609
- self.output_fields.remove(i)
610
-
611
- def _append_to_one(
612
- self,
613
- field_name: str,
614
- field_type: Literal["input", "output", "request"],
615
- value: Any = UNDEFINED,
616
- annotation: Any = UNDEFINED,
617
- field_obj: FieldInfo | Any = UNDEFINED,
618
- **kwargs,
619
- ) -> None:
620
- """Append a field to one of the field lists.
621
-
622
- Args:
623
- field_name: The name of the field to append.
624
- field_type: The type of field to append.
625
- value: The value of the field.
626
- annotation: The type annotation for the field.
627
- field_obj: The field object containing metadata.
628
- **kwargs: Additional keyword arguments for field configuration.
629
-
630
- Raises:
631
- ValueError: If the field type is invalid or if appending to
632
- input or request fields in strict_form mode.
633
- """
634
-
635
- if self.strict_form and field_type in {"input", "request"}:
636
- raise ValueError(
637
- f"Cannot modify {field_type} fields in strict form mode."
638
- )
639
-
640
- config = {
641
- "field_name": field_name,
642
- "value": value,
643
- "annotation": annotation,
644
- "field_obj": field_obj,
645
- **kwargs,
79
+ "guidance": self.guidance,
80
+ "task": self.task,
81
+ "required_outputs": self.output_fields,
646
82
  }
647
-
648
- match field_type:
649
- case "input":
650
- if field_name not in self.input_fields:
651
- self.input_fields.append(field_name)
652
- if field_name not in self.assignment:
653
- self.assignment = f"{field_name}, " + self.assignment
654
-
655
- case "request":
656
- if field_name not in self.request_fields:
657
- self.request_fields.append(field_name)
658
- config.pop("value")
659
- if field_name not in self.assignment:
660
- self.assignment += f", {field_name}"
661
-
662
- case "output":
663
- if field_name not in self.output_fields:
664
- self.output_fields.append(field_name)
665
-
666
- case _:
667
- raise ValueError(f"Invalid field type {field_type}")
668
-
669
- if (
670
- any(
671
- [
672
- value is not UNDEFINED,
673
- annotation is not UNDEFINED,
674
- field_obj is not UNDEFINED,
675
- bool(kwargs),
676
- ]
677
- )
678
- or field_name not in self.all_fields
679
- ):
680
- self.update_field(**config)
681
-
682
- def append_to_input(
683
- self,
684
- field_name: str,
685
- value: Any = UNDEFINED,
686
- annotation: Any = UNDEFINED,
687
- field_obj: FieldInfo | Any = UNDEFINED,
688
- **kwargs,
689
- ) -> None:
690
- """Append a field to the input fields.
691
-
692
- Args:
693
- field_name: The name of the field to append.
694
- value: The value of the field.
695
- annotation: The type annotation for the field.
696
- field_obj: The field object containing metadata.
697
- **kwargs: Additional keyword arguments for field configuration.
698
-
699
- Raises:
700
- ValueError: If appending the field fails.
701
- """
702
- try:
703
- self._append_to_one(
704
- field_name=field_name,
705
- field_type="input",
706
- value=value,
707
- annotation=annotation,
708
- field_obj=field_obj,
709
- **kwargs,
710
- )
711
- except Exception as e:
712
- raise ValueError(
713
- f"Failed to append {field_name} to input fields."
714
- ) from e
715
-
716
- def append_to_output(
717
- self,
718
- field_name: str,
719
- value: Any = UNDEFINED,
720
- annotation: Any = UNDEFINED,
721
- field_obj: FieldInfo | Any = UNDEFINED,
722
- **kwargs,
723
- ) -> None:
724
- """Append a field to the output fields.
725
-
726
- Args:
727
- field_name: The name of the field to append.
728
- value: The value of the field.
729
- annotation: The type annotation for the field.
730
- field_obj: The field object containing metadata.
731
- **kwargs: Additional keyword arguments for field configuration.
732
-
733
- Raises:
734
- ValueError: If appending the field fails.
735
- """
736
-
737
- try:
738
- self._append_to_one(
739
- field_name=field_name,
740
- field_type="output",
741
- value=value,
742
- annotation=annotation,
743
- field_obj=field_obj,
744
- **kwargs,
745
- )
746
- except Exception as e:
747
- raise ValueError(
748
- f"Failed to append {field_name} to output fields."
749
- ) from e
750
-
751
- def append_to_request(
752
- self,
753
- field_name: str,
754
- annotation: Any = UNDEFINED,
755
- field_obj: FieldInfo | Any = UNDEFINED,
756
- **kwargs,
757
- ) -> None:
758
- """Append a field to the request fields.
759
-
760
- Args:
761
- field_name: The name of the field to append.
762
- value: The value of the field.
763
- annotation: The type annotation for the field.
764
- field_obj: The field object containing metadata.
765
- **kwargs: Additional keyword arguments for field configuration.
766
-
767
- Raises:
768
- ValueError: If appending the field fails.
769
- """
770
- if "value" in kwargs:
771
- raise ValueError("Cannot provide value to request fields.")
772
- try:
773
- self._append_to_one(
774
- field_name=field_name,
775
- field_type="request",
776
- annotation=annotation,
777
- field_obj=field_obj,
778
- **kwargs,
779
- )
780
- except Exception as e:
781
- raise ValueError(
782
- f"Failed to append {field_name} to request fields."
783
- ) from e
784
-
785
-
786
- __all__ = ["Form"]