Repository: tmbo/questionary

Issue:
Add print question type to dictionary style prompt
### Describe the problem

With version `1.10.0` when I want to use both prompt with dictionary and `print` question type, I need to call several prompts and to merge answers:

```python
from pprint import pprint
from questionary import Separator, prompt
from questionary import print as qprint


def ask_dictstyle(**kwargs):
    questions = [
        {
            "type": "text",
            "name": "first",
            "message": "Your first message:",
        },
        {
            "type": "text",
            "name": "second",
            "message": "Your second message",
        },
    ]

    answers = prompt(questions)
    qprint("Oh! I need to give somme fancy explanations about what’s next!🦄", style="bold italic fg:darkred")
    questions = [
        {
            "type": "select",
            "name": "third",
            "message": "Select item",
            "choices": ["item1", "item2", Separator(), "other"],
        },
    ]
    answers.update(prompt(questions))
    return answers


if __name__ == "__main__":
    pprint(ask_dictstyle())

```

### Describe the solution

Adding `print` type to prompt dictionary would resolve the issue:

```python
from pprint import pprint
from questionary import Separator, prompt


def ask_dictstyle(**kwargs):
    questions = [
        {
            "type": "text",
            "name": "first",
            "message": "Your first message:",
        },
        {
            "type": "text",
            "name": "second",
            "message": "Your second message",
        },
        {
            "type": "print",
            "name": "help", #  Do I need a name?
            "message": "Oh! I need to give somme fancy explanations about what’s next!🦄",
            "style": "bold italic fg:darkred",
        },
        {
            "type": "select",
            "name": "third",
            "message": "Select item",
            "choices": ["item1", "item2", Separator(), "other"],
        },
    ]
    return prompt(questions)


if __name__ == "__main__":
    pprint(ask_dictstyle())
```

### Alternatives considered

_No response_

Discussion:
kiancross: Thanks for this PR.

> The pycodestyle test required by contributor's guide had many failures (mostly long lines) that were already present in the source code -- not changed in this PR.

I think this part of the contributor's guide needs to be updated. You should just be able to run `make develop` and then `make lint` to apply all formatting rules. The `# noqa` comments should not be necessary, as the aforementioned commands will sort out all line length issues.
kiancross: Looks good to me - thanks for the PR!

Patch:
diff --git a/examples/advanced_workflow.py b/examples/advanced_workflow.py
index 6f820f8..5b06633 100644
--- a/examples/advanced_workflow.py
+++ b/examples/advanced_workflow.py
@@ -6,6 +6,14 @@ from questionary import prompt
 
 def ask_dictstyle(**kwargs):
     questions = [
+        {
+            # just print a message, don't ask a question
+            # does not require a name (but if provided, is ignored) and does not return a value
+            "type": "print",
+            "name": "intro",
+            "message": "This example demonstrates advanced features! 🦄",
+            "style": "bold italic",
+        },
         {
             "type": "confirm",
             "name": "conditional_step",
@@ -27,6 +35,14 @@ def ask_dictstyle(**kwargs):
             "message": "Select item",
             "choices": ["item1", "item2", Separator(), "other"],
         },
+        {
+            # just print a message, don't ask a question
+            # does not require a name and does not return a value
+            "type": "print",
+            "message": "Please enter a value for 'other'",
+            "style": "bold italic fg:darkred",
+            "when": lambda x: x["second_question"] == "other",
+        },
         {
             "type": "text",
             # intentionally overwrites result from previous question
@@ -35,7 +51,7 @@ def ask_dictstyle(**kwargs):
             "when": lambda x: x["second_question"] == "other",
         },
     ]
-    return prompt(questions)
+    return prompt(questions, **kwargs)
 
 
 if __name__ == "__main__":
diff --git a/questionary/prompt.py b/questionary/prompt.py
index 0180f0d..5cabcee 100644
--- a/questionary/prompt.py
+++ b/questionary/prompt.py
@@ -11,6 +11,7 @@ from questionary import utils
 from questionary.constants import DEFAULT_KBI_MESSAGE
 from questionary.prompts import AVAILABLE_PROMPTS
 from questionary.prompts import prompt_by_name
+from questionary.prompts.common import print_formatted_text
 
 
 class PromptParameterException(ValueError):
@@ -143,7 +144,8 @@ def unsafe_prompt(
         # import the question
         if "type" not in question_config:
             raise PromptParameterException("type")
-        if "name" not in question_config:
+        # every type except 'print' needs a name
+        if "name" not in question_config and question_config["type"] != "print":
             raise PromptParameterException("name")
 
         _kwargs = kwargs.copy()
@@ -151,7 +153,7 @@ def unsafe_prompt(
 
         _type = _kwargs.pop("type")
         _filter = _kwargs.pop("filter", None)
-        name = _kwargs.pop("name")
+        name = _kwargs.pop("name", None) if _type == "print" else _kwargs.pop("name")
         when = _kwargs.pop("when", None)
 
         if true_color:
@@ -172,6 +174,22 @@ def unsafe_prompt(
                     "'when' needs to be function that accepts a dict argument"
                 )
 
+        # handle 'print' type
+        if _type == "print":
+            try:
+                message = _kwargs.pop("message")
+            except KeyError as e:
+                raise PromptParameterException("message") from e
+
+            # questions can take 'input' arg but print_formatted_text does not
+            # Remove 'input', if present, to avoid breaking during tests
+            _kwargs.pop("input", None)
+
+            print_formatted_text(message, **_kwargs)
+            if name:
+                answers[name] = None
+            continue
+
         choices = question_config.get("choices")
         if choices is not None and callable(choices):
             calculated_choices = choices(answers)


Test patch:
diff --git a/tests/test_examples.py b/tests/test_examples.py
index 8fca7fc..274faa6 100644
--- a/tests/test_examples.py
+++ b/tests/test_examples.py
@@ -101,3 +101,28 @@ def test_autocomplete_example():
 
     assert result_dict == {"ants": "Polyergus lucidus"}
     assert result_py == "Polyergus lucidus"
+
+
+def test_advanced_workflow_example():
+    from examples.advanced_workflow import ask_dictstyle
+
+    text = (
+        KeyInputs.ENTER
+        + "questionary"
+        + KeyInputs.ENTER
+        + KeyInputs.DOWN
+        + KeyInputs.DOWN
+        + KeyInputs.ENTER
+        + "Hello World"
+        + KeyInputs.ENTER
+        + "\r"
+    )
+
+    result_dict = ask_with_patched_input(ask_dictstyle, text)
+
+    assert result_dict == {
+        "intro": None,
+        "conditional_step": True,
+        "next_question": "questionary",
+        "second_question": "Hello World",
+    }
diff --git a/tests/test_prompt.py b/tests/test_prompt.py
index 186bae8..9a9a84b 100644
--- a/tests/test_prompt.py
+++ b/tests/test_prompt.py
@@ -2,6 +2,7 @@ import pytest
 
 from questionary.prompt import PromptParameterException
 from questionary.prompt import prompt
+from tests.utils import patched_prompt
 
 
 def test_missing_message():
@@ -47,3 +48,31 @@ def test_invalid_question_type():
                 }
             ]
         )
+
+
+def test_missing_print_message():
+    """Test 'print' raises exception if missing 'message'"""
+    with pytest.raises(PromptParameterException):
+        prompt(
+            [
+                {
+                    "name": "test",
+                    "type": "print",
+                }
+            ]
+        )
+
+
+def test_print_no_name():
+    """'print' type doesn't require a name so it
+    should not throw PromptParameterException"""
+    questions = [{"type": "print", "message": "Hello World"}]
+    result = patched_prompt(questions, "")
+    assert result == {}
+
+
+def test_print_with_name():
+    """'print' type should return {name: None} when name is provided"""
+    questions = [{"name": "hello", "type": "print", "message": "Hello World"}]
+    result = patched_prompt(questions, "")
+    assert result == {"hello": None}