> For the complete documentation index, see [llms.txt](https://cortex-docs.paloaltonetworks.com/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://cortex-docs.paloaltonetworks.com/demisto-sdk-development-guide/demisto-sdk-guide/demisto-sdk-commands/generate-yml-from-python.md).

# generate-yml-from-python

Generates a YAML file from Python code that includes its special syntax.

The output file name is the same as the Python file name but with the **`.yml`** extension instead of **`.py`**.

The generation currently supports integrations only. The feature is supported from content Base Pack version 1.20.0 and later. You must have the following line in your Python code:\
`from CommonServerPython import *`.

<details>

<summary>Arguments</summary>

| `-i, --input` | The path to the source Python file used for generation. (Required)                                                                    |
| ------------- | ------------------------------------------------------------------------------------------------------------------------------------- |
| `-f, --force` | Overwrite the existing YAML file. If this is not used and the YAML file already exists, the script does not generate a new YAML file. |

</details>

<details>

<summary>Initialization and Integration Configuration</summary>

The initialization begins with the `YMLMetadataCollector` object:

`metadata_collector = YMLMetadataCollector(integration_name="sample_name")`

* The generation only works if the name of the initialized object is `metadata_collector`.
* The only mandatory argument for the `YMLMetadataCollector` is the `integration_name` , which is used as your integration ID.
* All other arguments for the integration are passed in this initialization. See the examples below. Other arguments include:
  * `integration_name`
  * `display`
  * `image`
  * `detailed_description`
  * `description`
  * `category`
  * `tests`
  * `fromVersion`
  * `system`
  * `timeout`
  * `default_classifier`
  * `default_mapper_in`
  * `default_enabled`
  * `deprecated`
  * `default_enabled_x2`
  * `integration_name_x2`
  * `docker_image`
  * `is_feed`
  * `is_fetch`
  * `is_runonce`
  * `long_running`
  * `long_running_port`
  * `type`
  * `subtype`

[ConfKey](#UUID-552b438e-8954-64e1-4cbd-1e12daef0980_sidebar-idm4535349627484833796953686291_body)

To specify integration configuration keys, use the keyword `conf=[ConfKey(), ConfKey()]`. For example:

```programlisting
metadata_collector = YMLMetadataCollector(integration_name="some_name",
                                                      conf=[ConfKey(name="some_name",
                                                                    key_type=ParameterTypes.STRING),
                                                            ConfKey(name="some_other_name",
                                                                    key_type=ParameterTypes.NUMBER)])
```

`ConfKey` arguments include:

* `name`
* `display`
* `default_value`
* `required`
* `additional_info`
* `options`
* `input_type`

The trailing `s` was added to reduce circular imports.

The argument `options` takes a list of strings and specifies the argument as predefined and the list as the options.

`input_type` takes an enum class and results in the enum attributes as the predefined options.

For example:

```programlisting
class InputOptions(enum.Enum):
    A = "a"
    B = "b"

metadata_collector = YMLMetadataCollector(integration_name="some_name",
                                           conf=[ConfKey(name="some_name",
                                                         input_type=InputOptions)])
```

This results in the following metadata for the configuration key:

```programlisting
{
    "display": "some_name",
    "name": "some_name",
    "type": 0,
    "required": false,
    "options": ["a", "b"]
}
```

</details>

<details>

<summary>Adding Commands</summary>

To add command metadata, wrap the command function using `@metadata_collector.command()`. For example:

```programlisting
metadata_collector = YMLMetadataCollector(integration_name="some_name")

@metadata_collector.command(command_name="funky_command")
def funky_command():
    print("funk")
```

The only mandatory argument is `command_name`. Other `metadata_collector.command` arguments include:

* `deprecated`
* `execution`
* `description`
* `outputs_prefix`
* `outputs_list`
* `inputs_list`
* `file_output`
* `multiple_output_prefixes`

#### Restored Arguments

To reduce code duplication, reuse the following arguments:

* `command_name`
* `outputs_prefix`
* `execution`

For example:

```programlisting
metadata_collector = YMLMetadataCollector(integration_name="some_name")

@metadata_collector.command(command_name="funky-command", outputs_prefix="funk", execution=False, restore=True)
def funky_command(client, command_name, outputs_prefix, execution):
    """Some funky command"""
    print(f"The command name is {command_name} output_prefix is {outputs_prefix} and execution {execution}")
```

When running `funky_command(client=SomeClient)` the following is printed:

```programlisting
The command name is funky-command output_prefix is funk and execution False
```

Warnings:

* Specify `restore=True` in the `metadata_collector.command` arguments.
* The restored arguments do not show up in the command arguments in the YAML.
* Other arguments, including `client`, `args`, can be added to the function declaration without issues.

#### Command With File Output

If the command has a file output, specify `file_output=True` in the `@metadata_collector.command` arguments.

For example:

```programlisting
metadata_collector = YMLMetadataCollector(integration_name="some_name")

@metadata_collector.command(command_name="funky-command", outputs_prefix="FunkyFile", file_output=True)
def funky_command():
    print("funk")
```

This results in the following metadata for the command outputs:

```programlisting
  commands:
  - deprecated: false
    description: ''
    name: funky-command
    arguments: []
    outputs:
    - contextPath: FunkyFile.EntryID
      description: The EntryID of the report file.
      type: Unknown
    - contextPath: FunkyFile.Extension
      description: The extension of the report file.
      type: String
    - contextPath: FunkyFile.Name
      description: The name of the report file.
      type: String
    - contextPath: FunkyFile.Info
      description: The info of the report file.
      type: String
    - contextPath: FunkyFile.Size
      description: The size of the report file.
      type: Number
    - contextPath: FunkyFile.Type
      description: The type of the report file.
      type: String
```

**Metadata from metadata\_collector.command inputs**

To add command description, you can specify it in `@metadata_collector.command` under the description key.

For example:

```programlisting
metadata_collector = YMLMetadataCollector(integration_name="some_name")

@metadata_collector.command(command_name="funky-command", description="Some funky command.")
def funky_command():
    print("funk")
```

#### InputArgument

To explicitly add input arguments use the `InputArgument` object. For example:

```programlisting
metadata_collector = YMLMetadataCollector(integration_name="some_name")

@metadata_collector.command(command_name="funky-command",
                            description="Very funky",
                            inputs_list=[InputArgument(name="some_name",
                                                       description="some description."),
                                         InputArgument(name="some_other_name",
                                                       description="some other description.")])
def funky_command():
    print("funk")
```

`InputArgument` arguments include the following:

* `name`
* `description`
* `required`
* `default`
* `is_array`
* `secret`
* `execution`
* `options`
* `input_type`
* `default_arg`

Where `options` and `input_type` behave similarly to those in the ConfKey: `options` to specify predefined arguments with list of options, and `input_type` to specify an enum class as the predefined options.

If `is_array` is not specified it is inferred from the `input_type` or defaults to `False`.

To set a default value to a given input argument, use the `default='some val'`.

In order to specify the input argument as a default argument of the command (to be used without specifying argument name), specify `default_arg=True`. For example `!ip 1.1.1.1` will be equivalent to `!ip ip=1.1.1.1` if `ip` is set as a default argument.

#### OutputArgument

To explicitly add output arguments, use the `OutputArgument` object.

For example:

```programlisting
metadata_collector = YMLMetadataCollector(integration_name="some_name")

@metadata_collector.command(command_name="funky-command",
                            description="Very funky",
                            output_prefix="funk",
                            outputs_list=[OutputArgument(name="some_out",
                                                         description="some desc",
                                                         output_type=int),
                                          OutputArgument(name="some_other_name",
                                                         description="some other description.",
                                                         output_type=bool)])
def funky_command():
    print("funk")
```

`OutputArgument` arguments include the following:

* `name`
* `description`
* `output_type`
* `prefix`

Where `output_type` is converted from a Python type to available context outputs types.

The output prefix in the context data is the one specified in the `@metadata_collector.command`, for our example it will be `funk`.

Multiple prefixes

Tto have multiple prefixes, specify the `multiple_output_prefixes=True` argument in `@metadata_collector.command` and add a `prefix` argument to every `OutputArgument`.

For example:

```programlisting
metadata_collector = YMLMetadataCollector(integration_name="some_name")

@metadata_collector.command(command_name="funky-command",
                            description="Very funky",
                            multiple_output_prefixes=True,
                            outputs_list=[OutputArgument(name="some_out",
                                                         description="some desc",
                                                         output_type=int,
                                                         prefix="funky"),
                                          OutputArgument(name="some_other_name",
                                                         description="some other description.",
                                                         output_type=bool,
                                                         prefix="classy")])
def funky_command():
    print("funk")
```

If `prefix` was not specified in the `OutputArgument`, the general `outputs_prefix` provided in `@metadata_collector.command` is used. If `outputs_prefix` is not defined, the `integration_name` provided in `YMLMetadataCollector` initialization is used.

[Metadata from docstring and declaration](#UUID-552b438e-8954-64e1-4cbd-1e12daef0980_sidebar-idm4538856542443233796974347239_body)

As an alternative to specifying the command metadata in the `@metadata_collector.command` usage, you can specify the description, the output arguments, and the input arguments in the docstring of the function.

#### Description

To specify a description of the command in the docstring, use the first line (or lines). For example:

```programlisting
metadata_collector = YMLMetadataCollector(integration_name="some_name")

@metadata_collector.command(command_name="funky-command")
def funky_command():
    """Very funky command."""
    print("funk")
```

The description is `Very funky command`. Use an empty line to end the description. For example:

```programlisting
metadata_collector = YMLMetadataCollector(integration_name="some_name")

@metadata_collector.command(command_name="funky-command")
def funky_command():
    """The command is used to give up the funk.
    It is a very funky command.

    The function implementation though requires no funk.
    """
    print("funk")
```

The description is `The command is used to give up the funk.\n It is a very funky command.`.

#### Args

To specify input arguments, use the `Args` section. For example:

```programlisting
metadata_collector = YMLMetadataCollector(integration_name="some_name")

@metadata_collector.command(command_name="funky-command")
def funky_command():
    """Very funky command.

    Args:
        beat: The beat to the funk.
        punk: The punk to the funk.
    """
    print("funk")
```

This results in the following input argument metadata:

```programlisting
    arguments:
    - name: beat
      isArray: false
      description: The beat to the funk.
      required: false 
      secret: false
      default: false
    - name: punk
      isArray: false
      description: The punk to the funk.
      required: false
      secret: false
      default: false
```

To specify whether the argument is an array, you can either add an argument type in the docstring, such as:

```programlisting
metadata_collector = YMLMetadataCollector(integration_name="some_name")

@metadata_collector.command(command_name="funky-command")
def funky_command():
    """Very funky command.

    Args:
        beat (int): The beat to the funk.
        punk (str): The punk to the funk.
    """
    print("funk")
```

or specify it as a type in the function declaration, such as:

```programlisting
metadata_collector = YMLMetadataCollector(integration_name="some_name")

@metadata_collector.command(command_name="funky-command")
def funky_command(beat: int, punk: str):
    """Very funky command.

    Args:
        beat: The beat to the funk.
        punk: The punk to the funk.
    """
    print("funk")
```

If your argument has predefined options, you can either add it as an enum class as the argument type, such as:

```programlisting
class BeatOptions(enum.Enum):
    UpTown = "up_town"
    DownTown = "down_town"

@metadata_collector.command(command_name="funky-command")
def funky_command(beat: BeatOptions):
    """Very funky command.

    Args:
        beat: The beat to the funk.
    """
    print("funk")
```

Or equivalently:

```programlisting
class BeatOptions(enum.Enum):
    UpTown = "up_town"
    DownTown = "down_town"

@metadata_collector.command(command_name="funky-command")
def funky_command(beat):
    """Very funky command.

    Args:
        beat (BeatOptions): The beat to the funk.
    """
    print("funk")
```

Or specify `options=[<option1<,<option2>, ...]`. such as:

```programlisting
@metadata_collector.command(command_name="funky-command")
def funky_command():
    """Very funky command.

    Args:
        beat: The beat to the funk. options=[up_town, down_town].
    """
    print("funk")
```

To specify a default, use default=\<The default>. For example:

```programlisting
@metadata_collector.command(command_name="funky-command")
def funky_command():
    """Very funky command.

    Args:
        punk: The punk to the funk. default=Avril Lavigne.
    """
    print("funk")
```

To specify the argument is a secret, add `secret.` to the argument description.

To specify `execution=True`, add `potentially harmful.` or `execution.` to the argument description.

To specify `required=True`, add `required.` to the argument description.

To specify the argument as a default argument, add `default argument.` to the argument description. This will result in being able to use the argument without specifing argument name. For example `!ip 1.1.1.1` will be equivalent to `!ip ip=1.1.1.1` if `ip` is set as a default argument.

```programlisting
@metadata_collector.command(command_name="funky-command")
def funky_command():
    """Very funky command.

    Args:
        punk: The punk to the funk. default=Avril Lavigne. secret. execution. default argument.
    """
    print("funk")
```

The `docstring` in the example above results in the following input argument metadata:

```programlisting
    arguments:
    - name: punk
      isArray: false
      description: The punk to the funk.
      required: false
      secret: true
      default: true
      defaultValue: Avril Lavigne
      execution: true
```

#### Context Outputs

To specify command outputs, use the `Context Outputs` section. For example:

```programlisting
metadata_collector = YMLMetadataCollector(integration_name="some_name")

@metadata_collector.command(command_name="funky-command")
def funky_command():
    """Very funky command.

    Context Outputs:
        song (str): A funky song.
    """
    print("funk")
```

This results in the following metadata:

```programlisting
  - deprecated: false
    description: Very funky command.
    name: funky-command
    arguments: []
    outputs:
    - contextPath: some_name.song
      description: A funky song.
      type: String
```

To add an output prefix for all context outputs of the command, use the `outputs_prefix` in the `@metadata_collector.command`.

Multiple Prefixes

To specify multiple output prefixes in the docstring you can specify the `multiple_output_prefixes=True` in `@metadata_collector.command` and add the prefixes to the command names. For example:

```programlisting
metadata_collector = YMLMetadataCollector(integration_name="some_name")

@metadata_collector.command(command_name="funky-command", multiple_output_prefixes=True)
def funky_command():
    """Very funky command.

    Context Outputs:
        funky.song (str): A funky song.
        classy.song (str): An alternative classy song.
    """
    print("funk")
```

[Notes](#UUID-552b438e-8954-64e1-4cbd-1e12daef0980_sidebar-idm4524441268419233797064130422_body)

* Metadata from explicit `@metadata_collector.command` arguments can be used along with metadata provided in `docstring`, as long as it is not the same field. For example, you can provide `inputs_list` via `@metadata_collector.command` and `Context Outputs` in the `docstring`, specifying both inputs and outputs.
* If a field is specified explicitly in `@metadata_collector.command` and in the `docstring`, the explicit one is used.

For example:

```programlisting
metadata_collector = YMLMetadataCollector(integration_name="some_name")

@metadata_collector.command(command_name="funky-command",
                            description="Very funky")
def funky_command():
    """Very Classy"""
    print("func")
```

The description of the latter command will be `Very funky`.

</details>

<details>

<summary>Hello World Example</summary>

```programlisting
import demistomock as demisto
from CommonServerPython import *
from CommonServerUserPython import *

import enum
import json
import urllib3
import dateparser
import traceback
from typing import Any, Dict, List, Optional, Union

# Disable insecure warnings
urllib3.disable_warnings()

''' CONSTANTS '''

DATE_FORMAT = '%Y-%m-%dT%H:%M:%SZ'
MAX_INCIDENTS_TO_FETCH = 50
HELLOWORLD_SEVERITIES = ['Low', 'Medium', 'High', 'Critical']

# Starting with init of metadata_collector
metadata_collector = YMLMetadataCollector(integration_name="HelloWorldNoYML",
                                          description="This is the Hello World integration for getting started.",
                                          display="HelloWorldNoYML",
                                          category="Utilities",
                                          docker_image="demisto/python3:3.9.8.24399",
                                          is_fetch=True,
                                          long_running=False,
                                          long_running_port=False,
                                          is_runonce=False,
                                          integration_subtype="python3",
                                          integration_type="python",
                                          fromversion="5.0.0",
                                          default_mapper_in="HelloWorld-mapper",
                                          default_classifier="HelloWorld",
                                          conf=[ConfKey(name="url",
                                                        display="Server URL (e.g., https://soar.monstersofhack.com)",
                                                        required=True,
                                                        default_value="https://soar.monstersofhack.com"),
                                                ConfKey(name="isFetch",
                                                        display="Fetch incidents",
                                                        required=False,
                                                        key_type=ParameterTypes.BOOLEAN),
                                                ConfKey(name="incidentType",
                                                        display="Incident type",
                                                        required=False,
                                                        default_value="https://soar.monstersofhack.com",
                                                        key_type=ParameterTypes.SINGLE_SELECT),
                                                ConfKey(name="max_fetch",
                                                        display="Maximum number of incidents per fetch",
                                                        required=False,
                                                        default_value='10'),
                                                ConfKey(name="apikey",
                                                        display="API Key",
                                                        required=True,
                                                        key_type=ParameterTypes.TEXT_AREA_ENCRYPTED),
                                                ConfKey(name="threshold_ip",
                                                        display="Score threshold for IP reputation command",
                                                        required=False,
                                                        default_value='65',
                                                        additional_info="Set this to determine the HelloWorld score "
                                                                        "that will determine if an IP is malicious "
                                                                        "(0-100)"),
                                                ConfKey(name="threshold_domain",
                                                        display="Score threshold for domain reputation command",
                                                        required=False,
                                                        default_value='65',
                                                        additional_info="Set this to determine the HelloWorld score "
                                                                        "that will determine if a domain is malicious "
                                                                        "(0-100)"),
                                                ConfKey(name="alert_status",
                                                        display="Fetch alerts with status (ACTIVE, CLOSED)",
                                                        required=False,
                                                        default_value="ACTIVE",
                                                        options=["ACTIVE", "CLOSED"],
                                                        key_type=ParameterTypes.SINGLE_SELECT),
                                                ConfKey(name="alert_type",
                                                        display="Fetch alerts with type",
                                                        required=False,
                                                        additional_info="Comma-separated list of types of alerts to "
                                                                        "fetch. Types might change over time. Some "
                                                                        "examples are 'Bug' and 'Vulnerability'"),
                                                ConfKey(name="min_severity",
                                                        display="Minimum severity of alerts to fetch",
                                                        required=True,
                                                        default_value='Low',
                                                        options=["Low", "Medium", "High", "Critical"],
                                                        key_type=ParameterTypes.SINGLE_SELECT),
                                                ConfKey(name="first_fetch",
                                                        display="First fetch time",
                                                        required=False,
                                                        default_value="3 days"),
                                                ConfKey(name="insecure",
                                                        display="Trust any certificate (not secure)",
                                                        required=False,
                                                        key_type=ParameterTypes.BOOLEAN),
                                                ConfKey(name="proxy",
                                                        display="Use system proxy settings",
                                                        required=False,
                                                        key_type=ParameterTypes.BOOLEAN)
                                                ])

''' CLIENT CLASS '''


class Client(BaseClient):
    """Client class to interact with the service API

    This Client implements API calls, and does not contain any Demisto logic.
    It should only do requests and return data.
    It inherits from BaseClient defined in CommonServer Python.
    Most calls use _http_request() that handles proxy, SSL verification, etc.
    For this HelloWorld implementation, no special attributes are defined.
    """

    def get_ip_reputation(self, ip):
        """Gets the IP reputation using the '/ip' API endpoint

        :type ip: ``str``
        :param ip: IP address to get the reputation for

        :return: dict containing the IP reputation as returned from the API
        :rtype: ``Dict[str, Any]``
        """

        return self._http_request(
            method='GET',
            url_suffix='/ip',
            params={
                'ip': ip
            }
        )

    def get_domain_reputation(self, domain: str) -> Dict[str, Any]:
        """Gets the Domain reputation using the '/domain' API endpoint

        :type domain: ``str``
        :param domain: domain name to get the reputation for

        :return: dict containing the domain reputation as returned from the API
        :rtype: ``Dict[str, Any]``
        """

        return self._http_request(
            method='GET',
            url_suffix='/domain',
            params={
                'domain': domain
            }
        )

    def search_alerts(self, alert_status: Optional[str], severity: Optional[str],
                      alert_type: Optional[str], max_results: Optional[int],
                      start_time: Optional[int]) -> List[Dict[str, Any]]:
        """Searches for HelloWorld alerts using the '/get_alerts' API endpoint

        All the parameters are passed directly to the API as HTTP POST parameters in the request

        :type alert_status: ``Optional[str]``
        :param alert_status: status of the alert to search for. Options are: 'ACTIVE' or 'CLOSED'

        :type severity: ``Optional[str]``
        :param severity:
            severity of the alert to search for. Comma-separated values.
            Options are: "Low", "Medium", "High", "Critical"

        :type alert_type: ``Optional[str]``
        :param alert_type: type of alerts to search for. There is no list of predefined types

        :type max_results: ``Optional[int]``
        :param max_results: maximum number of results to return

        :type start_time: ``Optional[int]``
        :param start_time: start timestamp (epoch in seconds) for the alert search

        :return: list containing the found HelloWorld alerts as dicts
        :rtype: ``List[Dict[str, Any]]``
        """

        request_params: Dict[str, Any] = {}

        if alert_status:
            request_params['alert_status'] = alert_status

        if alert_type:
            request_params['alert_type'] = alert_type

        if severity:
            request_params['severity'] = severity

        if max_results:
            request_params['max_results'] = max_results

        if start_time:
            request_params['start_time'] = start_time

        return self._http_request(
            method='GET',
            url_suffix='/get_alerts',
            params=request_params
        )

    def get_alert(self, alert_id: str) -> Dict[str, Any]:
        """Gets a specific HelloWorld alert by id

        :type alert_id: ``str``
        :param alert_id: ID of the alert to return

        :return: dict containing the alert as returned from the API
        :rtype: ``Dict[str, Any]``
        """

        return self._http_request(
            method='GET',
            url_suffix='/get_alert_details',
            params={
                'alert_id': alert_id
            }
        )

    def update_alert_status(self, alert_id: str, alert_status: str) -> Dict[str, Any]:
        """Changes the status of a specific HelloWorld alert

        :type alert_id: ``str``
        :param alert_id: ID of the alert to return

        :type alert_status: ``str``
        :param alert_status: new alert status. Options are: 'ACTIVE' or 'CLOSED'

        :return: dict containing the alert as returned from the API
        :rtype: ``Dict[str, Any]``
        """

        return self._http_request(
            method='GET',
            url_suffix='/change_alert_status',
            params={
                'alert_id': alert_id,
                'alert_status': alert_status
            }
        )

    def scan_start(self, hostname: str) -> Dict[str, Any]:
        """Starts a HelloWorld scan on a specific hostname

        :type hostname: ``str``
        :param hostname: hostname of the machine to scan

        :return: dict containing the scan status as returned from the API
        :rtype: ``Dict[str, Any]``
        """

        return self._http_request(
            method='GET',
            url_suffix='/start_scan',
            params={
                'hostname': hostname
            }
        )

    def scan_status(self, scan_id: str) -> Dict[str, Any]:
        """Gets the status of a HelloWorld scan

        :type scan_id: ``str``
        :param scan_id: ID of the scan to retrieve status for

        :return: dict containing the scan status as returned from the API
        :rtype: ``Dict[str, Any]``
        """

        return self._http_request(
            method='GET',
            url_suffix='/check_scan',
            params={
                'scan_id': scan_id
            }
        )

    def scan_results(self, scan_id: str) -> Dict[str, Any]:
        """Gets the results of a HelloWorld scan

        :type scan_id: ``str``
        :param scan_id: ID of the scan to retrieve results for

        :return: dict containing the scan results as returned from the API
        :rtype: ``Dict[str, Any]``
        """

        return self._http_request(
            method='GET',
            url_suffix='/get_scan_results',
            params={
                'scan_id': scan_id
            }
        )

    def say_hello(self, name: str) -> str:
        """Returns 'Hello {name}'

        :type name: ``str``
        :param name: name to append to the 'Hello' string

        :return: string containing 'Hello {name}'
        :rtype: ``str``
        """

        return f'Hello {name}'


''' HELPER FUNCTIONS '''


def parse_domain_date(domain_date: Union[List[str], str], date_format: str = '%Y-%m-%dT%H:%M:%S.000Z') -> Optional[str]:
    """Converts WHOIS date format to an ISO8601 string

    Converts the HelloWorld domain WHOIS date (YYYY-mm-dd HH:MM:SS) format
    in a datetime. If a list is returned with multiple elements, takes only
    the first one.

    :type domain_date: ``Union[List[str],str]``
    :param date_format:
        a string or list of strings with the format 'YYYY-mm-DD HH:MM:SS'

    :return: Parsed time in ISO8601 format
    :rtype: ``Optional[str]``
    """

    if isinstance(domain_date, str):
        # if str parse the value
        domain_date_dt = dateparser.parse(domain_date)
        if domain_date_dt:
            return domain_date_dt.strftime(date_format)
    elif isinstance(domain_date, list) and len(domain_date) > 0 and isinstance(domain_date[0], str):
        # if list with at least one element, parse the first element
        domain_date_dt = dateparser.parse(domain_date[0])
        if domain_date_dt:
            return domain_date_dt.strftime(date_format)
    # in any other case return nothing
    return None


def convert_to_demisto_severity(severity: str) -> int:
    """Maps HelloWorld severity to Cortex XSOAR severity

    Converts the HelloWorld alert severity level ('Low', 'Medium',
    'High', 'Critical') to Cortex XSOAR incident severity (1 to 4)
    for mapping.

    :type severity: ``str``
    :param severity: severity as returned from the HelloWorld API (str)

    :return: Cortex XSOAR Severity (1 to 4)
    :rtype: ``int``
    """

    # In this case the mapping is straightforward, but more complex mappings
    # might be required in your integration, so a dedicated function is
    # recommended. This mapping should also be documented.
    return {
        'Low': IncidentSeverity.LOW,
        'Medium': IncidentSeverity.MEDIUM,
        'High': IncidentSeverity.HIGH,
        'Critical': IncidentSeverity.CRITICAL
    }[severity]


''' COMMAND FUNCTIONS '''


def test_module(client: Client, first_fetch_time: int) -> str:
    """Tests API connectivity and authentication'

    Returning 'ok' indicates that the integration works like it is supposed to.
    Connection to the service is successful.
    Raises exceptions if something goes wrong.

    :type client: ``Client``
    :param Client: HelloWorld client to use

    :type name: ``str``
    :param name: name to append to the 'Hello' string

    :return: 'ok' if test passed, anything else will fail the test.
    :rtype: ``str``
    """

    # INTEGRATION DEVELOPER TIP
    # Client class should raise the exceptions, but if the test fails
    # the exception text is printed to the Cortex XSOAR UI.
    # If you have some specific errors you want to capture (i.e., auth failure)
    # you should catch the exception here and return a string with a more
    # readable output (for example return 'Authentication Error, API Key
    # invalid').
    # Cortex XSOAR will print everything you return that is different than 'ok' as
    # an error.
    try:
        client.search_alerts(max_results=1, start_time=first_fetch_time, alert_status=None, alert_type=None,
                             severity=None)
    except DemistoException as e:
        if 'Forbidden' in str(e):
            return 'Authorization Error: make sure API Key is correctly set'
        else:
            raise e
    return 'ok'


# Example of a decorated function with args and context outputs in docstirng.
@metadata_collector.command(command_name="helloworld-say-hello", outputs_prefix="HelloWorld")
def say_hello_command(client: Client, args: Dict[str, Any], **kwargs) -> CommandResults:
    """Hello command - prints hello to anyone.

    Args:
        client (Client): HelloWorld client to use.
        name (str):  The name of whom you want to say hello to.

    Returns:
        A ``CommandResults`` object that is then passed to ``return_results``,
        that contains the hello world message

    Context Outputs:
        hello (str): Should be Hello **something** here.

    """

    name = args.get('name', None)
    if not name:
        raise ValueError('name not specified')

    # Call the Client function and get the raw response
    result = client.say_hello(name)
    readable_output = f'## {result}'

    return CommandResults(
        readable_output=readable_output,
        outputs_prefix='HelloWorld.hello',
        outputs_key_field='',
        outputs=result
    )


class SeverityEnum(enum.Enum):
    """YML configuration key types."""
    Low = "Low"
    Medium = "Medium"
    High = "High"
    Critical = "Critical"


# Example of input arguments list and command outputs in docstring.
SEARCH_ALERTS_INPUTS = [InputArgument(name='severity',  # option 1
                                      description='Filter by alert severity. Comma-separated value '
                                                  '(Low,Medium,High,Critical)',
                                      input_type=SeverityEnum),
                        InputArgument(name='status',
                                      description='Filter by alert status.',
                                      options=['ACTIVE', 'CLOSED']),
                        InputArgument(name='alert_type',
                                      description='Filter by alert type.'),
                        InputArgument(name='max_results',
                                      description='Maximum results to return.'),
                        InputArgument(name='start_time',
                                      description='Filter by start time. \nExamples:\n  \"3 days ago\"\n  '
                                                  '\"1 month\"\n  \"2019-10-10T12:22:00\"\n  \"2019-10-10\"')]


@metadata_collector.command(command_name='helloworld-search-alerts', inputs_list=SEARCH_ALERTS_INPUTS,
                            outputs_prefix='HelloWorld.Alert')
def search_alerts_command(client: Client, args: Dict[str, Any]) -> CommandResults:
    """Search HelloWorld Alerts.

    :type client: ``Client``
    :param Client: HelloWorld client to use

    :type args: ``Dict[str, Any]``
    :param args:
        all command arguments, usually passed from ``demisto.args()``.
        ``args['status']`` alert status. Options are 'ACTIVE' or 'CLOSED'
        ``args['severity']`` alert severity CSV
        ``args['alert_type']`` alert type
        ``args['start_time']``  start time as ISO8601 date or seconds since epoch
        ``args['max_results']`` maximum number of results to return

    :return:
        A ``CommandResults`` object that is then passed to ``return_results``,
        that contains alerts

    :rtype: ``CommandResults``

    Context Outputs:
        alert_id (str): Alert ID.
        alert_status (str): Alert status. Can be 'ACTIVE' or 'CLOSED'.
        alert_type (str): Alert type. For example 'Bug' or 'Vulnerability'.
        created (datetime.datetime): Alert created time. Format is ISO8601 (i.e., '2020-04-30T10:35:00.000Z').
        name (str): Alert name.
        severity (str): Alert severity. Can be 'Low', 'Medium', 'High' or 'Critical'.
    """

    status = args.get('status')

    severities: List[str] = HELLOWORLD_SEVERITIES
    severity = args.get('severity', None)
    if severity:
        severities = severity.split(',')
        if not all(s in HELLOWORLD_SEVERITIES for s in severities):
            raise ValueError(
                f'severity must be a comma-separated value '
                f'with the following options: {",".join(HELLOWORLD_SEVERITIES)}')

    alert_type = args.get('alert_type')

    start_time = arg_to_datetime(
        arg=args.get('start_time'),
        arg_name='start_time',
        required=False
    )

    max_results = arg_to_number(
        arg=args.get('max_results'),
        arg_name='max_results',
        required=False
    )

    alerts = client.search_alerts(
        severity=','.join(severities),
        alert_status=status,
        alert_type=alert_type,
        start_time=int(start_time.timestamp()) if start_time else None,
        max_results=max_results
    )

    for alert in alerts:
        if 'created' not in alert:
            continue
        created_time_ms = int(alert.get('created', '0')) * 1000
        alert['created'] = timestamp_to_datestring(created_time_ms)

    return CommandResults(
        outputs_prefix='HelloWorld.Alert',
        outputs_key_field='alert_id',
        outputs=alerts
    )


# Example of outputs dict and reuse of outputs_prefix
GET_ALERT_OUTPUTS = [OutputArgument(name='alert_id', output_type=str, description='Alert ID.'),
                     OutputArgument(name='created', output_type=datetime,
                                    description="Alert created time. Format is ISO8601 "
                                                "(i.e., '2020-04-30T10:35:00.000Z').")]


@metadata_collector.command(command_name='helloworld-get-alert', outputs_prefix='HelloWorld.Alert',
                            outputs_list=GET_ALERT_OUTPUTS, restore=True)
def get_alert_command(client: Client, args: Dict[str, Any], outputs_prefix) -> CommandResults:
    """Retrieve alert extra data by ID.

    :type client: ``Client``
    :param Client: HelloWorld client to use

    :param alert_id: alert ID to return

    :return:
        A ``CommandResults`` object that is then passed to ``return_results``,
        that contains an alert

    :rtype: ``CommandResults``
    """

    alert_id = args.get('alert_id', None)
    if not alert_id:
        raise ValueError('alert_id not specified')

    alert = client.get_alert(alert_id=alert_id)

    if 'created' in alert:
        created_time_ms = int(alert.get('created', '0')) * 1000
        alert['created'] = timestamp_to_datestring(created_time_ms)

    readable_output = tableToMarkdown(f'HelloWorld Alert {alert_id}', alert)

    return CommandResults(
        readable_output=readable_output,
        outputs_prefix=outputs_prefix,
        outputs_key_field='alert_id',
        outputs=alert
    )


# option 3
class StatusEnum(enum.Enum):
    """YML configuration key types."""
    ACTIVE = "ACTIVE"
    CLOSED = "CLOSED"


# Complicated args in docstring example. (required and options tags)
@metadata_collector.command(command_name='helloworld-update-alert-status', outputs_prefix='HelloWorld.Alert')
def update_alert_status_command(client: Client, args: Dict[str, Any]) -> CommandResults:
    """Update the status for an alert.

    Changes the status of a HelloWorld alert and returns the updated alert info.

    Args:
        client (Client): HelloWorld client to use.
        alert_id: required. Alert ID to update.
        status (StatusEnum): required. New status of the alert. Options=[ACTIVE, CLOSED].

    Returns:
       A ``CommandResults`` object that is then passed to ``return_results``,
        that contains an updated alert.

    Context Outputs:
        alert_id (str): Alert ID.
        updated (datetime): Alert update time. Format is ISO8601 (i.e., '2020-04-30T10:35:00.000Z').
        alert_status (str): Alert status. Can be 'ACTIVE' or 'CLOSED'.
    """

    alert_id = args.get('alert_id', None)
    if not alert_id:
        raise ValueError('alert_id not specified')

    status = args.get('status', None)
    if status not in ('ACTIVE', 'CLOSED'):
        raise ValueError('status must be either ACTIVE or CLOSED')

    alert = client.update_alert_status(alert_id, status)

    if 'updated' in alert:
        updated_time_ms = int(alert.get('updated', '0')) * 1000
        alert['updated'] = timestamp_to_datestring(updated_time_ms)

    readable_output = tableToMarkdown(f'HelloWorld Alert {alert_id}', alert)

    return CommandResults(
        readable_output=readable_output,
        outputs_prefix='HelloWorld.Alert',
        outputs_key_field='alert_id',
        outputs=alert
    )


def scan_results_command(client: Client, args: Dict[str, Any]) ->\
        Union[Dict[str, Any], CommandResults, List[CommandResults]]:
    """helloworld-scan-results command: Returns results for a HelloWorld scan

    :type client: ``Client``
    :param Client: HelloWorld client to use

    :type args: ``Dict[str, Any]``
    :param args:
        all command arguments, usually passed from ``demisto.args()``.
        ``args['scan_id']`` scan ID to retrieve results
        ``args['format']`` format of the results. Options are 'file' or 'json'

    :return:
        A ``CommandResults`` compatible to return ``return_results()``,
        that contains a scan result when json format is selected, or
        A Dict of entries also compatible to ``return_results()`` that
        contains the output file when file format is selected.

    :rtype: ``Union[Dict[str, Any],CommandResults]``
    """

    scan_id = args.get('scan_id', None)
    if not scan_id:
        raise ValueError('scan_id not specified')

    scan_format = args.get('format', 'file')

    results = client.scan_results(scan_id=scan_id)
    if scan_format == 'file':
        return (
            fileResult(
                filename=f'{scan_id}.json',
                data=json.dumps(results, indent=4),
                file_type=entryTypes['entryInfoFile']
            )
        )
    elif scan_format == 'json':
        cves: List[Common.CVE] = []
        command_results: List[CommandResults] = []
        entities = results.get('entities', [])
        for e in entities:
            if 'vulns' in e.keys() and isinstance(e['vulns'], list):
                cves.extend(
                    [Common.CVE(id=c, cvss=None, published=None, modified=None, description=None) for c in e['vulns']])

        readable_output = tableToMarkdown(f'Scan {scan_id} results', entities)
        command_results.append(CommandResults(
            readable_output=readable_output,
            outputs_prefix='HelloWorld.Scan',
            outputs_key_field='scan_id',
            outputs=results
        ))

        cves = list(set(cves))  # make the indicator list unique
        for cve in cves:
            command_results.append(CommandResults(
                readable_output=f"CVE {cve}",
                indicator=cve
            ))
        return command_results
    else:
        raise ValueError('Incorrect format, must be "json" or "file"')


''' MAIN FUNCTION '''


def main() -> None:
    """main function, parses params and runs command functions

    :return:
    :rtype:
    """

    api_key = demisto.params().get('apikey')

    base_url = urljoin(demisto.params()['url'], '/api/v1')

    verify_certificate = not demisto.params().get('insecure', False)

    first_fetch_time = arg_to_datetime(
        arg=demisto.params().get('first_fetch', '3 days'),
        arg_name='First fetch time',
        required=True
    )
    first_fetch_timestamp = int(first_fetch_time.timestamp()) if first_fetch_time else None
    assert isinstance(first_fetch_timestamp, int)
    proxy = demisto.params().get('proxy', False)

    demisto.debug(f'Command being called is {demisto.command()}')
    try:
        headers = {
            'Authorization': f'Bearer {api_key}'
        }
        client = Client(
            base_url=base_url,
            verify=verify_certificate,
            headers=headers,
            proxy=proxy)

        if demisto.command() == 'test-module':
            result = test_module(client, first_fetch_timestamp)
            return_results(result)

        elif demisto.command() == 'helloworld-say-hello':
            return_results(say_hello_command(client, demisto.args()))

        elif demisto.command() == 'helloworld-search-alerts':
            return_results(search_alerts_command(client, demisto.args()))

        elif demisto.command() == 'helloworld-get-alert':
            return_results(get_alert_command(client, demisto.args()))

        elif demisto.command() == 'helloworld-update-alert-status':
            return_results(update_alert_status_command(client, demisto.args()))

    except Exception as e:
        demisto.error(traceback.format_exc())  # print the traceback
        return_error(f'Failed to execute {demisto.command()} command.\nError:\n{str(e)}')


''' ENTRY POINT '''

if __name__ in ('__main__', '__builtin__', 'builtins'):
    main()
                                                ])
```

Result:

```programlisting
category: Utilities
description: This is the Hello World integration for getting started.
commonfields:
  id: HelloWorldNoYML
  version: -1
name: HelloWorldNoYML
display: HelloWorldNoYML
configuration:
- display: Server URL (e.g., https://soar.monstersofhack.com)
  name: url
  type: 0
  required: true
  defaultvalue: https://soar.monstersofhack.com
- display: Fetch incidents
  name: isFetch
  type: 8
  required: false
- display: Incident type
  name: incidentType
  type: 15
  required: false
  defaultvalue: https://soar.monstersofhack.com
- display: Maximum number of incidents per fetch
  name: max_fetch
  type: 0
  required: false
  defaultvalue: '10'
- display: API Key
  name: apikey
  type: 14
  required: true
- display: Score threshold for IP reputation command
  name: threshold_ip
  type: 0
  required: false
  defaultvalue: '65'
  additionalinfo: Set this to determine the HelloWorld score that will determine if
    an IP is malicious (0-100)
- display: Score threshold for domain reputation command
  name: threshold_domain
  type: 0
  required: false
  defaultvalue: '65'
  additionalinfo: Set this to determine the HelloWorld score that will determine if
    a domain is malicious (0-100)
- display: Fetch alerts with status (ACTIVE, CLOSED)
  name: alert_status
  type: 15
  required: false
  defaultvalue: ACTIVE
  options:
  - ACTIVE
  - CLOSED
- display: Fetch alerts with type
  name: alert_type
  type: 0
  required: false
  additionalinfo: Comma-separated list of types of alerts to fetch. Types might change
    over time. Some examples are 'Bug' and 'Vulnerability'.
- display: Minimum severity of alerts to fetch
  name: min_severity
  type: 15
  required: true
  defaultvalue: Low
  options:
  - Low
  - Medium
  - High
  - Critical
- display: First fetch time
  name: first_fetch
  type: 0
  required: false
  defaultvalue: 3 days
- display: Trust any certificate (not secure)
  name: insecure
  type: 8
  required: false
- display: Use system proxy settings
  name: proxy
  type: 8
  required: false
script:
  commands:
  - deprecated: false
    description: Retrieve alert extra data by ID.
    name: helloworld-get-alert
    arguments: []
    outputs:
    - contextPath: HelloWorld.Alert.alert_id
      description: Alert ID.
      type: String
    - contextPath: HelloWorld.Alert.created
      description: Alert created time. Format is ISO8601 (i.e., '2020-04-30T10:35:00.000Z').
      type: Unknown
  - deprecated: false
    description: Hello command - prints hello to anyone.
    name: helloworld-say-hello
    arguments:
    - name: name
      isArray: false
      description: The name of whom you want to say hello to.
      required: true
      secret: false
      default: false
    outputs:
    - contextPath: HelloWorld.hello
      description: Should be Hello **something** here.
      type: String
  - deprecated: false
    description: Search HelloWorld Alerts.
    name: helloworld-search-alerts
    arguments:
    - name: severity
      isArray: false
      description: Filter by alert severity. Comma-separated value (Low,Medium,High,Critical).
      required: true
      secret: false
      default: false
      predefined:
      - Low
      - Medium
      - High
      - Critical
      auto: PREDEFINED
    - name: status
      isArray: false
      description: Filter by alert status.
      required: true
      secret: false
      default: false
      predefined:
      - ACTIVE
      - CLOSED
      auto: PREDEFINED
    - name: alert_type
      isArray: false
      description: Filter by alert type.
      required: true
      secret: false
      default: false
    - name: max_results
      isArray: false
      description: Maximum number of results to return.
      required: true
      secret: false
      default: false
    - name: start_time
      isArray: false
      description: "Filter by start time. \nExamples:\n  \"3 days ago\"\n  \"1 month\"\
        \n  \"2019-10-10T12:22:00\"\n  \"2019-10-10\""
      required: true
      secret: false
      default: false
    outputs:
    - contextPath: HelloWorld.Alert.alert_id
      description: Alert ID.
      type: String
    - contextPath: HelloWorld.Alert.alert_status
      description: Alert status. Can be 'ACTIVE' or 'CLOSED'.
      type: String
    - contextPath: HelloWorld.Alert.alert_type
      description: Alert type. For example 'Bug' or 'Vulnerability'.
      type: String
    - contextPath: HelloWorld.Alert.created
      description: Alert created time. Format is ISO8601 (i.e., '2020-04-30T10:35:00.000Z').
      type: Date
    - contextPath: HelloWorld.Alert.name
      description: Alert name.
      type: String
    - contextPath: HelloWorld.Alert.severity
      description: Alert severity. Can be 'Low', 'Medium', 'High' or 'Critical'.
      type: String
  - deprecated: false
    description: Update the status for an alert.
    name: helloworld-update-alert-status
    arguments:
    - name: alert_id
      isArray: false
      description: Alert ID to update.
      required: true
      secret: false
      default: false
    - name: status
      isArray: false
      description: New status of the alert. Options=[ACTIVE, CLOSED].
      required: true
      secret: false
      default: false
      predefined:
      - ACTIVE
      - CLOSED
      auto: PREDEFINED
    outputs:
    - contextPath: HelloWorld.Alert.alert_id
      description: Alert ID.
      type: String
    - contextPath: HelloWorld.Alert.updated
      description: Alert update time. Format is ISO8601 (i.e., '2020-04-30T10:35:00.000Z').
      type: Unknown
    - contextPath: HelloWorld.Alert.alert_status
      description: Alert status. Can be 'ACTIVE' or 'CLOSED'.
      type: String
  script: '-'
  type: python
  subtype: python3
  dockerimage: demisto/python3:3.9.8.24399
  feed: false
  isfetch: true
  runonce: false
  longRunning: false
  longRunningPort: false
fromversion: 5.0.0
tests:
- No tests
defaultclassifier: HelloWorld
defaultmapperin: HelloWorld-mapper
```

</details>


---

# Agent Instructions
This documentation is published with GitBook. GitBook is the documentation platform designed so that both humans and AI agents can read, navigate, and reason over technical content effectively. Learn more at gitbook.com.

## Querying This Documentation
If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter, and the optional `goal` query parameter:

```
GET https://cortex-docs.paloaltonetworks.com/demisto-sdk-development-guide/demisto-sdk-guide/demisto-sdk-commands/generate-yml-from-python.md?ask=<question>&goal=<endgoal>
```

`ask` is the immediate question: it should be specific, self-contained, and written in natural language.
`goal` is optional and describes the broader end goal you are ultimately trying to accomplish on behalf of the user. GitBook uses it to tailor the answer towards what is most useful for that goal.

The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
