=========
Packaging
=========
Packaging is bundling up your python library so that it can be easily ``pip install`` by others.

Typically this involves:

1. Bundling the code into a Built Distribution (wheel) and/or Source Distribution (sdist).

2. Uploading (publishing) the distribution(s) to python package repository, like PyPI.

This section is a brief bootcamp on package **configuration** for a CLI application.
This is **not** intended to be a complete tutorial on python packaging and publishing.
In this tutorial, replace all instances of ``mypackage`` with your own project name.

---------------
\_\_main\_\_.py
---------------

In python, if you have a module ``mypackage/__main__.py``, it will be executed with the bash command ``python -m mypackage``.

A pretty bare-bones Cyclopts ``mypackage/__main__.py`` will look like:

.. code-block:: python

   # mypackage/__main__.py

   import cyclopts

   app = cyclopts.App()

   @app.command
   def foo(name: str):
       print(f"Hello {name}!")

   if __name__ == "__main__":
       app()


.. code-block:: console

   $ python -m mypackage World
   Hello World!

-----------
Entrypoints
-----------
If you want your application to be callable like a standard bash executable (i.e. ``my-package`` instead of ``python -m mypackage``), we'll need to add an entrypoint_.

Modern Python projects typically use ``pyproject.toml`` for configuration. The standard way to define console scripts is:

.. code-block:: toml

   # pyproject.toml
   [project.scripts]
   my-package = "mypackage.__main__:app"

This creates an executable named ``my-package`` that executes the callable ``app`` object (from the right of the colon) from the python module ``mypackage.__main__``.
Note that this configuration is independent of any special naming, like ``__main__`` or ``app``.

^^^^^^^^^^^^^^^^^^^^^
Legacy Configurations
^^^^^^^^^^^^^^^^^^^^^

For older projects, you may encounter these alternative formats:

**setup.py:**

.. code-block:: python

    # setup.py
    from setuptools import setup

    setup(
        # There should be a lot more fields populated here.
        entry_points={
            "console_scripts": [
                "my-package = mypackage.__main__:app",
            ]
        },
    )

**setup.cfg:**

.. code-block:: cfg

    # setup.cfg
    [options.entry_points]
    console_scripts =
        my-package = mypackage.__main__:app

**Poetry:**

.. code-block:: toml

   # pyproject.toml
   [tool.poetry.scripts]
   my-package = "mypackage.__main__:app"

The setuptools entrypoint_ documentation goes into further detail.

.. _Result Action:

-------------
Result Action
-------------

When using Cyclopts as a CLI application, command return values are automatically handled appropriately. By default, :class:`~cyclopts.App` uses ``"print_non_int_sys_exit"`` mode, which calls :func:`sys.exit` with the appropriate exit code:

- String returns are printed to stdout, then :func:`sys.exit(0) <sys.exit>` is called
- Integer returns are passed to :func:`sys.exit(int) <sys.exit>` as the exit code
- Boolean returns are converted: :obj:`True` → :func:`sys.exit(0) <sys.exit>`, :obj:`False` → :func:`sys.exit(1) <sys.exit>`
- :obj:`None` returns call :func:`sys.exit(0) <sys.exit>`

This default behavior makes Cyclopts applications work consistently whether run directly as scripts or installed via `console_scripts entry points <https://packaging.python.org/en/latest/specifications/entry-points/#use-for-scripts>`_. The :attr:`~cyclopts.App.result_action` can be customized if different behavior is needed:


.. _Poetry: https://python-poetry.org
.. _entrypoint: https://setuptools.pypa.io/en/latest/userguide/entry_point.html#entry-points
