From 72c8aab818ff81d7491f646928c6ad4f48dd565b Mon Sep 17 00:00:00 2001 From: Vaghinak Basentsyan Date: Tue, 31 Jan 2023 16:37:10 +0400 Subject: [PATCH 1/2] docs update --- .pre-commit-config.yaml | 5 + docs/source/api_reference/api_client.rst | 142 +++++ docs/source/api_reference/api_metadata.rst | 223 +++++++ docs/source/api_reference/cli_client.rst | 220 +++++++ docs/source/api_reference/helpers.rst | 42 ++ docs/source/api_reference/index.rst | 26 + docs/source/conf.py | 57 +- docs/source/index.rst | 4 +- docs/source/superannotate.sdk.rst | 2 +- docs/source/tutorial.sdk.rst | 135 +--- docs/source/userguide/datafiles.rst | 555 +++++++++++++++++ docs/source/userguide/declarative_config.rst | 326 ++++++++++ .../userguide/dependency_management.rst | 422 +++++++++++++ docs/source/userguide/development_mode.rst | 271 ++++++++ docs/source/userguide/distribution.rst | 198 ++++++ docs/source/userguide/entry_point.rst | 571 +++++++++++++++++ docs/source/userguide/ext_modules.rst | 172 +++++ docs/source/userguide/extension.rst | 308 +++++++++ docs/source/userguide/index.rst | 48 ++ docs/source/userguide/miscellaneous.rst | 100 +++ docs/source/userguide/package_discovery.rst | 589 ++++++++++++++++++ docs/source/userguide/pyproject_config.rst | 260 ++++++++ docs/source/userguide/quickstart.rst | 134 ++++ requirements_extra.txt | 4 + tox.ini | 10 +- 25 files changed, 4694 insertions(+), 130 deletions(-) create mode 100644 docs/source/api_reference/api_client.rst create mode 100644 docs/source/api_reference/api_metadata.rst create mode 100644 docs/source/api_reference/cli_client.rst create mode 100644 docs/source/api_reference/helpers.rst create mode 100644 docs/source/api_reference/index.rst create mode 100644 docs/source/userguide/datafiles.rst create mode 100644 docs/source/userguide/declarative_config.rst create mode 100644 docs/source/userguide/dependency_management.rst create mode 100644 docs/source/userguide/development_mode.rst create mode 100644 docs/source/userguide/distribution.rst create mode 100644 docs/source/userguide/entry_point.rst create mode 100644 docs/source/userguide/ext_modules.rst create mode 100644 docs/source/userguide/extension.rst create mode 100644 docs/source/userguide/index.rst create mode 100644 docs/source/userguide/miscellaneous.rst create mode 100644 docs/source/userguide/package_discovery.rst create mode 100644 docs/source/userguide/pyproject_config.rst create mode 100644 docs/source/userguide/quickstart.rst diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 11137c579..16d05668b 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -36,6 +36,11 @@ repos: - id: check-byte-order-marker - id: trailing-whitespace - id: end-of-file-fixer + - repo: https://github.com/editorconfig-checker/editorconfig-checker.python + rev: '' # pick a git hash / tag to point to + hooks: + - id: editorconfig-checker + alias: ec # - repo: 'https://github.com/asottile/dead' # rev: v1.3.0 # hooks: diff --git a/docs/source/api_reference/api_client.rst b/docs/source/api_reference/api_client.rst new file mode 100644 index 000000000..69a88c8dc --- /dev/null +++ b/docs/source/api_reference/api_client.rst @@ -0,0 +1,142 @@ +========== +SAClient interface +========== + +Instantiation and authentication +_________________________________ + +.. autoclass:: superannotate.SAClient + + +Projects +________ + +.. _ref_search_projects: +.. automethod:: superannotate.SAClient.search_projects +.. automethod:: superannotate.SAClient.create_project +.. automethod:: superannotate.SAClient.create_project_from_metadata +.. automethod:: superannotate.SAClient.clone_project +.. automethod:: superannotate.SAClient.delete_project +.. automethod:: superannotate.SAClient.rename_project +.. _ref_get_project_metadata: +.. automethod:: superannotate.SAClient.get_project_by_id +.. automethod:: superannotate.SAClient.get_project_metadata +.. automethod:: superannotate.SAClient.get_project_image_count +.. automethod:: superannotate.SAClient.search_folders +.. automethod:: superannotate.SAClient.assign_folder +.. automethod:: superannotate.SAClient.unassign_folder +.. automethod:: superannotate.SAClient.get_folder_by_id +.. automethod:: superannotate.SAClient.get_folder_metadata +.. automethod:: superannotate.SAClient.create_folder +.. automethod:: superannotate.SAClient.delete_folders +.. automethod:: superannotate.SAClient.upload_images_to_project +.. automethod:: superannotate.SAClient.attach_items_from_integrated_storage +.. automethod:: superannotate.SAClient.upload_image_to_project +.. automethod:: superannotate.SAClient.upload_annotations +.. automethod:: superannotate.SAClient.delete_annotations +.. _ref_upload_images_from_folder_to_project: +.. automethod:: superannotate.SAClient.upload_images_from_folder_to_project +.. automethod:: superannotate.SAClient.upload_video_to_project +.. automethod:: superannotate.SAClient.upload_videos_from_folder_to_project +.. _ref_upload_annotations_from_folder_to_project: +.. automethod:: superannotate.SAClient.upload_annotations_from_folder_to_project +.. automethod:: superannotate.SAClient.add_contributors_to_project +.. automethod:: superannotate.SAClient.get_project_settings +.. automethod:: superannotate.SAClient.set_project_default_image_quality_in_editor +.. automethod:: superannotate.SAClient.get_project_workflow +.. automethod:: superannotate.SAClient.set_project_workflow + +---------- + +Exports +_______ + +.. automethod:: superannotate.SAClient.prepare_export +.. automethod:: superannotate.SAClient.get_annotations +.. automethod:: superannotate.SAClient.get_annotations_per_frame +.. _ref_download_export: +.. automethod:: superannotate.SAClient.download_export +.. automethod:: superannotate.SAClient.get_exports + +---------- + +Items +______ + +.. automethod:: superannotate.SAClient.query +.. automethod:: superannotate.SAClient.get_item_by_id +.. automethod:: superannotate.SAClient.search_items +.. automethod:: superannotate.SAClient.download_annotations +.. automethod:: superannotate.SAClient.attach_items +.. automethod:: superannotate.SAClient.copy_items +.. automethod:: superannotate.SAClient.move_items +.. automethod:: superannotate.SAClient.delete_items +.. automethod:: superannotate.SAClient.assign_items +.. automethod:: superannotate.SAClient.unassign_items +.. automethod:: superannotate.SAClient.get_item_metadata +.. automethod:: superannotate.SAClient.set_annotation_statuses +.. automethod:: superannotate.SAClient.set_approval_statuses +.. automethod:: superannotate.SAClient.set_approval + +---------- + +Custom Metadata +______ + +.. automethod:: superannotate.SAClient.create_custom_fields +.. automethod:: superannotate.SAClient.get_custom_fields +.. automethod:: superannotate.SAClient.delete_custom_fields +.. automethod:: superannotate.SAClient.upload_custom_values +.. automethod:: superannotate.SAClient.delete_custom_values + +---------- + +Subsets +______ + +.. automethod:: superannotate.SAClient.get_subsets +.. automethod:: superannotate.SAClient.add_items_to_subset + +---------- + +Images +______ + + +.. _ref_search_images: +.. automethod:: superannotate.SAClient.download_image +.. automethod:: superannotate.SAClient.download_image_annotations +.. automethod:: superannotate.SAClient.upload_image_annotations +.. automethod:: superannotate.SAClient.pin_image +.. automethod:: superannotate.SAClient.upload_priority_scores + +---------- + +Annotation Classes +__________________ + +.. automethod:: superannotate.SAClient.create_annotation_class +.. _ref_create_annotation_classes_from_classes_json: +.. automethod:: superannotate.SAClient.create_annotation_classes_from_classes_json +.. automethod:: superannotate.SAClient.search_annotation_classes +.. automethod:: superannotate.SAClient.download_annotation_classes_json +.. automethod:: superannotate.SAClient.delete_annotation_class + +---------- + +Team +_________________ + +.. automethod:: superannotate.SAClient.get_team_metadata +.. automethod:: superannotate.SAClient.get_integrations +.. automethod:: superannotate.SAClient.invite_contributors_to_team +.. automethod:: superannotate.SAClient.search_team_contributors + +---------- + +Neural Network +_______________ + +.. automethod:: superannotate.SAClient.download_model +.. automethod:: superannotate.SAClient.run_prediction +.. automethod:: superannotate.SAClient.search_models \ No newline at end of file diff --git a/docs/source/api_reference/api_metadata.rst b/docs/source/api_reference/api_metadata.rst new file mode 100644 index 000000000..792e76d93 --- /dev/null +++ b/docs/source/api_reference/api_metadata.rst @@ -0,0 +1,223 @@ +========== +Remote metadata reference +========== + +Projects metadata +_________________ + +Project metadata example: + +.. code-block:: python + + { + "name": "Example Project test", + "description": "test vector", + "creator_id": "admin@superannotate.com", + "updatedAt": "2020-08-31T05:43:43.118Z", + "createdAt": "2020-08-31T05:43:43.118Z" + "type": "Vector", + "attachment_name": None, + "attachment_path": None, + "entropy_status": 1, + "status": "NotStarted", + "...": "..." + } + + +---------- + +Setting metadata +_________________ + +Setting metadata example: + +.. code-block:: python + + { + "attribute": "FrameRate", + "value": 3 + } + + +---------- + +Export metadata +_______________ + +Export metadata example: + +.. code-block:: python + + { + "name": "Aug 17 2020 15:44 First Name.zip", + "user_id": "user@gmail.com", + "status": 2, + "createdAt": "2020-08-17T11:44:26.000Z", + "...": "..." + } + + +---------- + + +Integration metadata +______________________ + +Integration metadata example: + +.. code-block:: python + + { + "name": "My S3 Bucket", + "type": "aws", + "root": "test-openseadragon-1212" + } + + +---------- + + +Item metadata +_______________ + +Item metadata example: + +.. code-block:: python + + { + "name": "example.jpeg", + "path": "project/folder_1/meow.jpeg", + "url": "https://sa-public-files.s3.../text_file_example_1.jpeg", + "annotation_status": "NotStarted", + "annotator_name": None, + "qa_name": None, + "entropy_value": None, + "createdAt": "2022-02-15T20:46:44.000Z", + "updatedAt": "2022-02-15T20:46:44.000Z" + } + +---------- + + +Image metadata +_______________ + + +Image metadata example: + +.. code-block:: python + + { + "name": "000000000001.jpg", + "annotation_status": "Completed", + "prediction_status": "NotStarted", + "segmentation_status": "NotStarted", + "annotator_id": None, + "annotator_name": None, + "qa_id": None, + "qa_name": None, + "entropy_value": None, + "approval_status": None, + "createdAt": "2020-08-18T07:30:06.000Z", + "updatedAt": "2020-08-18T07:30:06.000Z" + "is_pinned": 0, + "...": "...", + } + + +---------- + +Priority score +_______________ + + +Priority score example: + +.. code-block:: python + + { + "name" : "image1.png", + "priority": 0.567 + } + + +---------- + +Attachment +_______________ + + +Attachment example: + +.. code-block:: python + + { + "url": "https://sa-public-files.s3.../text_file_example_1.jpeg", + "name": "example.jpeg" + } + + +---------- + +.. _ref_class: + +Annotation class metadata +_________________________ + + +Annotation class metadata example: + +.. code-block:: python + + { + "id": 4444, + "name": "Human", + "color": "#e4542b", + "attribute_groups": [ + { + "name": "tall", + "attributes": [ + { + "name": "yes" + }, + { + "name": "no" + } + ] + }, + { + "name": "age", + "attributes": [ + { + "name": "young" + }, + { + "name": "old" + } + ] + } + ], + + "...": "..." + } + + + +---------- + +Team contributor metadata +_________________________ + +Team contributor metadata example: + +.. code-block:: python + + { + "id": "admin@superannotate.com", + "first_name": "First Name", + "last_name": "Last Name", + "email": "admin@superannotate.com", + "user_role": 6 + "...": "...", + } + diff --git a/docs/source/api_reference/cli_client.rst b/docs/source/api_reference/cli_client.rst new file mode 100644 index 000000000..b668c2ae0 --- /dev/null +++ b/docs/source/api_reference/cli_client.rst @@ -0,0 +1,220 @@ +.. _ref_cli: +========== +CLI Reference +========== + +With SuperAnnotate CLI, basic tasks can be accomplished using shell commands: + +.. code-block:: bash + + superannotatecli <--arg1 val1> <--arg2 val2> [--optional_arg3 val3] [--optional_arg4] ... + +To use the CLI a command line initialization step should be performed after the +:ref:`installation `: + +.. code-block:: bash + + superannotatecli init + +---------- + + +Available commands +________________________ + + +.. _ref_cli_init: + +Initialization and configuration +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +To initialize CLI (and SDK) with team token: + +.. code-block:: bash + + superannotatecli init + +---------- + + +.. _ref_create_server: + +Creating a server +~~~~~~~~~~~~~~~~~~ + +This will create a directory by the given name in your current or provided directory: + +.. code-block:: bash + + superannotatecli create-server --name --path + +---------- + + +.. _ref_create_project: + +Creating a project +~~~~~~~~~~~~~~~~~~ + +To create a new project: + +.. code-block:: bash + + superannotatecli create-project --name --description --type + +---------- + +Creating a folder in a project +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +To create a new folder: + +.. code-block:: bash + + superannotatecli create-folder --project --name + +---------- + +.. _ref_upload_images: + +Uploading images +~~~~~~~~~~~~~~~~ + +To upload images from folder to project use: + +.. code-block:: bash + + superannotatecli upload-images --project --folder [--recursive] [--extensions ,,...] + +If optional argument *recursive* is given then subfolders of :file:`` are also recursively +scanned for available images. + +Optional argument *extensions* accepts comma separated list of image extensions +to look for. If the argument is not given then value *jpg,jpeg,png,tif,tiff,webp,bmp* is assumed. + +---------- + +.. _ref_attach_image_urls: + +Attaching image URLs +~~~~~~~~~~~~~~~~~~~~ + +To attach image URLs to project use: + +.. code-block:: bash + + superannotatecli attach-image-urls --project --attachments [--annotation_status ] + +---------- + +.. _ref_upload_videos: + +Uploading videos +~~~~~~~~~~~~~~~~ + +To upload videos from folder to project use: + +.. code-block:: bash + + superannotatecli upload-videos --project --folder + [--recursive] [--extensions mp4,avi,mov,webm,flv,mpg,ogg] + [--target-fps ] [--start-time ] + [--end-time ] + +If optional argument *recursive* is given then subfolders of :file:`` are also recursively +scanned for available videos. + +Optional argument *extensions* accepts comma separated list of image extensions +to look for. If the argument is not given then value *mp4,avi,mov,webm,flv,mpg,ogg* is assumed. + +*target-fps* specifies how many frames per second need to extract from the videos (approximate). +If not specified all frames will be uploaded. + +*start-time* specifies time (in seconds) from which to start extracting frames, +default is 0.0. + +*end-time* specifies time (in seconds) up to which to extract frames. +If it is not specified, then up to end is assumed. + +---------- + +.. _ref_upload_preannotations: + +Uploading preannotations +~~~~~~~~~~~~~~~~~~~~~~~~ + +To upload preannotations from folder to project use: + +.. code-block:: bash + + superannotatecli upload-preannotations --project --folder + [--format "COCO" or "SuperAnnotate"] + [--dataset-name ""] + [--task "] + + +Optional argument *format* accepts input annotation format. It can have COCO or SuperAnnotate values. +If the argument is not given then SuperAnnotate (the native annotation format) is assumed. + +Only when COCO format is specified *dataset-name* and *task* arguments are required. + +*dataset-name* specifies JSON filename (without extension) in . + +*task* specifies the COCO task for conversion. Please see +:ref:`import_annotation_format ` for more details. + + +---------- + +.. _ref_upload_annotations: + +Uploading annotations +~~~~~~~~~~~~~~~~~~~~~~~~ + +To upload annotations from folder to project use: + +.. code-block:: bash + + superannotatecli upload-annotations --project --folder + [--format "COCO" or "SuperAnnotate"] + [--dataset-name ""] + [--task "] + +Optional argument *format* accepts input annotation format. It can have COCO or SuperAnnotate values. +If the argument is not given then SuperAnnotate (the native annotation format) is assumed. + +Only when COCO format is specified *dataset-name* and *task* arguments are required. + +*dataset-name* specifies JSON filename (without extension) in . + +*task* specifies the COCO task for conversion. Please see +:ref:`import_annotation_format ` for more details. + +---------- + +.. _ref_export_project: + +Exporting projects +~~~~~~~~~~~~~~~~~~~~~~~~ + +To export project + +.. code-block:: bash + + superannotatecli export-project --project --folder + [--include-fuse] + [--disable-extract-zip-contents] + [--annotation-statuses ] + +---------- + +.. _ref_cli_version: + +SDK version information +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +To show the version of the current SDK installation: + +.. code-block:: bash + + superannotatecli version diff --git a/docs/source/api_reference/helpers.rst b/docs/source/api_reference/helpers.rst new file mode 100644 index 000000000..7f4e59e65 --- /dev/null +++ b/docs/source/api_reference/helpers.rst @@ -0,0 +1,42 @@ +========== +Annotation JSON helper functions +========== + + +.. _ref_converter: + +Converting annotation format to and from src.superannotate format +_________________________________________________________________ + + +.. _ref_import_annotation_format: +.. autofunction:: superannotate.import_annotation +.. autofunction:: superannotate.export_annotation +.. autofunction:: superannotate.convert_project_type +.. autofunction:: superannotate.convert_json_version + + + +---------- + +Working with annotations +________________________ + +.. _ref_aggregate_annotations_as_df: +.. automethod:: superannotate.SAClient.validate_annotations +.. automethod:: superannotate.SAClient.aggregate_annotations_as_df + +---------- + +Aggregating class distribution from annotations +_____________________________________________________________ + +.. autofunction:: superannotate.class_distribution + +---------- + +Utility functions +-------------------------------- + +.. autofunction:: superannotate.SAClient.consensus +.. autofunction:: superannotate.SAClient.benchmark diff --git a/docs/source/api_reference/index.rst b/docs/source/api_reference/index.rst new file mode 100644 index 000000000..5bd4dc170 --- /dev/null +++ b/docs/source/api_reference/index.rst @@ -0,0 +1,26 @@ +================================================== +API Reference +================================================== +BLA BLA + + +Contents +======== + +.. toctree:: + :maxdepth: 1 + + api_client + cli_client + api_metadata + helpers + +--- + +.. rubric:: Notes + +.. [#package-overload] + A :term:`Distribution Package` is also referred in the Python community simply as "package" + Unfortunately, this jargon might be a bit confusing for new users because the term package + can also to refer any :term:`directory ` (or sub directory) used to organize + :term:`modules ` and auxiliary files. diff --git a/docs/source/conf.py b/docs/source/conf.py index 416bd9d46..1fe82bff2 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -34,6 +34,62 @@ # ones. extensions = ['sphinx.ext.autodoc'] + +extensions += ['sphinx_inline_tabs'] +# extensions += ['pip install jaraco.tidelift'] +extensions += ['notfound.extension'] + +html_theme = 'furo' + +html_theme_options = { + "sidebar_hide_name": True, + "light_css_variables": { + "color-brand-primary": "#336790", # "blue" + "color-brand-content": "#336790", + }, + "dark_css_variables": { + "color-brand-primary": "#E5B62F", # "yellow" + "color-brand-content": "#E5B62F", + }, +} +nitpick_ignore = [ + ('c:func', 'SHGetSpecialFolderPath'), # ref to MS docs + ('envvar', 'DISTUTILS_DEBUG'), # undocumented + ('envvar', 'HOME'), # undocumented + ('envvar', 'PLAT'), # undocumented + ('envvar', 'DIST_EXTRA_CONFIG'), # undocumented + ('py:attr', 'CCompiler.language_map'), # undocumented + ('py:attr', 'CCompiler.language_order'), # undocumented + ('py:class', 'distutils.dist.Distribution'), # undocumented + ('py:class', 'distutils.extension.Extension'), # undocumented + ('py:class', 'BorlandCCompiler'), # undocumented + ('py:class', 'CCompiler'), # undocumented + ('py:class', 'CygwinCCompiler'), # undocumented + ('py:class', 'distutils.dist.DistributionMetadata'), # undocumented + ('py:class', 'FileList'), # undocumented + ('py:class', 'IShellLink'), # ref to MS docs + ('py:class', 'MSVCCompiler'), # undocumented + ('py:class', 'OptionDummy'), # undocumented + ('py:class', 'UnixCCompiler'), # undocumented + ('py:exc', 'CompileError'), # undocumented + ('py:exc', 'DistutilsExecError'), # undocumented + ('py:exc', 'DistutilsFileError'), # undocumented + ('py:exc', 'LibError'), # undocumented + ('py:exc', 'LinkError'), # undocumented + ('py:exc', 'PreprocessError'), # undocumented + ('py:exc', 'setuptools.errors.PlatformError'), # sphinx cannot find it + ('py:func', 'distutils.CCompiler.new_compiler'), # undocumented + # undocumented: + ('py:func', 'distutils.dist.DistributionMetadata.read_pkg_file'), + ('py:func', 'distutils.file_util._copy_file_contents'), # undocumented + ('py:func', 'distutils.log.debug'), # undocumented + ('py:func', 'distutils.spawn.find_executable'), # undocumented + ('py:func', 'distutils.spawn.spawn'), # undocumented + # TODO: check https://docutils.rtfd.io in the future + ('py:mod', 'docutils'), # there's no Sphinx site documenting this +] + + # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] @@ -47,7 +103,6 @@ # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. # -html_theme = 'sphinx_rtd_theme' # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, diff --git a/docs/source/index.rst b/docs/source/index.rst index 10b21ca92..fd68a1fb6 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -15,8 +15,8 @@ :name: mastertoc :maxdepth: 1 - tutorial.sdk.rst - superannotate.sdk.rst + User guide + API Reference server.rst cli.rst LICENSE.rst diff --git a/docs/source/superannotate.sdk.rst b/docs/source/superannotate.sdk.rst index 8713337cd..197df936c 100644 --- a/docs/source/superannotate.sdk.rst +++ b/docs/source/superannotate.sdk.rst @@ -98,7 +98,7 @@ ______ ---------- Subsets -______ +__________________ .. automethod:: superannotate.SAClient.get_subsets .. automethod:: superannotate.SAClient.add_items_to_subset diff --git a/docs/source/tutorial.sdk.rst b/docs/source/tutorial.sdk.rst index ee66d8880..b6b124c48 100644 --- a/docs/source/tutorial.sdk.rst +++ b/docs/source/tutorial.sdk.rst @@ -11,96 +11,6 @@ Installation ____________ -SDK is available on PyPI: - -.. code-block:: bash - - pip install superannotate - -The package officially supports Python 3.6+ and was tested under Linux and -Windows (`Anaconda `_) platforms. - -For certain video related functions to work, ffmpeg package needs to be installed. -It can be installed on Ubuntu with: - -.. code-block:: bash - - sudo apt-get install ffmpeg - -For Windows and Mac OS based installations to use :py:obj:`benchmark` and :py:obj:`consensus` -functions you might also need to install beforehand :py:obj:`shapely` package, -which we found to work properly only under Anaconda distribution, with: - -.. code-block:: bash - - conda install shapely - - ----------- - -Config file -____________________ - -To use the SDK, a config file with team specific authentication token needs to be -created. The token is available to team admins on -team setting page at https://app.superannotate.com/team. - -Default location config file -~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -To generate a default location (:file:`~/.superannotate/config.json`) config file, -:ref:`CLI init ` can be used: - -.. code-block:: bash - - superannotatecli init - -.. _ref_custom_config_file: - -Custom config file -~~~~~~~~~~~~~~~~~~~~~~ - -To create a custom config file a new JSON file with key "token" can be created: - -.. code-block:: json - - { - "token" : "" - } - ----------- - -Initialization and authorization -________________________________ - -Include the package in your Python code: - -.. code-block:: python - - from superannotate import SAClient - -SDK is ready to be used if default location config file was created using -the :ref:`CLI init `. Otherwise to authenticate SDK with the :ref:`custom config file `: - -.. code-block:: python - - sa = SAClient(config_path="") - -Creating a project -____________________________ - -To create a new "Vector" project with name "Example Project 1" and description -"test": - -.. code-block:: python - - project = "Example Project 1" - - sa.create_project(project, "test", "Vector") - -.. warning:: - - In general, SDK functions are not thread-safe. Creating a folder in a project ______________________________ @@ -115,31 +25,6 @@ After that point almost all SDK functions that use project name as argument can point to that folder with slash after the project name, e.g., "Example Project 1/folder1", in this case. -Uploading images to project -____________________________ - - -To upload all images with extensions "jpg" or "png" from the -:file:`""` to the project "Example Project 1": - -.. code-block:: python - - sa.upload_images_from_folder_to_project(project, "") - -See the full argument options for -:py:func:`upload_images_from_folder_to_project` :ref:`here `. - -For full list of available functions on projects, see :ref:`ref_projects`. - -.. note:: - - Python SDK functions that accept project argument will accept both project - name or :ref:`project metadata ` (returned either by - :ref:`get_project_metadata ` or - :ref:`search_projects ` with argument :py:obj:`return_metadata=True`). - If project name is used it should be unique in team's project list. Using project metadata will give - performance improvement. - .. note:: CLI command :ref:`upload-images ` can also be used for @@ -147,8 +32,8 @@ For full list of available functions on projects, see :ref:`ref_projects`. .. note:: - To upload images to the "folder1" instead of the root of the project: - + To upload images to the "folder1" instead of the root of the project: + .. code-block:: python sa.upload_images_from_folder_to_project(project + "/folder1", "") @@ -163,10 +48,10 @@ An annotation class for a project can be created with SDK's: sa.create_annotation_class(project, "Large car", color="#FFFFAA") -To create annotation classes in bulk with SuperAnnotate export format +To create annotation classes in bulk with SuperAnnotate export format :file:`classes.json` (documentation at: https://app.superannotate.com/documentation Management Tools --> Project Workflow part): +-> Project Workflow part): .. code-block:: python @@ -174,7 +59,7 @@ https://app.superannotate.com/documentation Management Tools All of the annotation classes of a project are downloaded (as :file:`classes/classes.json`) with -:ref:`download_export ` along with annotations, but they +:ref:`download_export ` along with annotations, but they can also be downloaded separately with: .. code-block:: python @@ -192,7 +77,7 @@ The SuperAnnotate format annotation JSONs have the general form: .. code-block:: json - [ + [ { "className": "Human", "points" : "...", @@ -218,12 +103,12 @@ To upload annotations to platform: sa.upload_annotations_from_folder_to_project(project, "") -This will try uploading to the project all the JSON files in the folder that have specific +This will try uploading to the project all the JSON files in the folder that have specific file naming convention. For vector projects JSONs should be named :file:`"___objects.json"`. For pixel projects -JSON files should be named :file:`"___pixel.json"` and also for -each JSON a mask image file should be present with the name -:file:`"___save.png"`. Image with :file:`` should +JSON files should be named :file:`"___pixel.json"` and also for +each JSON a mask image file should be present with the name +:file:`"___save.png"`. Image with :file:`` should already be present in the project for the upload to work. diff --git a/docs/source/userguide/datafiles.rst b/docs/source/userguide/datafiles.rst new file mode 100644 index 000000000..44ff74252 --- /dev/null +++ b/docs/source/userguide/datafiles.rst @@ -0,0 +1,555 @@ +==================== +Data Files Support +==================== + +Old packaging installation methods in the Python ecosystem +have traditionally allowed installation of "data files", which +are placed in a platform-specific location. However, the most common use case +for data files distributed with a package is for use *by* the package, usually +by including the data files **inside the package directory**. + +Setuptools focuses on this most common type of data files and offers three ways +of specifying which files should be included in your packages, as described in +the following sections. + +include_package_data +==================== + +First, you can simply use the ``include_package_data`` keyword. +For example, if the package tree looks like this:: + + project_root_directory + ├── setup.py # and/or setup.cfg, pyproject.toml + └── src + └── mypkg + ├── __init__.py + ├── data1.rst + ├── data2.rst + ├── data1.txt + └── data2.txt + +and you supply this configuration: + +.. tab:: setup.cfg + + .. code-block:: ini + + [options] + # ... + packages = find: + package_dir = + = src + include_package_data = True + + [options.packages.find] + where = src + +.. tab:: setup.py + + .. code-block:: python + + from setuptools import setup, find_packages + setup( + # ..., + packages=find_packages(where="src"), + package_dir={"": "src"}, + include_package_data=True + ) + +.. tab:: pyproject.toml (**BETA**) [#beta]_ + + .. code-block:: toml + + [tool.setuptools] + # ... + # By default, include-package-data is true in pyproject.toml, so you do + # NOT have to specify this line. + include-package-data = true + + [tool.setuptools.packages.find] + where = ["src"] + +then all the ``.txt`` and ``.rst`` files will be automatically installed with +your package, provided: + +1. These files are included via the |MANIFEST.in|_ file, like so:: + + include src/mypkg/*.txt + include src/mypkg/*.rst + +2. OR, they are being tracked by a revision control system such as Git, Mercurial + or SVN, and you have configured an appropriate plugin such as + :pypi:`setuptools-scm` or :pypi:`setuptools-svn`. + (See the section below on :ref:`Adding Support for Revision + Control Systems` for information on how to write such plugins.) + +package_data +============ + +By default, ``include_package_data`` considers **all** non ``.py`` files found inside +the package directory (``src/mypkg`` in this case) as data files, and includes those that +satisfy (at least) one of the above two conditions into the source distribution, and +consequently in the installation of your package. +If you want finer-grained control over what files are included, then you can also use +the ``package_data`` keyword. +For example, if the package tree looks like this:: + + project_root_directory + ├── setup.py # and/or setup.cfg, pyproject.toml + └── src + └── mypkg + ├── __init__.py + ├── data1.rst + ├── data2.rst + ├── data1.txt + └── data2.txt + +then you can use the following configuration to capture the ``.txt`` and ``.rst`` files as +data files: + +.. tab:: setup.cfg + + .. code-block:: ini + + [options] + # ... + packages = find: + package_dir = + = src + + [options.packages.find] + where = src + + [options.package_data] + mypkg = + *.txt + *.rst + +.. tab:: setup.py + + .. code-block:: python + + from setuptools import setup, find_packages + setup( + # ..., + packages=find_packages(where="src"), + package_dir={"": "src"}, + package_data={"mypkg": ["*.txt", "*.rst"]} + ) + +.. tab:: pyproject.toml (**BETA**) [#beta]_ + + .. code-block:: toml + + [tool.setuptools.packages.find] + where = ["src"] + + [tool.setuptools.package-data] + mypkg = ["*.txt", "*.rst"] + +The ``package_data`` argument is a dictionary that maps from package names to +lists of glob patterns. Note that the data files specified using the ``package_data`` +option neither require to be included within a |MANIFEST.in|_ file, nor +require to be added by a revision control system plugin. + +.. note:: + If your glob patterns use paths, you *must* use a forward slash (``/``) as + the path separator, even if you are on Windows. Setuptools automatically + converts slashes to appropriate platform-specific separators at build time. + +.. note:: + Glob patterns do not automatically match dotfiles (directory or file names + starting with a dot (``.``)). To include such files, you must explicitly start + the pattern with a dot, e.g. ``.*`` to match ``.gitignore``. + +If you have multiple top-level packages and a common pattern of data files for all these +packages, for example:: + + project_root_directory + ├── setup.py # and/or setup.cfg, pyproject.toml + └── src + ├── mypkg1 + │   ├── data1.rst + │   ├── data1.txt + │   └── __init__.py + └── mypkg2 + ├── data2.txt + └── __init__.py + +Here, both packages ``mypkg1`` and ``mypkg2`` share a common pattern of having ``.txt`` +data files. However, only ``mypkg1`` has ``.rst`` data files. In such a case, if you want to +use the ``package_data`` option, the following configuration will work: + +.. tab:: setup.cfg + + .. code-block:: ini + + [options] + packages = find: + package_dir = + = src + + [options.packages.find] + where = src + + [options.package_data] + * = + *.txt + mypkg1 = + data1.rst + +.. tab:: setup.py + + .. code-block:: python + + from setuptools import setup, find_packages + setup( + # ..., + packages=find_packages(where="src"), + package_dir={"": "src"}, + package_data={"": ["*.txt"], "mypkg1": ["data1.rst"]}, + ) + +.. tab:: pyproject.toml (**BETA**) [#beta]_ + + .. code-block:: toml + + [tool.setuptools.packages.find] + where = ["src"] + + [tool.setuptools.package-data] + "*" = ["*.txt"] + mypkg1 = ["data1.rst"] + +Notice that if you list patterns in ``package_data`` under the empty string ``""`` in +``setup.py``, and the asterisk ``*`` in ``setup.cfg`` and ``pyproject.toml``, these +patterns are used to find files in every package. For example, we use ``""`` or ``*`` +to indicate that the ``.txt`` files from all packages should be captured as data files. +Also note how we can continue to specify patterns for individual packages, i.e. +we specify that ``data1.rst`` from ``mypkg1`` alone should be captured as well. + +.. note:: + When building an ``sdist``, the datafiles are also drawn from the + ``package_name.egg-info/SOURCES.txt`` file, so make sure that this is removed if + the ``setup.py`` ``package_data`` list is updated before calling ``setup.py``. + +.. note:: + If using the ``include_package_data`` argument, files specified by + ``package_data`` will *not* be automatically added to the manifest unless + they are listed in the |MANIFEST.in|_ file or by a plugin like + :pypi:`setuptools-scm` or :pypi:`setuptools-svn`. + +.. https://docs.python.org/3/distutils/setupscript.html#installing-package-data + +exclude_package_data +==================== + +Sometimes, the ``include_package_data`` or ``package_data`` options alone +aren't sufficient to precisely define what files you want included. For example, +consider a scenario where you have ``include_package_data=True``, and you are using +a revision control system with an appropriate plugin. +Sometimes developers add directory-specific marker files (such as ``.gitignore``, +``.gitkeep``, ``.gitattributes``, or ``.hgignore``), these files are probably being +tracked by the revision control system, and therefore by default they will be +included when the package is installed. + +Supposing you want to prevent these files from being included in the +installation (they are not relevant to Python or the package), then you could +use the ``exclude_package_data`` option: + +.. tab:: setup.cfg + + .. code-block:: ini + + [options] + # ... + packages = find: + package_dir = + = src + include_package_data = True + + [options.packages.find] + where = src + + [options.exclude_package_data] + mypkg = + .gitattributes + +.. tab:: setup.py + + .. code-block:: python + + from setuptools import setup, find_packages + setup( + # ..., + packages=find_packages(where="src"), + package_dir={"": "src"}, + include_package_data=True, + exclude_package_data={"mypkg": [".gitattributes"]}, + ) + +.. tab:: pyproject.toml (**BETA**) [#beta]_ + + .. code-block:: toml + + [tool.setuptools.packages.find] + where = ["src"] + + [tool.setuptools.exclude-package-data] + mypkg = [".gitattributes"] + +The ``exclude_package_data`` option is a dictionary mapping package names to +lists of wildcard patterns, just like the ``package_data`` option. And, just +as with that option, you can use the empty string key ``""`` in ``setup.py`` and the +asterisk ``*`` in ``setup.cfg`` and ``pyproject.toml`` to match all top-level packages. + +Any files that match these patterns will be *excluded* from installation, +even if they were listed in ``package_data`` or were included as a result of using +``include_package_data``. + +Subdirectory for Data Files +=========================== + +A common pattern is where some (or all) of the data files are placed under +a separate subdirectory. For example:: + + project_root_directory + ├── setup.py # and/or setup.cfg, pyproject.toml + └── src + └── mypkg + ├── data + │   ├── data1.rst + │   └── data2.rst + ├── __init__.py + ├── data1.txt + └── data2.txt + +Here, the ``.rst`` files are placed under a ``data`` subdirectory inside ``mypkg``, +while the ``.txt`` files are directly under ``mypkg``. + +In this case, the recommended approach is to treat ``data`` as a namespace package +(refer :pep:`420`). With ``package_data``, +the configuration might look like this: + +.. tab:: setup.cfg + + .. code-block:: ini + + [options] + # ... + packages = find_namespace: + package_dir = + = src + + [options.packages.find] + where = src + + [options.package_data] + mypkg = + *.txt + mypkg.data = + *.rst + +.. tab:: setup.py + + .. code-block:: python + + from setuptools import setup, find_namespace_packages + setup( + # ..., + packages=find_namespace_packages(where="src"), + package_dir={"": "src"}, + package_data={ + "mypkg": ["*.txt"], + "mypkg.data": ["*.rst"], + } + ) + +.. tab:: pyproject.toml (**BETA**) [#beta]_ + + .. code-block:: toml + + [tool.setuptools.packages.find] + # scanning for namespace packages is true by default in pyproject.toml, so + # you do NOT need to include the following line. + namespaces = true + where = ["src"] + + [tool.setuptools.package-data] + mypkg = ["*.txt"] + "mypkg.data" = ["*.rst"] + +In other words, we allow Setuptools to scan for namespace packages in the ``src`` directory, +which enables the ``data`` directory to be identified, and then, we separately specify data +files for the root package ``mypkg``, and the namespace package ``data`` under the package +``mypkg``. + +With ``include_package_data`` the configuration is simpler: you simply need to enable +scanning of namespace packages in the ``src`` directory and the rest is handled by Setuptools. + +.. tab:: setup.cfg + + .. code-block:: ini + + [options] + packages = find_namespace: + package_dir = + = src + include_package_data = True + + [options.packages.find] + where = src + +.. tab:: setup.py + + .. code-block:: python + + from setuptools import setup, find_namespace_packages + setup( + # ... , + packages=find_namespace_packages(where="src"), + package_dir={"": "src"}, + include_package_data=True, + ) + +.. tab:: pyproject.toml (**BETA**) [#beta]_ + + .. code-block:: toml + + [tool.setuptools] + # ... + # By default, include-package-data is true in pyproject.toml, so you do + # NOT have to specify this line. + include-package-data = true + + [tool.setuptools.packages.find] + # scanning for namespace packages is true by default in pyproject.toml, so + # you need NOT include the following line. + namespaces = true + where = ["src"] + +Summary +======= + +In summary, the three options allow you to: + +``include_package_data`` + Accept all data files and directories matched by |MANIFEST.in|_ or added by + a :ref:`plugin `. + +``package_data`` + Specify additional patterns to match files that may or may + not be matched by |MANIFEST.in|_ or added by + a :ref:`plugin `. + +``exclude_package_data`` + Specify patterns for data files and directories that should *not* be + included when a package is installed, even if they would otherwise have + been included due to the use of the preceding options. + +.. note:: + Due to the way the build process works, a data file that you + include in your project and then stop including may be "orphaned" in your + project's build directories, requiring you to run ``setup.py clean --all`` to + fully remove them. This may also be important for your users and contributors + if they track intermediate revisions of your project using Subversion; be sure + to let them know when you make changes that remove files from inclusion so they + can run ``setup.py clean --all``. + + +.. _Accessing Data Files at Runtime: + +Accessing Data Files at Runtime +=============================== + +Typically, existing programs manipulate a package's ``__file__`` attribute in +order to find the location of data files. For example, if you have a structure +like this:: + + project_root_directory + ├── setup.py # and/or setup.cfg, pyproject.toml + └── src + └── mypkg + ├── data + │   └── data1.txt + ├── __init__.py + └── foo.py + +Then, in ``mypkg/foo.py``, you may try something like this in order to access +``mypkg/data/data1.txt``: + +.. code-block:: python + + import os + data_path = os.path.join(os.path.dirname(__file__), 'data', 'data1.txt') + with open(data_path, 'r') as data_file: + ... + +However, this manipulation isn't compatible with :pep:`302`-based import hooks, +including importing from zip files and Python Eggs. It is strongly recommended that, +if you are using data files, you should use :mod:`importlib.resources` to access them. +In this case, you would do something like this: + +.. code-block:: python + + from importlib.resources import files + data_text = files('mypkg.data').joinpath('data1.txt').read_text() + +:mod:`importlib.resources` was added to Python 3.7. However, the API illustrated in +this code (using ``files()``) was added only in Python 3.9, [#files_api]_ and support +for accessing data files via namespace packages was added only in Python 3.10 [#namespace_support]_ +(the ``data`` subdirectory is a namespace package under the root package ``mypkg``). +Therefore, you may find this code to work only in Python 3.10 (and above). For other +versions of Python, you are recommended to use the :pypi:`importlib-resources` backport +which provides the latest version of this library. In this case, the only change that +has to be made to the above code is to replace ``importlib.resources`` with ``importlib_resources``, i.e. + +.. code-block:: python + + from importlib_resources import files + ... + +See :doc:`importlib-resources:using` for detailed instructions. + +.. tip:: Files inside the package directory should be *read-only* to avoid a + series of common problems (e.g. when multiple users share a common Python + installation, when the package is loaded from a zip file, or when multiple + instances of a Python application run in parallel). + + If your Python package needs to write to a file for shared data or configuration, + you can use standard platform/OS-specific system directories, such as + ``~/.local/config/$appname`` or ``/usr/share/$appname/$version`` (Linux specific) [#system-dirs]_. + A common approach is to add a read-only template file to the package + directory that is then copied to the correct system directory if no + pre-existing file is found. + + +Non-Package Data Files +====================== + +Historically, ``setuptools`` by way of ``easy_install`` would encapsulate data +files from the distribution into the egg (see `the old docs +`_). As eggs are deprecated and pip-based installs +fall back to the platform-specific location for installing data files, there is +no supported facility to reliably retrieve these resources. + +Instead, the PyPA recommends that any data files you wish to be accessible at +run time be included **inside the package**. + + +---- + +.. [#beta] + Support for adding build configuration options via the ``[tool.setuptools]`` + table in the ``pyproject.toml`` file. See :doc:`/userguide/pyproject_config`. + +.. [#system-dirs] These locations can be discovered with the help of + third-party libraries such as :pypi:`platformdirs`. + +.. [#files_api] Reference: https://importlib-resources.readthedocs.io/en/latest/using.html#migrating-from-legacy + +.. [#namespace_support] Reference: https://github.com/python/importlib_resources/pull/196#issuecomment-734520374 + + +.. |MANIFEST.in| replace:: ``MANIFEST.in`` +.. _MANIFEST.in: https://packaging.python.org/en/latest/guides/using-manifest-in/ diff --git a/docs/source/userguide/declarative_config.rst b/docs/source/userguide/declarative_config.rst new file mode 100644 index 000000000..fa104b10e --- /dev/null +++ b/docs/source/userguide/declarative_config.rst @@ -0,0 +1,326 @@ +.. _declarative config: + +------------------------------------------------ +Configuring setuptools using ``setup.cfg`` files +------------------------------------------------ + +.. note:: New in 30.3.0 (8 Dec 2016). + +.. important:: + If compatibility with legacy builds (i.e. those not using the :pep:`517` + build API) is desired, a ``setup.py`` file containing a ``setup()`` function + call is still required even if your configuration resides in ``setup.cfg``. + +``Setuptools`` allows using configuration files (usually :file:`setup.cfg`) +to define a package’s metadata and other options that are normally supplied +to the ``setup()`` function (declarative config). + +This approach not only allows automation scenarios but also reduces +boilerplate code in some cases. + +.. _example-setup-config: + +.. code-block:: ini + + [metadata] + name = my_package + version = attr: my_package.VERSION + author = Josiah Carberry + author_email = josiah_carberry@brown.edu + description = My package description + long_description = file: README.rst, CHANGELOG.rst, LICENSE.rst + keywords = one, two + license = BSD-3-Clause + classifiers = + Framework :: Django + Programming Language :: Python :: 3 + + [options] + zip_safe = False + include_package_data = True + packages = find: + python_requires = >=3.7 + install_requires = + requests + importlib-metadata; python_version<"3.8" + + [options.package_data] + * = *.txt, *.rst + hello = *.msg + + [options.entry_points] + console_scripts = + executable-name = my_package.module:function + + [options.extras_require] + pdf = ReportLab>=1.2; RXP + rest = docutils>=0.3; pack ==1.1, ==1.3 + + [options.packages.find] + exclude = + examples* + tools* + docs* + my_package.tests* + +Metadata and options are set in the config sections of the same name. + +* Keys are the same as the :doc:`keyword arguments ` one + provides to the ``setup()`` function. + +* Complex values can be written comma-separated or placed one per line + in *dangling* config values. The following are equivalent: + + .. code-block:: ini + + [metadata] + keywords = one, two + + [metadata] + keywords = + one + two + +* In some cases, complex values can be provided in dedicated subsections for + clarity. + +* Some keys allow ``file:``, ``attr:``, ``find:``, and ``find_namespace:`` directives in + order to cover common usecases. + +* Unknown keys are ignored. + + +Using a ``src/`` layout +======================= + +One commonly used configuration has all the Python source code in a +subdirectory (often called the ``src/`` layout), like this:: + + ├── src + │   └── mypackage + │   ├── __init__.py + │   └── mod1.py + ├── setup.py + └── setup.cfg + +You can set up your ``setup.cfg`` to automatically find all your packages in +the subdirectory, using :ref:`package_dir `, like this: + +.. code-block:: ini + + # This example contains just the necessary options for a src-layout, set up + # the rest of the file as described above. + + [options] + package_dir= + =src + packages=find: + + [options.packages.find] + where=src + +In this example, the value for the :ref:`package_dir ` +configuration (i.e. ``=src``) is parsed as ``{"": "src"}``. +The ``""`` key has a special meaning in this context, and indicates that all the +packages are contained inside the given directory. +Also note that the value for ``[options.packages.find] where`` matches the +value associated with ``""`` in the ``package_dir`` dictionary. + +.. + TODO: Add the following tip once the auto-discovery is no longer experimental: + + Starting in version 61, ``setuptools`` can automatically infer the + configurations for both ``packages`` and ``package_dir`` for projects using + a ``src/`` layout (as long as no value is specified for ``py_modules``). + Please see :doc:`package discovery ` for more + details. + +Specifying values +================= + +Some values are treated as simple strings, some allow more logic. + +Type names used below: + +* ``str`` - simple string +* ``list-comma`` - dangling list or string of comma-separated values +* ``list-semi`` - dangling list or string of semicolon-separated values +* ``bool`` - ``True`` is 1, yes, true +* ``dict`` - list-comma where each entry corresponds to a key/value pair, + with keys separated from values by ``=``. + If an entry starts with ``=``, the key is assumed to be an empty string + (e.g. ``=src`` is parsed as ``{"": "src"}``). +* ``section`` - values are read from a dedicated (sub)section + + +Special directives: + +* ``attr:`` - Value is read from a module attribute. ``attr:`` supports + callables and iterables; unsupported types are cast using ``str()``. + + In order to support the common case of a literal value assigned to a variable + in a module containing (directly or indirectly) third-party imports, + ``attr:`` first tries to read the value from the module by examining the + module's AST. If that fails, ``attr:`` falls back to importing the module. + +* ``file:`` - Value is read from a list of files and then concatenated + + .. important:: + The ``file:`` directive is sandboxed and won't reach anything outside the + project directory (i.e. the directory containing ``setup.cfg``/``pyproject.toml``). + + .. note:: + If you are using an old version of ``setuptools``, you might need to ensure + that all files referenced by the ``file:`` directive are included in the ``sdist`` + (you can do that via ``MANIFEST.in`` or using plugins such as ``setuptools-scm``, + please have a look on :doc:`/userguide/miscellaneous` for more information). + + .. versionchanged:: 66.1.0 + Newer versions of ``setuptools`` will automatically add these files to the ``sdist``. + + +Metadata +-------- + +.. attention:: + The aliases given below are supported for compatibility reasons, + but their use is not advised. + +============================== ================= ================= =============== ========== +Key Aliases Type Minimum Version Notes +============================== ================= ================= =============== ========== +name str +version attr:, file:, str 39.2.0 [#meta-1]_ +url home-page str +download_url download-url str +project_urls dict 38.3.0 +author str +author_email author-email str +maintainer str +maintainer_email maintainer-email str +classifiers classifier file:, list-comma +license str +license_files license_file list-comma 42.0.0 +description summary file:, str +long_description long-description file:, str +long_description_content_type str 38.6.0 +keywords list-comma +platforms platform list-comma +provides list-comma +requires list-comma +obsoletes list-comma +============================== ================= ================= =============== ========== + +**Notes**: + +.. [#meta-1] The ``version`` file attribute has only been supported since 39.2.0. + + A version loaded using the ``file:`` directive must comply with PEP 440. + It is easy to accidentally put something other than a valid version + string in such a file, so validation is stricter in this case. + + +Options +------- + +======================= =================================== =============== ==================== +Key Type Minimum Version Notes +======================= =================================== =============== ==================== +zip_safe bool +setup_requires list-semi 36.7.0 +install_requires file:, list-semi **BETA** [#opt-2]_, [#opt-6]_ +extras_require file:, section **BETA** [#opt-2]_, [#opt-6]_ +python_requires str 34.4.0 +entry_points file:, section 51.0.0 +scripts list-comma +eager_resources list-comma +dependency_links list-comma +tests_require list-semi +include_package_data bool +packages find:, find_namespace:, list-comma [#opt-3]_ +package_dir dict +package_data section [#opt-1]_ +exclude_package_data section +namespace_packages list-comma [#opt-5]_ +py_modules list-comma 34.4.0 +data_files section 40.6.0 [#opt-4]_ +======================= =================================== =============== ==================== + +**Notes**: + +.. [#opt-1] In the ``package_data`` section, a key named with a single asterisk + (``*``) refers to all packages, in lieu of the empty string used in ``setup.py``. + +.. [#opt-2] In ``install_requires`` and ``extras_require``, values are parsed as ``list-semi``. + This implies that in order to include markers, each requirement **must** be *dangling* + in a new line: + + .. code-block:: ini + + [options] + install_requires = + importlib-metadata; python_version<"3.8" + + [options.extras_require] + all = + importlib-metadata; python_version < "3.8" + +.. [#opt-3] The ``find:`` and ``find_namespace:`` directive can be further configured + in a dedicated subsection ``options.packages.find``. This subsection accepts the + same keys as the ``setuptools.find_packages`` and the + ``setuptools.find_namespace_packages`` function: + ``where``, ``include``, and ``exclude``. + + The ``find_namespace:`` directive is supported since Python >=3.3. + +.. [#opt-4] ``data_files`` is deprecated and should be avoided. + Please check :doc:`/userguide/datafiles` for more information. + +.. [#opt-5] ``namespace_packages`` is deprecated in favour of native/implicit + namespaces (:pep:`420`). Check :doc:`the Python Packaging User Guide + ` for more information. + +.. [#opt-6] ``file:`` directives for reading requirements are supported since version 62.6. + The format for the file resembles a ``requirements.txt`` file, + however please keep in mind that all non-comment lines must conform with :pep:`508` + (``pip``-specify syntaxes, e.g. ``-c/-r/-e`` flags, are not supported). + Library developers should avoid tightly pinning their dependencies to a specific + version (e.g. via a "locked" requirements file). + + +Compatibility with other tools +============================== + +Historically, several tools explored declarative package configuration +in parallel. And several of them chose to place the packaging +configuration within the project's :file:`setup.cfg` file. +One of the first was ``distutils2``, which development has stopped in +2013. Other include ``pbr`` which is still under active development or +``d2to1``, which was a plug-in that backports declarative configuration +to ``distutils``, but has had no release since Oct. 2015. +As a way to harmonize packaging tools, ``setuptools``, having held the +position of *de facto* standard, has gradually integrated those +features as part of its core features. + +Still this has lead to some confusion and feature incompatibilities: + +- some tools support features others don't; +- some have similar features but the declarative syntax differs; + +The table below tries to summarize the differences. But, please, refer +to each tool documentation for up-to-date information. + +=========================== ========== ========== ===== === +feature setuptools distutils2 d2to1 pbr +=========================== ========== ========== ===== === +[metadata] description-file S Y Y Y +[files] S Y Y Y +entry_points Y Y Y S +[backwards_compat] N Y Y Y +=========================== ========== ========== ===== === + +Y: supported, N: unsupported, S: syntax differs (see +:ref:`above example`). + +Also note that some features were only recently added to ``setuptools``. +Please refer to the previous sections to find out when. diff --git a/docs/source/userguide/dependency_management.rst b/docs/source/userguide/dependency_management.rst new file mode 100644 index 000000000..33aaf6c65 --- /dev/null +++ b/docs/source/userguide/dependency_management.rst @@ -0,0 +1,422 @@ +===================================== +Dependencies Management in Setuptools +===================================== + +There are three types of dependency styles offered by setuptools: +1) build system requirement, 2) required dependency and 3) optional +dependency. + +Each dependency, regardless of type, needs to be specified according to :pep:`508` +and :pep:`440`. +This allows adding version :pep:`range restrictions <440#version-specifiers>` +and :ref:`environment markers `. + + +.. _build-requires: + +Build system requirement +======================== + +After organizing all the scripts and files and getting ready for packaging, +there needs to be a way to specify what programs and libraries are actually needed +do the packaging (in our case, ``setuptools`` of course). +This needs to be specified in your ``pyproject.toml`` file +(if you have forgot what this is, go to :doc:`/userguide/quickstart` or :doc:`/build_meta`): + +.. code-block:: toml + + [build-system] + requires = ["setuptools"] + #... + +Please note that you should also include here any other ``setuptools`` plugin +(e.g., :pypi:`setuptools-scm`, :pypi:`setuptools-golang`, :pypi:`setuptools-rust`) +or build-time dependency (e.g., :pypi:`Cython`, :pypi:`cppy`, :pypi:`pybind11`). + +.. note:: + In previous versions of ``setuptools``, + this used to be accomplished with the ``setup_requires`` keyword but is + now considered deprecated in favor of the :pep:`517` style described above. + To peek into how this legacy keyword is used, consult our :doc:`guide on + deprecated practice (WIP) `. + + +.. _Declaring Dependencies: + +Declaring required dependency +============================= +This is where a package declares its core dependencies, without which it won't +be able to run. ``setuptools`` supports automatically downloading and installing +these dependencies when the package is installed. Although there is more +finesse to it, let's start with a simple example. + +.. tab:: pyproject.toml + + .. code-block:: toml + + [project] + # ... + dependencies = [ + "docutils", + "BazSpam == 1.1", + ] + # ... + +.. tab:: setup.cfg + + .. code-block:: ini + + [options] + #... + install_requires = + docutils + BazSpam ==1.1 + +.. tab:: setup.py + + .. code-block:: python + + setup( + ..., + install_requires=[ + 'docutils', + 'BazSpam ==1.1', + ], + ) + + +When your project is installed (e.g., using :pypi:`pip`), all of the dependencies not +already installed will be located (via `PyPI`_), downloaded, built (if necessary), +and installed and 2) Any scripts in your project will be installed with wrappers +that verify the availability of the specified dependencies at runtime. + + +.. _environment-markers: + +Platform specific dependencies +------------------------------ +Setuptools offers the capability to evaluate certain conditions before blindly +installing everything listed in ``install_requires``. This is great for platform +specific dependencies. For example, the ``enum`` package was added in Python +3.4, therefore, package that depends on it can elect to install it only when +the Python version is older than 3.4. To accomplish this + +.. tab:: pyproject.toml + + .. code-block:: toml + + [project] + # ... + dependencies = [ + "enum34; python_version<'3.4'", + ] + # ... + +.. tab:: setup.cfg + + .. code-block:: ini + + [options] + #... + install_requires = + enum34;python_version<'3.4' + +.. tab:: setup.py + + .. code-block:: python + + setup( + ..., + install_requires=[ + "enum34;python_version<'3.4'", + ], + ) + +Similarly, if you also wish to declare ``pywin32`` with a minimal version of 1.0 +and only install it if the user is using a Windows operating system: + +.. tab:: pyproject.toml + + .. code-block:: toml + + [project] + # ... + dependencies = [ + "enum34; python_version<'3.4'", + "pywin32 >= 1.0; platform_system=='Windows'", + ] + # ... + +.. tab:: setup.cfg + + .. code-block:: ini + + [options] + #... + install_requires = + enum34;python_version<'3.4' + pywin32 >= 1.0;platform_system=='Windows' + +.. tab:: setup.py + + .. code-block:: python + + setup( + ..., + install_requires=[ + "enum34;python_version<'3.4'", + "pywin32 >= 1.0;platform_system=='Windows'", + ], + ) + +The environmental markers that may be used for testing platform types are +detailed in :pep:`508`. + +.. seealso:: + If environment markers are not enough an specific use case, + you can also consider creating a :ref:`backend wrapper ` + to implement custom detection logic. + + +Direct URL dependencies +----------------------- + +.. attention:: + `PyPI`_ and other standards-conformant package indices **do not** accept + packages that declare dependencies using direct URLs. ``pip`` will accept them + when installing packages from the local filesystem or from another URL, + however. + +Dependencies that are not available on a package index but can be downloaded +elsewhere in the form of a source repository or archive may be specified +using a variant of :pep:`PEP 440's direct references <440#direct-references>`: + +.. tab:: pyproject.toml + + .. code-block:: toml + + [project] + # ... + dependencies = [ + "Package-A @ git+https://example.net/package-a.git@main", + "Package-B @ https://example.net/archives/package-b.whl", + ] + +.. tab:: setup.cfg + + .. code-block:: ini + + [options] + #... + install_requires = + Package-A @ git+https://example.net/package-a.git@main + Package-B @ https://example.net/archives/package-b.whl + +.. tab:: setup.py + + .. code-block:: python + + setup( + install_requires=[ + "Package-A @ git+https://example.net/package-a.git@main", + "Package-B @ https://example.net/archives/package-b.whl", + ], + ..., + ) + +For source repository URLs, a list of supported protocols and VCS-specific +features such as selecting certain branches or tags can be found in pip's +documentation on `VCS support `_. +Supported formats for archive URLs are sdists and wheels. + + +Optional dependencies +===================== +Setuptools allows you to declare dependencies that are not installed by default. +This effectively means that you can create a "variant" of your package with a +set of extra functionalities. + +For example, let's consider a ``Package-A`` that offers +optional PDF support and requires two other dependencies for it to work: + +.. tab:: pyproject.toml + + .. code-block:: toml + + [project] + name = "Package-A" + # ... + [project.optional-dependencies] + PDF = ["ReportLab>=1.2", "RXP"] + +.. tab:: setup.cfg + + .. code-block:: ini + + [metadata] + name = Package-A + + [options.extras_require] + PDF = + ReportLab>=1.2 + RXP + + +.. tab:: setup.py + + .. code-block:: python + + setup( + name="Package-A", + ..., + extras_require={ + "PDF": ["ReportLab>=1.2", "RXP"], + }, + ) + +.. sidebar:: + + .. tip:: + It is also convenient to declare optional requirements for + ancillary tasks such as running tests and or building docs. + +The name ``PDF`` is an arbitrary :pep:`identifier <685>` of such a list of dependencies, to +which other components can refer and have them installed. + +A use case for this approach is that other package can use this "extra" for their +own dependencies. For example, if ``Package-B`` needs ``Package-A`` with PDF support +installed, it might declare the dependency like this: + +.. tab:: pyproject.toml + + .. code-block:: toml + + [project] + name = "Package-B" + # ... + dependencies = [ + "Package-A[PDF]" + ] + +.. tab:: setup.cfg + + .. code-block:: ini + + [metadata] + name = Package-B + #... + + [options] + #... + install_requires = + Package-A[PDF] + +.. tab:: setup.py + + .. code-block:: python + + setup( + name="Package-B", + install_requires=["Package-A[PDF]"], + ..., + ) + +This will cause ``ReportLab`` to be installed along with ``Package-A``, if ``Package-B`` is +installed -- even if ``Package-A`` was already installed. In this way, a project +can encapsulate groups of optional "downstream dependencies" under a feature +name, so that packages that depend on it don't have to know what the downstream +dependencies are. If a later version of ``Package-A`` builds in PDF support and +no longer needs ``ReportLab``, or if it ends up needing other dependencies besides +``ReportLab`` in order to provide PDF support, ``Package-B``'s setup information does +not need to change, but the right packages will still be installed if needed. + +.. tip:: + Best practice: if a project ends up no longer needing any other packages to + support a feature, it should keep an empty requirements list for that feature + in its ``extras_require`` argument, so that packages depending on that feature + don't break (due to an invalid feature name). + +.. warning:: + Historically ``setuptools`` also used to support extra dependencies in console + scripts, for example: + + .. tab:: setup.cfg + + .. code-block:: ini + + [metadata] + name = Package-A + #... + + [options] + #... + entry_points= + [console_scripts] + rst2pdf = project_a.tools.pdfgen [PDF] + rst2html = project_a.tools.htmlgen + + .. tab:: setup.py + + .. code-block:: python + + setup( + name="Package-A", + ..., + entry_points={ + "console_scripts": [ + "rst2pdf = project_a.tools.pdfgen [PDF]", + "rst2html = project_a.tools.htmlgen", + ], + }, + ) + + This syntax indicates that the entry point (in this case a console script) + is only valid when the PDF extra is installed. It is up to the installer + to determine how to handle the situation where PDF was not indicated + (e.g., omit the console script, provide a warning when attempting to load + the entry point, assume the extras are present and let the implementation + fail later). + + **However**, ``pip`` and other tools might not support this use case for extra + dependencies, therefore this practice is considered **deprecated**. + See :doc:`PyPUG:specifications/entry-points`. + + +Python requirement +================== +In some cases, you might need to specify the minimum required python version. +This can be configured as shown in the example below. + +.. tab:: pyproject.toml + + .. code-block:: toml + + [project] + name = "Package-B" + requires-python = ">=3.6" + # ... + +.. tab:: setup.cfg + + .. code-block:: ini + + [metadata] + name = Package-B + #... + + [options] + #... + python_requires = >=3.6 + +.. tab:: setup.py + + .. code-block:: python + + setup( + name="Package-B", + python_requires=">=3.6", + ..., + ) + + +.. _PyPI: https://pypi.org diff --git a/docs/source/userguide/development_mode.rst b/docs/source/userguide/development_mode.rst new file mode 100644 index 000000000..6f9f5417f --- /dev/null +++ b/docs/source/userguide/development_mode.rst @@ -0,0 +1,271 @@ +Development Mode (a.k.a. "Editable Installs") +============================================= + +When creating a Python project, developers usually want to implement and test +changes iteratively, before cutting a release and preparing a distribution archive. + +In normal circumstances this can be quite cumbersome and require the developers +to manipulate the ``PYTHONPATH`` environment variable or to continuously re-build +and re-install the project. + +To facilitate iterative exploration and experimentation, setuptools allows +users to instruct the Python interpreter and its import machinery to load the +code under development directly from the project folder without having to +copy the files to a different location in the disk. +This means that changes in the Python source code can immediately take place +without requiring a new installation. + +You can enter this "development mode" by performing an :doc:`editable installation +` inside of a :term:`virtual environment`, +using :doc:`pip's ` ``-e/--editable`` flag, as shown below: + +.. code-block:: bash + + $ cd your-python-project + $ python -m venv .venv + # Activate your environemt with: + # `source .venv/bin/activate` on Unix/macOS + # or `.venv\Scripts\activate` on Windows + + $ pip install --editable . + + # Now you have access to your package + # as if it was installed in .venv + $ python -c "import your_python_project" + + +An "editable installation" works very similarly to a regular install with +``pip install .``, except that it only installs your package dependencies, +metadata and wrappers for :ref:`console and GUI scripts `. +Under the hood, setuptools will try to create a special :mod:`.pth file ` +in the target directory (usually ``site-packages``) that extends the +``PYTHONPATH`` or install a custom :doc:`import hook `. + +When you're done with a given development task, you can simply uninstall your +package (as you would normally do with ``pip uninstall ``). + +Please note that, by default an editable install will expose at least all the +files that would be available in a regular installation. However, depending on +the file and directory organization in your project, it might also expose +as a side effect files that would not be normally available. +This is allowed so you can iteratively create new Python modules. +Please have a look on the following section if you are looking for a different behaviour. + +.. admonition:: Virtual Environments + + You can think about virtual environments as "isolated Python runtime deployments" + that allow users to install different sets of libraries and tools without + messing with the global behaviour of the system. + + They are a safe way of testing new projects and can be created easily + with the :mod:`venv` module from the standard library. + + Please note however that depending on your operating system or distribution, + ``venv`` might not come installed by default with Python. For those cases, + you might need to use the OS package manager to install it. + For example, in Debian/Ubuntu-based systems you can obtain it via: + + .. code-block:: bash + + sudo apt install python3-venv + + Alternatively, you can also try installing :pypi:`virtualenv`. + More information is available on the Python Packaging User Guide on + :doc:`PyPUG:guides/installing-using-pip-and-virtual-environments`. + +.. note:: + .. versionchanged:: v64.0.0 + Editable installation hooks implemented according to :pep:`660`. + Support for :pep:`namespace packages <420>` is still **EXPERIMENTAL**. + + +"Strict" editable installs +-------------------------- + +When thinking about editable installations, users might have the following +expectations: + +1. It should allow developers to add new files (or split/rename existing ones) + and have them automatically exposed. +2. It should behave as close as possible to a regular installation and help + users to detect problems (e.g. new files not being included in the distribution). + +Unfortunately these expectations are in conflict with each other. +To solve this problem ``setuptools`` allows developers to choose a more +*"strict"* mode for the editable installation. This can be done by passing +a special *configuration setting* via :pypi:`pip`, as indicated below: + +.. code-block:: bash + + pip install -e . --config-settings editable_mode=strict + +In this mode, new files **won't** be exposed and the editable installs will +try to mimic as much as possible the behavior of a regular install. +Under the hood, ``setuptools`` will create a tree of file links in an auxiliary +directory (``$your_project_dir/build``) and add it to ``PYTHONPATH`` via a +:mod:`.pth file `. (Please be careful to not delete this repository +by mistake otherwise your files may stop being accessible). + +.. warning:: + Strict editable installs require auxiliary files to be placed in a + ``build/__editable__.*`` directory (relative to your project root). + + Please be careful to not remove this directory while testing your project, + otherwise your editable installation may be compromised. + + You can remove the ``build/__editable__.*`` directory after uninstalling. + + +.. note:: + .. versionadded:: v64.0.0 + Added new *strict* mode for editable installations. + The exact details of how this mode is implemented may vary. + + +Limitations +----------- + +- The *editable* term is used to refer only to Python modules + inside the package directories. Non-Python files, external (data) files, + executable script files, binary extensions, headers and metadata may be + exposed as a *snapshot* of the version they were at the moment of the + installation. +- Adding new dependencies, entry-points or changing your project's metadata + require a fresh "editable" re-installation. +- Console scripts and GUI scripts **MUST** be specified via :doc:`entry-points + ` to work properly. +- *Strict* editable installs require the file system to support + either :wiki:`symbolic ` or :wiki:`hard links `. + This installation mode might also generate auxiliary files under the project directory. +- There is *no guarantee* that the editable installation will be performed + using a specific technique. Depending on each project, ``setuptools`` may + select a different approach to ensure the package is importable at runtime. +- There is *no guarantee* that files outside the top-level package directory + will be accessible after an editable install. +- There is *no guarantee* that attributes like ``__path__`` or ``__file__`` + will correspond to the exact location of the original files (e.g., + ``setuptools`` might employ file links to perform the editable installation). + Users are encouraged to use tools like :mod:`importlib.resources` or + :mod:`importlib.metadata` when trying to access package files directly. +- Editable installations may not work with + :doc:`namespaces created with pkgutil or pkg_resources + `. + Please use :pep:`420`-style implicit namespaces [#namespaces]_. +- Support for :pep:`420`-style implicit namespace packages for + projects structured using :ref:`flat-layout` is still **experimental**. + If you experience problems, you can try converting your package structure + to the :ref:`src-layout`. +- File system entries in the current working directory + whose names coincidentally match installed packages + may take precedence in :doc:`Python's import system `. + Users are encouraged to avoid such scenarios [#cwd]_. + +.. attention:: + Editable installs are **not a perfect replacement for regular installs** + in a test environment. When in doubt, please test your projects as + installed via a regular wheel. There are tools in the Python ecosystem, + like :pypi:`tox` or :pypi:`nox`, that can help you with that + (when used with appropriate configuration). + + +Legacy Behavior +--------------- + +If your project is not compatible with the new "editable installs" or you wish +to replicate the legacy behavior, for the time being you can also perform the +installation in the ``compat`` mode: + +.. code-block:: bash + + pip install -e . --config-settings editable_mode=compat + +This installation mode will try to emulate how ``python setup.py develop`` +works (still within the context of :pep:`660`). + +.. warning:: + The ``compat`` mode is *transitional* and will be removed in + future versions of ``setuptools``, it exists only to help during the + migration period. + Also note that support for this mode is limited: + it is safe to assume that the ``compat`` mode is offered "as is", and + improvements are unlikely to be implemented. + Users are encouraged to try out the new editable installation techniques + and make the necessary adaptations. + +If the ``compat`` mode does not work for you, you can also disable the +:pep:`editable install <660>` hooks in ``setuptools`` by setting an environment +variable: + +.. code-block:: + + SETUPTOOLS_ENABLE_FEATURES="legacy-editable" + +This *may* cause the installer (e.g. ``pip``) to effectively run the "legacy" +installation command: ``python setup.py develop`` [#installer]_. + + +How editable installations work +------------------------------- + +*Advanced topic* + +There are many techniques that can be used to expose packages under development +in such a way that they are available as if they were installed. +Depending on the project file structure and the selected mode, ``setuptools`` +will choose one of these approaches for the editable installation [#criteria]_. + +A non-exhaustive list of implementation mechanisms is presented below. +More information is available on the text of :pep:`PEP 660 <660#what-to-put-in-the-wheel>`. + +- A static ``.pth`` file [#static_pth]_ can be added to one of the directories + listed in :func:`site.getsitepackages` or :func:`site.getusersitepackages` to + extend :obj:`sys.path`. +- A directory containing a *farm of file links* that mimic the + project structure and point to the original files can be employed. + This directory can then be added to :obj:`sys.path` using a static ``.pth`` file. +- A dynamic ``.pth`` file [#dynamic_pth]_ can also be used to install an + "import :term:`finder`" (:obj:`~importlib.abc.MetaPathFinder` or + :obj:`~importlib.abc.PathEntryFinder`) that will hook into Python's + :doc:`import system ` machinery. + +.. attention:: + ``Setuptools`` offers **no guarantee** of which technique will be used to + perform an editable installation. This will vary from project to project + and may change depending on the specific version of ``setuptools`` being + used. + + +---- + +.. rubric:: Notes + +.. [#namespaces] + You *may* be able to use *strict* editable installations with namespace + packages created with ``pkgutil`` or ``pkg_namespaces``, however this is not + officially supported. + +.. [#cwd] + Techniques like the :ref:`src-layout` or tooling-specific options like + `tox's changedir `_ + can be used to prevent such kinds of situations (checkout `this blog post + `_ for more + insights). + +.. [#installer] + For this workaround to work, the installer tool needs to support legacy + editable installations. (Future versions of ``pip``, for example, may drop + support for this feature). + +.. [#criteria] + ``setuptools`` strives to find a balance between allowing the user to see + the effects of project files being edited while still trying to keep the + editable installation as similar as possible to a regular installation. + +.. [#static_pth] + i.e., a ``.pth`` file where each line correspond to a path that should be + added to :obj:`sys.path`. See :mod:`Site-specific configuration hook `. + +.. [#dynamic_pth] + i.e., a ``.pth`` file that starts where each line starts with an ``import`` + statement and executes arbitrary Python code. See :mod:`Site-specific + configuration hook `. diff --git a/docs/source/userguide/distribution.rst b/docs/source/userguide/distribution.rst new file mode 100644 index 000000000..ae2dc4a45 --- /dev/null +++ b/docs/source/userguide/distribution.rst @@ -0,0 +1,198 @@ +.. _Specifying Your Project's Version: + +Specifying Your Project's Version +================================= + +Setuptools can work well with most versioning schemes. Over the years, +setuptools has tried to closely follow the :pep:`440` scheme, but it +also supports legacy versions. There are, however, a +few special things to watch out for, in order to ensure that setuptools and +other tools can always tell what version of your package is newer than another +version. Knowing these things will also help you correctly specify what +versions of other projects your project depends on. + +A version consists of an alternating series of release numbers and +`pre-release `_ +or `post-release `_ tags. A +release number is a series of digits punctuated by +dots, such as ``2.4`` or ``0.5``. Each series of digits is treated +numerically, so releases ``2.1`` and ``2.1.0`` are different ways to spell the +same release number, denoting the first subrelease of release 2. But ``2.10`` +is the *tenth* subrelease of release 2, and so is a different and newer release +from ``2.1`` or ``2.1.0``. Leading zeros within a series of digits are also +ignored, so ``2.01`` is the same as ``2.1``, and different from ``2.0.1``. + +Following a release number, you can have either a pre-release or post-release +tag. Pre-release tags make a version be considered *older* than the version +they are appended to. So, revision ``2.4`` is *newer* than release candidate +``2.4rc1``, which in turn is newer than beta release ``2.4b1`` or +alpha release ``2.4a1``. Postrelease tags make +a version be considered *newer* than the version they are appended to. So, +revisions like ``2.4.post1`` are newer than ``2.4``, but *older* +than ``2.4.1`` (which has a higher release number). + +In the case of legacy versions (for example, ``2.4pl1``), they are considered +older than non-legacy versions. Taking that in count, a revision ``2.4pl1`` +is *older* than ``2.4``. Note that ``2.4pl1`` is not :pep:`440`-compliant. + +A pre-release tag is a series of letters that are alphabetically before +"final". Some examples of prerelease tags would include ``alpha``, ``beta``, +``a``, ``c``, ``dev``, and so on. You do not have to place a dot or dash +before the prerelease tag if it's immediately after a number, but it's okay to +do so if you prefer. Thus, ``2.4c1`` and ``2.4.c1`` and ``2.4-c1`` all +represent release candidate 1 of version ``2.4``, and are treated as identical +by setuptools. Note that only ``a``, ``b``, and ``rc`` are :pep:`440`-compliant +pre-release tags. + +In addition, there are three special prerelease tags that are treated as if +they were ``rc``: ``c``, ``pre``, and ``preview``. So, version +``2.4c1``, ``2.4pre1`` and ``2.4preview1`` are all the exact same version as +``2.4rc1``, and are treated as identical by setuptools. + +A post-release tag is the string ``.post``, followed by a non-negative integer +value. Post-release tags are generally used to separate patch numbers, port +numbers, build numbers, revision numbers, or date stamps from the release +number. For example, the version ``2.4.post1263`` might denote Subversion +revision 1263 of a post-release patch of version ``2.4``. Or you might use +``2.4.post20051127`` to denote a date-stamped post-release. Legacy post-release +tags could be either a series of letters that are alphabetically greater than or +equal to "final", or a dash (``-``) - for example ``2.4-r1263`` or +``2.4-20051127``. + +Notice that after each legacy pre or post-release tag, you are free to place +another release number, followed again by more pre- or post-release tags. For +example, ``0.6a9.dev41475`` could denote Subversion revision 41475 of the in- +development version of the ninth alpha of release 0.6. Notice that ``dev`` is +a pre-release tag, so this version is a *lower* version number than ``0.6a9``, +which would be the actual ninth alpha of release 0.6. But the ``41475`` is +a post-release tag, so this version is *newer* than ``0.6a9.dev``. + +For the most part, setuptools' interpretation of version numbers is intuitive, +but here are a few tips that will keep you out of trouble in the corner cases: + +* Don't stick adjoining pre-release tags together without a dot or number + between them. Version ``1.9adev`` is the ``adev`` prerelease of ``1.9``, + *not* a development pre-release of ``1.9a``. Use ``.dev`` instead, as in + ``1.9a.dev``, or separate the prerelease tags with a number, as in + ``1.9a0dev``. ``1.9a.dev``, ``1.9a0dev``, and even ``1.9a0.dev0`` are + identical versions from setuptools' point of view, so you can use whatever + scheme you prefer. Of these examples, only ``1.9a0.dev0`` is + :pep:`440`-compliant. + +* If you want to be certain that your chosen numbering scheme works the way + you think it will, you can use the ``pkg_resources.parse_version()`` function + to compare different version numbers:: + + >>> from pkg_resources import parse_version + >>> parse_version("1.9.a.dev") == parse_version("1.9a0dev") + True + >>> parse_version("2.1-rc2") < parse_version("2.1") + True + >>> parse_version("0.6a9dev-r41475") < parse_version("0.6a9") + True + +Once you've decided on a version numbering scheme for your project, you can +have setuptools automatically tag your in-development releases with various +pre- or post-release tags. See the following section for more details. + + +Tagging and "Daily Build" or "Snapshot" Releases +------------------------------------------------ + +.. warning:: + Please note that running ``python setup.py ...`` directly is no longer + considered a good practice and that in the future the commands ``egg_info`` + and ``rotate`` will be deprecated. + + As a result, the instructions and information presented in this section + should be considered **transitional** while setuptools don't provide a + mechanism for tagging releases. + + Meanwhile, if you can also consider using :pypi:`setuptools-scm` to achieve + similar objectives. + + +When a set of related projects are under development, it may be important to +track finer-grained version increments than you would normally use for e.g. +"stable" releases. While stable releases might be measured in dotted numbers +with alpha/beta/etc. status codes, development versions of a project often +need to be tracked by revision or build number or even build date. This is +especially true when projects in development need to refer to one another, and +therefore may literally need an up-to-the-minute version of something! + +To support these scenarios, ``setuptools`` allows you to "tag" your source and +egg distributions by adding one or more of the following to the project's +"official" version identifier: + +* A manually-specified pre-release tag, such as "build" or "dev", or a + manually-specified post-release tag, such as a build or revision number + (``--tag-build=STRING, -bSTRING``) + +* An 8-character representation of the build date (``--tag-date, -d``), as + a postrelease tag + +You can add these tags by adding ``egg_info`` and the desired options to +the command line ahead of the ``sdist`` or ``bdist`` commands that you want +to generate a daily build or snapshot for. See the section below on the +:ref:`egg_info ` command for more details. + +(Also, before you release your project, be sure to see the section on +:ref:`Specifying Your Project's Version` for more information about how pre- and +post-release tags affect how version numbers are interpreted. This is +important in order to make sure that dependency processing tools will know +which versions of your project are newer than others). + +Finally, if you are creating builds frequently, and either building them in a +downloadable location or are copying them to a distribution server, you should +probably also check out the :ref:`rotate ` command, which lets you automatically +delete all but the N most-recently-modified distributions matching a glob +pattern. So, you can use a command line like:: + + setup.py egg_info -rbDEV bdist_egg rotate -m.egg -k3 + +to build an egg whose version info includes "DEV-rNNNN" (where NNNN is the +most recent Subversion revision that affected the source tree), and then +delete any egg files from the distribution directory except for the three +that were built most recently. + +If you have to manage automated builds for multiple packages, each with +different tagging and rotation policies, you may also want to check out the +:ref:`alias ` command, which would let each package define an alias like ``daily`` +that would perform the necessary tag, build, and rotate commands. Then, a +simpler script or cron job could just run ``setup.py daily`` in each project +directory. (And, you could also define sitewide or per-user default versions +of the ``daily`` alias, so that projects that didn't define their own would +use the appropriate defaults.) + +Making "Official" (Non-Snapshot) Releases +----------------------------------------- + +When you make an official release, creating source or binary distributions, +you will need to override the tag settings from ``setup.cfg``, so that you +don't end up registering versions like ``foobar-0.7a1.dev-r34832``. This is +easy to do if you are developing on the trunk and using tags or branches for +your releases - just make the change to ``setup.cfg`` after branching or +tagging the release, so the trunk will still produce development snapshots. + +Alternately, if you are not branching for releases, you can override the +default version options on the command line, using something like:: + + setup.py egg_info -Db "" sdist bdist_egg + +The first part of this command (``egg_info -Db ""``) will override the +configured tag information, before creating source and binary eggs. Thus, these +commands will use the plain version from your ``setup.py``, without adding the +build designation string. + +Of course, if you will be doing this a lot, you may wish to create a personal +alias for this operation, e.g.:: + + setup.py alias -u release egg_info -Db "" + +You can then use it like this:: + + setup.py release sdist bdist_egg + +Or of course you can create more elaborate aliases that do all of the above. +See the sections below on the :ref:`egg_info ` and +:ref:`alias ` commands for more ideas. diff --git a/docs/source/userguide/entry_point.rst b/docs/source/userguide/entry_point.rst new file mode 100644 index 000000000..163ce1d9d --- /dev/null +++ b/docs/source/userguide/entry_point.rst @@ -0,0 +1,571 @@ +.. _`entry_points`: + +============ +Entry Points +============ + +Entry points are a type of metadata that can be exposed by packages on installation. +They are a very useful feature of the Python ecosystem, +and come specially handy in two scenarios: + +1. The package would like to provide commands to be run at the terminal. +This functionality is known as *console* scripts. The command may also +open up a GUI, in which case it is known as a *GUI* script. An example +of a console script is the one provided by the :pypi:`pip` package, which +allows you to run commands like ``pip install`` in the terminal. + +2. A package would like to enable customization of its functionalities +via *plugins*. For example, the test framework :pypi:`pytest` allows +customization via the ``pytest11`` entry point, and the syntax +highlighting tool :pypi:`pygments` allows specifying additional styles +using the entry point ``pygments.styles``. + + +.. _console-scripts: + +Console Scripts +=============== + +Let us start with console scripts. +First consider an example without entry points. Imagine a package +defined thus:: + + project_root_directory + ├── pyproject.toml # and/or setup.cfg, setup.py + └── src + └── timmins + ├── __init__.py + └── ... + +with ``__init__.py`` as: + +.. code-block:: python + + def hello_world(): + print("Hello world") + +Now, suppose that we would like to provide some way of executing the +function ``hello_world()`` from the command-line. One way to do this +is to create a file ``src/timmins/__main__.py`` providing a hook as +follows: + +.. code-block:: python + + from . import hello_world + + if __name__ == '__main__': + hello_world() + +Then, after installing the package ``timmins``, we may invoke the ``hello_world()`` +function as follows, through the `runpy `_ +module: + +.. code-block:: bash + + $ python -m timmins + Hello world + +Instead of this approach using ``__main__.py``, you can also create a +user-friendly CLI executable that can be called directly without ``python -m``. +In the above example, to create a command ``hello-world`` that invokes +``timmins.hello_world``, add a console script entry point to your +configuration: + +.. tab:: pyproject.toml + + .. code-block:: toml + + [project.scripts] + hello-world = "timmins:hello_world" + +.. tab:: setup.cfg + + .. code-block:: ini + + [options.entry_points] + console_scripts = + hello-world = timmins:hello_world + +.. tab:: setup.py + + .. code-block:: python + + from setuptools import setup + + setup( + # ..., + entry_points={ + 'console_scripts': [ + 'hello-world = timmins:hello_world', + ] + } + ) + + +After installing the package, a user may invoke that function by simply calling +``hello-world`` on the command line: + +.. code-block:: bash + + $ hello-world + Hello world + +Note that any function configured as a console script, i.e. ``hello_world()`` in +this example, should not accept any arguments. If your function requires any input +from the user, you can use regular command-line argument parsing utilities like +:mod:`argparse` within the body of +the function to parse user input given via :obj:`sys.argv`. + +You may have noticed that we have used a special syntax to specify the function +that must be invoked by the console script, i.e. we have written ``timmins:hello_world`` +with a colon ``:`` separating the package name and the function name. The full +specification of this syntax is discussed in the `last section <#entry-points-syntax>`_ +of this document, and this can be used to specify a function located anywhere in +your package, not just in ``__init__.py``. + +GUI Scripts +=========== + +In addition to ``console_scripts``, Setuptools supports ``gui_scripts``, which +will launch a GUI application without running in a terminal window. + +For example, if we have a project with the same directory structure as before, +with an ``__init__.py`` file containing the following: + +.. code-block:: python + + import PySimpleGUI as sg + + def hello_world(): + sg.Window(title="Hello world", layout=[[]], margins=(100, 50)).read() + +Then, we can add a GUI script entry point: + +.. tab:: pyproject.toml + + .. code-block:: toml + + [project.gui-scripts] + hello-world = "timmins:hello_world" + +.. tab:: setup.cfg + + .. code-block:: ini + + [options.entry_points] + gui_scripts = + hello-world = timmins:hello_world + +.. tab:: setup.py + + .. code-block:: python + + from setuptools import setup + + setup( + # ..., + entry_points={ + 'gui_scripts': [ + 'hello-world = timmins:hello_world', + ] + } + ) + +.. note:: + To be able to import ``PySimpleGUI``, you need to add ``pysimplegui`` to your package dependencies. + See :doc:`/userguide/dependency_management` for more information. + +Now, running: + +.. code-block:: bash + + $ hello-world + +will open a small application window with the title 'Hello world'. + +Note that just as with console scripts, any function configured as a GUI script +should not accept any arguments, and any user input can be parsed within the +body of the function. GUI scripts also use the same syntax (discussed in the +`last section <#entry-points-syntax>`_) for specifying the function to be invoked. + +.. note:: + + The difference between ``console_scripts`` and ``gui_scripts`` only affects + Windows systems. [#use_for_scripts]_ ``console_scripts`` are wrapped in a console + executable, so they are attached to a console and can use ``sys.stdin``, + ``sys.stdout`` and ``sys.stderr`` for input and output. ``gui_scripts`` are + wrapped in a GUI executable, so they can be started without a console, but + cannot use standard streams unless application code redirects them. Other + platforms do not have the same distinction. + +.. note:: + + Console and GUI scripts work because behind the scenes, installers like :pypi:`pip` + create wrapper scripts around the function(s) being invoked. For example, + the ``hello-world`` entry point in the above two examples would create a + command ``hello-world`` launching a script like this: [#use_for_scripts]_ + + .. code-block:: python + + import sys + from timmins import hello_world + sys.exit(hello_world()) + +.. _dynamic discovery of services and plugins: + +Advertising Behavior +==================== + +Console/GUI scripts are one use of the more general concept of entry points. Entry +points more generally allow a packager to advertise behavior for discovery by +other libraries and applications. This feature enables "plug-in"-like +functionality, where one library solicits entry points and any number of other +libraries provide those entry points. + +A good example of this plug-in behavior can be seen in +`pytest plugins `_, +where pytest is a test framework that allows other libraries to extend +or modify its functionality through the ``pytest11`` entry point. + +The console/GUI scripts work similarly, where libraries advertise their commands +and tools like ``pip`` create wrapper scripts that invoke those commands. + +Entry Points for Plugins +======================== + +Let us consider a simple example to understand how we can implement entry points +corresponding to plugins. Say we have a package ``timmins`` with the following +directory structure:: + + timmins + ├── pyproject.toml # and/or setup.cfg, setup.py + └── src + └── timmins + └── __init__.py + +and in ``src/timmins/__init__.py`` we have the following code: + +.. code-block:: python + + def hello_world(): + print('Hello world') + +Basically, we have defined a ``hello_world()`` function which will print the text +'Hello world'. Now, let us say we want to print the text 'Hello world' in different +ways. The current function just prints the text as it is - let us say we want another +style in which the text is enclosed within exclamation marks:: + + !!! Hello world !!! + +Let us see how this can be done using plugins. First, let us separate the style of +printing the text from the text itself. In other words, we can change the code in +``src/timmins/__init__.py`` to something like this: + +.. code-block:: python + + def display(text): + print(text) + + def hello_world(): + display('Hello world') + +Here, the ``display()`` function controls the style of printing the text, and the +``hello_world()`` function calls the ``display()`` function to print the text 'Hello +world`. + +Right now the ``display()`` function just prints the text as it is. In order to be able +to customize it, we can do the following. Let us introduce a new *group* of entry points +named ``timmins.display``, and expect plugin packages implementing this entry point +to supply a ``display()``-like function. Next, to be able to automatically discover plugin +packages that implement this entry point, we can use the +:mod:`importlib.metadata` module, +as follows: + +.. code-block:: python + + from importlib.metadata import entry_points + display_eps = entry_points(group='timmins.display') + +.. note:: + Each ``importlib.metadata.EntryPoint`` object is an object containing a ``name``, a + ``group``, and a ``value``. For example, after setting up the plugin package as + described below, ``display_eps`` in the above code will look like this: [#package_metadata]_ + + .. code-block:: python + + ( + EntryPoint(name='excl', value='timmins_plugin_fancy:excl_display', group='timmins.display'), + ..., + ) + +``display_eps`` will now be a list of ``EntryPoint`` objects, each referring to ``display()``-like +functions defined by one or more installed plugin packages. Then, to import a specific +``display()``-like function - let us choose the one corresponding to the first discovered +entry point - we can use the ``load()`` method as follows: + +.. code-block:: python + + display = display_eps[0].load() + +Finally, a sensible behaviour would be that if we cannot find any plugin packages customizing +the ``display()`` function, we should fall back to our default implementation which prints +the text as it is. With this behaviour included, the code in ``src/timmins/__init__.py`` +finally becomes: + +.. code-block:: python + + from importlib.metadata import entry_points + display_eps = entry_points(group='timmins.display') + try: + display = display_eps[0].load() + except IndexError: + def display(text): + print(text) + + def hello_world(): + display('Hello world') + +That finishes the setup on ``timmins``'s side. Next, we need to implement a plugin +which implements the entry point ``timmins.display``. Let us name this plugin +``timmins-plugin-fancy``, and set it up with the following directory structure:: + + timmins-plugin-fancy + ├── pyproject.toml # and/or setup.cfg, setup.py + └── src + └── timmins_plugin_fancy + └── __init__.py + +And then, inside ``src/timmins_plugin_fancy/__init__.py``, we can put a function +named ``excl_display()`` that prints the given text surrounded by exclamation marks: + +.. code-block:: python + + def excl_display(text): + print('!!!', text, '!!!') + +This is the ``display()``-like function that we are looking to supply to the +``timmins`` package. We can do that by adding the following in the configuration +of ``timmins-plugin-fancy``: + +.. tab:: pyproject.toml + + .. code-block:: toml + + # Note the quotes around timmins.display in order to escape the dot . + [project.entry-points."timmins.display"] + excl = "timmins_plugin_fancy:excl_display" + +.. tab:: setup.cfg + + .. code-block:: ini + + [options.entry_points] + timmins.display = + excl = timmins_plugin_fancy:excl_display + +.. tab:: setup.py + + .. code-block:: python + + from setuptools import setup + + setup( + # ..., + entry_points = { + 'timmins.display': [ + 'excl = timmins_plugin_fancy:excl_display' + ] + } + ) + +Basically, this configuration states that we are a supplying an entry point +under the group ``timmins.display``. The entry point is named ``excl`` and it +refers to the function ``excl_display`` defined by the package ``timmins-plugin-fancy``. + +Now, if we install both ``timmins`` and ``timmins-plugin-fancy``, we should get +the following: + +.. code-block:: pycon + + >>> from timmins import hello_world + >>> hello_world() + !!! Hello world !!! + +whereas if we only install ``timmins`` and not ``timmins-plugin-fancy``, we should +get the following: + +.. code-block:: pycon + + >>> from timmins import hello_world + >>> hello_world() + Hello world + +Therefore, our plugin works. + +Our plugin could have also defined multiple entry points under the group ``timmins.display``. +For example, in ``src/timmins_plugin_fancy/__init__.py`` we could have two ``display()``-like +functions, as follows: + +.. code-block:: python + + def excl_display(text): + print('!!!', text, '!!!') + + def lined_display(text): + print(''.join(['-' for _ in text])) + print(text) + print(''.join(['-' for _ in text])) + +The configuration of ``timmins-plugin-fancy`` would then change to: + +.. tab:: pyproject.toml + + .. code-block:: toml + + [project.entry-points."timmins.display"] + excl = "timmins_plugin_fancy:excl_display" + lined = "timmins_plugin_fancy:lined_display" + +.. tab:: setup.cfg + + .. code-block:: ini + + [options.entry_points] + timmins.display = + excl = timmins_plugin_fancy:excl_display + lined = timmins_plugin_fancy:lined_display + +.. tab:: setup.py + + .. code-block:: python + + from setuptools import setup + + setup( + # ..., + entry_points = { + 'timmins.display': [ + 'excl = timmins_plugin_fancy:excl_display', + 'lined = timmins_plugin_fancy:lined_display', + ] + } + ) + +On the ``timmins`` side, we can also use a different strategy of loading entry +points. For example, we can search for a specific display style: + +.. code-block:: python + + display_eps = entry_points(group='timmins.display') + try: + display = display_eps['lined'].load() + except KeyError: + # if the 'lined' display is not available, use something else + ... + +Or we can also load all plugins under the given group. Though this might not +be of much use in our current example, there are several scenarios in which this +is useful: + +.. code-block:: python + + display_eps = entry_points(group='timmins.display') + for ep in display_eps: + display = ep.load() + # do something with display + ... + +Another point is that in this particular example, we have used plugins to +customize the behaviour of a function (``display()``). In general, we can use entry +points to enable plugins to not only customize the behaviour of functions, but also +of entire classes and modules. This is unlike the case of console/GUI scripts, +where entry points can only refer to functions. The syntax used for specifying the +entry points remains the same as for console/GUI scripts, and is discussed in the +`last section <#entry-points-syntax>`_. + +.. tip:: + The recommended approach for loading and importing entry points is the + :mod:`importlib.metadata` module, + which is a part of the standard library since Python 3.8. For older versions of + Python, its backport :pypi:`importlib_metadata` should be used. While using the + backport, the only change that has to be made is to replace ``importlib.metadata`` + with ``importlib_metadata``, i.e. + + .. code-block:: python + + from importlib_metadata import entry_points + ... + +In summary, entry points allow a package to open its functionalities for +customization via plugins. +The package soliciting the entry points need not have any dependency +or prior knowledge about the plugins implementing the entry points, and +downstream users are able to compose functionality by pulling together +plugins implementing the entry points. + +Entry Points Syntax +=================== + +The syntax for entry points is specified as follows:: + + = [:[.[.]*]] + +Here, the square brackets ``[]`` denote optionality and the asterisk ``*`` +denotes repetition. +``name`` is the name of the script/entry point you want to create, the left hand +side of ``:`` is the package or module that contains the object you want to invoke +(think about it as something you would write in an import statement), and the right +hand side is the object you want to invoke (e.g. a function). + +To make this syntax more clear, consider the following examples: + +Package or module + If you supply:: + + = + + as the entry point, where ```` can contain ``.`` in the case + of sub-modules or sub-packages, then, tools in the Python ecosystem will roughly + interpret this value as: + + .. code-block:: python + + import + parsed_value = + +Module-level object + If you supply:: + + = : + + where ```` does not contain any ``.``, this will be roughly interpreted + as: + + .. code-block:: python + + from import + parsed_value = + +Nested object + If you supply:: + + = :.. + + this will be roughly interpreted as: + + .. code-block:: python + + from import + parsed_value = .. + +In the case of console/GUI scripts, this syntax can be used to specify a function, while +in the general case of entry points as used for plugins, it can be used to specify a function, +class or module. + +---- + +.. [#use_for_scripts] + Reference: https://packaging.python.org/en/latest/specifications/entry-points/#use-for-scripts + +.. [#package_metadata] + Reference: https://packaging.python.org/en/latest/guides/creating-and-discovering-plugins/#using-package-metadata diff --git a/docs/source/userguide/ext_modules.rst b/docs/source/userguide/ext_modules.rst new file mode 100644 index 000000000..a59599b27 --- /dev/null +++ b/docs/source/userguide/ext_modules.rst @@ -0,0 +1,172 @@ +========================== +Building Extension Modules +========================== + +Setuptools can build C/C++ extension modules. The keyword argument +``ext_modules`` of ``setup()`` should be a list of instances of the +:class:`setuptools.Extension` class. + + +For example, let's consider a simple project with only one extension module:: + + + ├── pyproject.toml + └── foo.c + +and all project metadata configuration in the ``pyproject.toml`` file: + +.. code-block:: toml + + # pyproject.toml + [build-system] + requires = ["setuptools"] + build-backend = "setuptools.build_meta" + + [project] + name = "mylib-foo" # as it would appear on PyPI + version = "0.42" + +To instruct setuptools to compile the ``foo.c`` file into the extension module +``mylib.foo``, we need to add a ``setup.py`` file similar to the following: + +.. code-block:: python + + from setuptools import Extension, setup + + setup( + ext_modules=[ + Extension( + name="mylib.foo", # as it would be imported + # may include packages/namespaces separated by `.` + + sources=["foo.c"], # all sources are compiled into a single binary file + ), + ] + ) + +.. seealso:: + You can find more information on the `Python docs about C/C++ extensions`_. + Alternatively, you might also be interested in learn about `Cython`_. + + If you plan to distribute a package that uses extensions across multiple + platforms, :pypi:`cibuildwheel` can also be helpful. + +.. important:: + All files used to compile your extension need to be available on the system + when building the package, so please make sure to include some documentation + on how developers interested in building your package from source + can obtain operating system level dependencies + (e.g. compilers and external binary libraries/artifacts). + + You will also need to make sure that all auxiliary files that are contained + inside your :term:`project` (e.g. C headers authored by you or your team) + are configured to be included in your :term:`sdist `. + Please have a look on our section on :ref:`Controlling files in the distribution`. + + +Compiler and linker options +=========================== + +The command ``build_ext`` builds C/C++ extension modules. It creates +a command line for running the compiler and linker by combining +compiler and linker options from various sources: + +.. Reference: `test_customize_compiler` in distutils/tests/test_sysconfig.py + +* the ``sysconfig`` variables ``CC``, ``CXX``, ``CCSHARED``, + ``LDSHARED``, and ``CFLAGS``, +* the environment variables ``CC``, ``CPP``, + ``CXX``, ``LDSHARED`` and ``CFLAGS``, + ``CPPFLAGS``, ``LDFLAGS``, +* the ``Extension`` attributes ``include_dirs``, + ``library_dirs``, ``extra_compile_args``, ``extra_link_args``, + ``runtime_library_dirs``. + +.. Ignoring AR, ARFLAGS, RANLIB here because they are used by the (obsolete?) build_clib, not build_ext. + +Specifically, if the environment variables ``CC``, ``CPP``, ``CXX``, and ``LDSHARED`` +are set, they will be used instead of the ``sysconfig`` variables of the same names. + +The compiler options appear in the command line in the following order: + +.. Reference: "compiler_so" and distutils.ccompiler.gen_preprocess_options, CCompiler.compile, UnixCCompiler._compile + +* first, the options provided by the ``sysconfig`` variable ``CFLAGS``, +* then, the options provided by the environment variables ``CFLAGS`` and ``CPPFLAGS``, +* then, the options provided by the ``sysconfig`` variable ``CCSHARED``, +* then, a ``-I`` option for each element of ``Extension.include_dirs``, +* finally, the options provided by ``Extension.extra_compile_args``. + +The linker options appear in the command line in the following order: + +.. Reference: "linker_so" and CCompiler.link + +* first, the options provided by environment variables and ``sysconfig`` variables, +* then, a ``-L`` option for each element of ``Extension.library_dirs``, +* then, a linker-specific option like ``-Wl,-rpath`` for each element of ``Extension.runtime_library_dirs``, +* finally, the options provided by ``Extension.extra_link_args``. + +The resulting command line is then processed by the compiler and linker. +According to the GCC manual sections on `directory options`_ and +`environment variables`_, the C/C++ compiler searches for files named in +``#include `` directives in the following order: + +* first, in directories given by ``-I`` options (in left-to-right order), +* then, in directories given by the environment variable ``CPATH`` (in left-to-right order), +* then, in directories given by ``-isystem`` options (in left-to-right order), +* then, in directories given by the environment variable ``C_INCLUDE_PATH`` (for C) and ``CPLUS_INCLUDE_PATH`` (for C++), +* then, in standard system directories, +* finally, in directories given by ``-idirafter`` options (in left-to-right order). + +The linker searches for libraries in the following order: + +* first, in directories given by ``-L`` options (in left-to-right order), +* then, in directories given by the environment variable ``LIBRARY_PATH`` (in left-to-right order). + + +Distributing Extensions compiled with Cython +============================================ + +When your :pypi:`Cython` extension modules *are declared using the* +:class:`setuptools.Extension` *class*, ``setuptools`` will detect at build time +whether Cython is installed or not. + +If Cython is present, then ``setuptools`` will use it to build the ``.pyx`` files. +Otherwise, ``setuptools`` will try to find and compile the equivalent ``.c`` files +(instead of ``.pyx``). These files can be generated using the +`cython command line tool`_. + +You can ensure that Cython is always automatically installed into the build +environment by including it as a :ref:`build dependency ` in +your ``pyproject.toml``: + +.. code-block:: toml + + [build-system] + requires = [..., "cython"] + +Alternatively, you can include the ``.c`` code that is pre-compiled by Cython +into your source distribution, alongside the original ``.pyx`` files (this +might save a few seconds when building from an ``sdist``). +To improve version compatibility, you probably also want to include current +``.c`` files in your :wiki:`revision control system`, and rebuild them whenever +you check changes in for the ``.pyx`` source files. +This will ensure that people tracking your project will be able to build it +without installing Cython, and that there will be no variation due to small +differences in the generate C files. +Please checkout our docs on :ref:`controlling files in the distribution` for +more information. + +---- + +Extension API Reference +======================= + +.. autoclass:: setuptools.Extension + + +.. _Python docs about C/C++ extensions: https://docs.python.org/3/extending/extending.html +.. _Cython: https://cython.readthedocs.io/en/stable/index.html +.. _directory options: https://gcc.gnu.org/onlinedocs/gcc/Directory-Options.html +.. _environment variables: https://gcc.gnu.org/onlinedocs/gcc/Environment-Variables.html +.. _cython command line tool: https://cython.readthedocs.io/en/stable/src/userguide/source_files_and_compilation.html diff --git a/docs/source/userguide/extension.rst b/docs/source/userguide/extension.rst new file mode 100644 index 000000000..6f8cbbb22 --- /dev/null +++ b/docs/source/userguide/extension.rst @@ -0,0 +1,308 @@ +.. _Creating ``distutils`` Extensions: + +Extending or Customizing Setuptools +=================================== + +Setuptools design is based on the distutils_ package originally distributed +as part of Python's standard library, effectively serving as its successor +(as established in :pep:`632`). + +This means that ``setuptools`` strives to honor the extension mechanisms +provided by ``distutils``, and allows developers to create third party packages +that modify or augment the build process behavior. + +A simple way of doing that is to hook in new or existing +commands and ``setup()`` arguments just by defining "entry points". These +are mappings from command or argument names to a specification of where to +import a handler from. (See the section on :ref:`Dynamic Discovery of +Services and Plugins` for some more background on entry points). + +The following sections describe the most common procedures for extending +the ``distutils`` functionality used by ``setuptools``. + +.. important:: + Any entry-point defined in your ``setup.cfg``, ``setup.py`` or + ``pyproject.toml`` files are not immediately available for use. Your + package needs to be installed first, then ``setuptools`` will be able to + access these entry points. For example consider a ``Project-A`` that + defines entry points. When building ``Project-A``, these will not be + available. If ``Project-B`` declares a :doc:`build system requirement + ` on ``Project-A``, then ``setuptools`` + will be able to use ``Project-A``' customizations. + +Customizing Commands +-------------------- + +Both ``setuptools`` and ``distutils`` are structured around the *command design +pattern*. This means that each main action executed when building a +distribution package (such as creating a :term:`sdist ` +or :term:`wheel`) correspond to the implementation of a Python class. + +Originally in ``distutils``, these commands would correspond to actual CLI +arguments that could be passed to the ``setup.py`` script to trigger a +different aspect of the build. In ``setuptools``, however, these command +objects are just a design abstraction that encapsulate logic and help to +organise the code. + +You can overwrite exiting commands (or add new ones) by defining entry +points in the ``distutils.commands`` group. For example, if you wanted to add +a ``foo`` command, you might add something like this to your project: + +.. code-block:: ini + + # setup.cfg + ... + [options.entry_points] + distutils.commands = + foo = mypackage.some_module:foo + +Assuming, of course, that the ``foo`` class in ``mypackage.some_module`` is +a ``setuptools.Command`` subclass (documented below). + +Once a project containing such entry points has been activated on ``sys.path``, +(e.g. by running ``pip install``) the command(s) will be available to any +``setuptools``-based project. In fact, this is +how setuptools' own commands are installed: the setuptools project's setup +script defines entry points for them! + +The commands ``sdist``, ``build_py`` and ``build_ext`` are especially useful +to customize ``setuptools`` builds. Note however that when overwriting existing +commands, you should be very careful to maintain API compatibility. +Custom commands should try to replicate the same overall behavior as the +original classes, and when possible, even inherit from them. + +You should also consider handling exceptions such as ``CompileError``, +``LinkError``, ``LibError``, among others. These exceptions are available in +the ``setuptools.errors`` module. + +.. autoclass:: setuptools.Command + :members: + + +Supporting sdists and editable installs in ``build`` sub-commands +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +``build`` sub-commands (like ``build_py`` and ``build_ext``) +are encouraged to implement the following protocol: + +.. autoclass:: setuptools.command.build.SubCommand + :members: + + +Adding Arguments +---------------- + +.. warning:: Adding arguments to setup is discouraged as such arguments + are only supported through imperative execution and not supported through + declarative config. + +Sometimes, your commands may need additional arguments to the ``setup()`` +call. You can enable this by defining entry points in the +``distutils.setup_keywords`` group. For example, if you wanted a ``setup()`` +argument called ``bar_baz``, you might add something like this to your +extension project: + +.. code-block:: ini + + # setup.cfg + ... + [options.entry_points] + distutils.commands = + foo = mypackage.some_module:foo + distutils.setup_keywords = + bar_baz = mypackage.some_module:validate_bar_baz + +The idea here is that the entry point defines a function that will be called +to validate the ``setup()`` argument, if it's supplied. The ``Distribution`` +object will have the initial value of the attribute set to ``None``, and the +validation function will only be called if the ``setup()`` call sets it to +a non-``None`` value. Here's an example validation function:: + + def assert_bool(dist, attr, value): + """Verify that value is True, False, 0, or 1""" + if bool(value) != value: + raise SetupError( + "%r must be a boolean value (got %r)" % (attr,value) + ) + +Your function should accept three arguments: the ``Distribution`` object, +the attribute name, and the attribute value. It should raise a +``SetupError`` (from the ``setuptools.errors`` module) if the argument +is invalid. Remember, your function will only be called with non-``None`` values, +and the default value of arguments defined this way is always ``None``. So, your +commands should always be prepared for the possibility that the attribute will +be ``None`` when they access it later. + +If more than one active distribution defines an entry point for the same +``setup()`` argument, *all* of them will be called. This allows multiple +extensions to define a common argument, as long as they agree on +what values of that argument are valid. + + +Customizing Distribution Options +-------------------------------- + +Plugins may wish to extend or alter the options on a ``Distribution`` object to +suit the purposes of that project. For example, a tool that infers the +``Distribution.version`` from SCM-metadata may need to hook into the +option finalization. To enable this feature, Setuptools offers an entry +point ``setuptools.finalize_distribution_options``. That entry point must +be a callable taking one argument (the ``Distribution`` instance). + +If the callable has an ``.order`` property, that value will be used to +determine the order in which the hook is called. Lower numbers are called +first and the default is zero (0). + +Plugins may read, alter, and set properties on the distribution, but each +plugin is encouraged to load the configuration/settings for their behavior +independently. + + +Defining Additional Metadata +---------------------------- + +Some extensible applications and frameworks may need to define their own kinds +of metadata, which they can then access using the :mod:`importlib.metadata` APIs. +Ordinarily, this is done by having plugin +developers include additional files in their ``ProjectName.egg-info`` +directory. However, since it can be tedious to create such files by hand, you +may want to create an extension that will create the necessary files +from arguments to ``setup()``, in much the same way that ``setuptools`` does +for many of the ``setup()`` arguments it adds. See the section below for more +details. + + +.. _Adding new EGG-INFO Files: + +Adding new EGG-INFO Files +~~~~~~~~~~~~~~~~~~~~~~~~~ + +Some extensible applications or frameworks may want to allow third parties to +develop plugins with application or framework-specific metadata included in +the plugins' EGG-INFO directory, for easy access via the ``pkg_resources`` +metadata API. The easiest way to allow this is to create an extension +to be used from the plugin projects' setup scripts (via ``setup_requires``) +that defines a new setup keyword, and then uses that data to write an EGG-INFO +file when the ``egg_info`` command is run. + +The ``egg_info`` command looks for extension points in an ``egg_info.writers`` +group, and calls them to write the files. Here's a simple example of an +extension defining a setup argument ``foo_bar``, which is a list of +lines that will be written to ``foo_bar.txt`` in the EGG-INFO directory of any +project that uses the argument: + +.. code-block:: ini + + # setup.cfg + ... + [options.entry_points] + distutils.setup_keywords = + foo_bar = setuptools.dist:assert_string_list + egg_info.writers = + foo_bar.txt = setuptools.command.egg_info:write_arg + +This simple example makes use of two utility functions defined by setuptools +for its own use: a routine to validate that a setup keyword is a sequence of +strings, and another one that looks up a setup argument and writes it to +a file. Here's what the writer utility looks like:: + + def write_arg(cmd, basename, filename): + argname = os.path.splitext(basename)[0] + value = getattr(cmd.distribution, argname, None) + if value is not None: + value = "\n".join(value) + "\n" + cmd.write_or_delete_file(argname, filename, value) + +As you can see, ``egg_info.writers`` entry points must be a function taking +three arguments: a ``egg_info`` command instance, the basename of the file to +write (e.g. ``foo_bar.txt``), and the actual full filename that should be +written to. + +In general, writer functions should honor the command object's ``dry_run`` +setting when writing files, and use ``logging`` to do any console output. +The easiest way to conform to this requirement is to use +the ``cmd`` object's ``write_file()``, ``delete_file()``, and +``write_or_delete_file()`` methods exclusively for your file operations. +See those methods' docstrings for more details. + + +.. _Adding Support for Revision Control Systems: + +Adding Support for Revision Control Systems +------------------------------------------------- + +If the files you want to include in the source distribution are tracked using +Git, Mercurial or SVN, you can use the following packages to achieve that: + +- Git and Mercurial: :pypi:`setuptools_scm` +- SVN: :pypi:`setuptools_svn` + +If you would like to create a plugin for ``setuptools`` to find files tracked +by another revision control system, you can do so by adding an entry point to +the ``setuptools.file_finders`` group. The entry point should be a function +accepting a single directory name, and should yield all the filenames within +that directory (and any subdirectories thereof) that are under revision +control. + +For example, if you were going to create a plugin for a revision control system +called "foobar", you would write a function something like this: + +.. code-block:: python + + def find_files_for_foobar(dirname): + ... # loop to yield paths that start with `dirname` + +And you would register it in a setup script using something like this: + +.. code-block:: ini + + # setup.cfg + ... + + [options.entry_points] + setuptools.file_finders = + foobar = my_foobar_module:find_files_for_foobar + +Then, anyone who wants to use your plugin can simply install it, and their +local setuptools installation will be able to find the necessary files. + +It is not necessary to distribute source control plugins with projects that +simply use the other source control system, or to specify the plugins in +``setup_requires``. When you create a source distribution with the ``sdist`` +command, setuptools automatically records what files were found in the +``SOURCES.txt`` file. That way, recipients of source distributions don't need +to have revision control at all. However, if someone is working on a package +by checking out with that system, they will need the same plugin(s) that the +original author is using. + +A few important points for writing revision control file finders: + +* Your finder function MUST return relative paths, created by appending to the + passed-in directory name. Absolute paths are NOT allowed, nor are relative + paths that reference a parent directory of the passed-in directory. + +* Your finder function MUST accept an empty string as the directory name, + meaning the current directory. You MUST NOT convert this to a dot; just + yield relative paths. So, yielding a subdirectory named ``some/dir`` under + the current directory should NOT be rendered as ``./some/dir`` or + ``/somewhere/some/dir``, but *always* as simply ``some/dir`` + +* Your finder function SHOULD NOT raise any errors, and SHOULD deal gracefully + with the absence of needed programs (i.e., ones belonging to the revision + control system itself. It *may*, however, use ``logging.warning()`` to + inform the user of the missing program(s). + + +.. _distutils: https://docs.python.org/3.9/library/distutils.html + + +Final Remarks +------------- + +* To use a ``setuptools`` plugin, your users will need to add your package as a + build requirement to their build-system configuration. Please check out our + guides on :doc:`/userguide/dependency_management` for more information. + +* Directly calling ``python setup.py ...`` is considered a **deprecated** practice. + You should not add new commands to ``setuptools`` expecting them to be run + via this interface. diff --git a/docs/source/userguide/index.rst b/docs/source/userguide/index.rst new file mode 100644 index 000000000..d631c5d8a --- /dev/null +++ b/docs/source/userguide/index.rst @@ -0,0 +1,48 @@ +================================================== +Building and Distributing Packages with Setuptools +================================================== + +The first step towards sharing a Python library or program is to build a +distribution package [#package-overload]_. This includes adding a set of +additional files containing metadata and configuration to not only instruct +``setuptools`` on how the distribution should be built but also +to help installer (such as :pypi:`pip`) during the installation process. + +This document contains information to help Python developers through this +process. Please check the :doc:`/userguide/quickstart` for an overview of +the workflow. + +Also note that ``setuptools`` is what is known in the community as :pep:`build +backend <517#terminology-and-goals>`, user facing interfaces are provided by tools +such as :pypi:`pip` and :pypi:`build`. To use ``setuptools``, one must +explicitly create a ``pyproject.toml`` file as described :doc:`/build_meta`. + + +Contents +======== + +.. toctree:: + :maxdepth: 1 + + quickstart + package_discovery + dependency_management + development_mode + entry_point + datafiles + ext_modules + distribution + miscellaneous + extension + declarative_config + pyproject_config + +--- + +.. rubric:: Notes + +.. [#package-overload] + A :term:`Distribution Package` is also referred in the Python community simply as "package" + Unfortunately, this jargon might be a bit confusing for new users because the term package + can also to refer any :term:`directory ` (or sub directory) used to organize + :term:`modules ` and auxiliary files. diff --git a/docs/source/userguide/miscellaneous.rst b/docs/source/userguide/miscellaneous.rst new file mode 100644 index 000000000..19908e05a --- /dev/null +++ b/docs/source/userguide/miscellaneous.rst @@ -0,0 +1,100 @@ +.. _Controlling files in the distribution: + +Controlling files in the distribution +===================================== + +For the most common use cases, ``setuptools`` will automatically find out which +files are necessary for distributing the package. +These include all :term:`pure Python modules ` in the +``py_modules`` or ``packages`` configuration, and the C sources (but not C +headers) listed as part of extensions when creating a :term:`source +distribution (or "sdist")`. + +However, when building more complex packages (e.g. packages that include +non-Python files, or that need to use custom C headers), you might find that +not all files present in your project folder are included in package +:term:`distribution archive `. + +If you are using a :wiki:`Revision Control System`, such as git_ or mercurial_, +and your source distributions only need to include files that you're +tracking in revision control, you can use a ``setuptools`` :ref:`plugin `, such as :pypi:`setuptools-scm` or +:pypi:`setuptools-svn` to automatically include all tracked files into the ``sdist``. + +.. _Using MANIFEST.in: + +Alternatively, if you need finer control over the files (e.g. you don't want to +distribute :wiki:`CI/CD`-related files) or you need automatically generated files, +you can add a ``MANIFEST.in`` file at the root of your project, +to specify any files that the default file location algorithm doesn't catch. + +This file contains instructions that tell ``setuptools`` which files exactly +should be part of the ``sdist`` (or not). +A comprehensive guide to ``MANIFEST.in`` syntax is available at the +:doc:`PyPA's Packaging User Guide `. + +.. attention:: + Please note that ``setuptools`` supports the ``MANIFEST.in``, + and not ``MANIFEST`` (no extension). Any documentation, tutorial or example + that recommends using ``MANIFEST`` (no extension) is likely outdated. + +.. tip:: + The ``MANIFEST.in`` file contains commands that allow you to discover and + manipulate lists of files. There are many commands that can be used with + different objectives, but you should try to not make your ``MANIFEST.in`` + file too fine grained. + + A good idea is to start with a ``graft`` command (to add all + files inside a set of directories) and then fine tune the file selection + by removing the excess or adding isolated files. + +An example of ``MANIFEST.in`` for a simple project that organized according to a +:ref:`src-layout` is: + +.. code-block:: bash + + # MANIFEST.in -- just for illustration + graft src + graft tests + graft docs + # `-> adds all files inside a directory + + include tox.ini + # `-> matches file paths relative to the root of the project + + global-exclude *~ *.py[cod] *.so + # `-> matches file names (regardless of directory) + +Once the correct files are present in the ``sdist``, they can then be used by +binary extensions during the build process, or included in the final +:term:`wheel ` [#build-process]_ if you configure ``setuptools`` with +``include_package_data=True``. + +.. important:: + Please note that, when using ``include_package_data=True``, only files **inside + the package directory** are included in the final ``wheel``, by default. + + So for example, if you create a :term:`Python project ` that uses + :pypi:`setuptools-scm` and have a ``tests`` directory outside of the package + folder, the ``tests`` directory will be present in the ``sdist`` but not in the + ``wheel`` [#wheel-vs-sdist]_. + + See :doc:`/userguide/datafiles` for more information. + +---- + +.. [#build-process] + You can think about the build process as two stages: first the ``sdist`` + will be created and then the ``wheel`` will be produced from that ``sdist``. + +.. [#wheel-vs-sdist] + This happens because the ``sdist`` can contain files that are useful during + development or the build process itself, but not in runtime (e.g. tests, + docs, examples, etc...). + The ``wheel``, on the other hand, is a file format that has been optimized + and is ready to be unpacked into a running installation of Python or + :term:`Virtual Environment`. + Therefore it only contains items that are required during runtime. + +.. _git: https://git-scm.com +.. _mercurial: https://www.mercurial-scm.org diff --git a/docs/source/userguide/package_discovery.rst b/docs/source/userguide/package_discovery.rst new file mode 100644 index 000000000..9577a5346 --- /dev/null +++ b/docs/source/userguide/package_discovery.rst @@ -0,0 +1,589 @@ +.. _`package_discovery`: + +======================================== +Package Discovery and Namespace Packages +======================================== + +.. note:: + a full specification for the keywords supplied to ``setup.cfg`` or + ``setup.py`` can be found at :doc:`keywords reference ` + +.. important:: + The examples provided here are only to demonstrate the functionality + introduced. More metadata and options arguments need to be supplied + if you want to replicate them on your system. If you are completely + new to setuptools, the :doc:`quickstart` section is a good place to start. + +``Setuptools`` provides powerful tools to handle package discovery, including +support for namespace packages. + +Normally, you would specify the packages to be included manually in the following manner: + +.. tab:: setup.cfg + + .. code-block:: ini + + [options] + #... + packages = + mypkg + mypkg.subpkg1 + mypkg.subpkg2 + +.. tab:: setup.py + + .. code-block:: python + + setup( + # ... + packages=['mypkg', 'mypkg.subpkg1', 'mypkg.subpkg2'] + ) + +.. tab:: pyproject.toml (**BETA**) [#beta]_ + + .. code-block:: toml + + # ... + [tool.setuptools] + packages = ["mypkg", "mypkg.subpkg1", "mypkg.subpkg2"] + # ... + + +If your packages are not in the root of the repository or do not correspond +exactly to the directory structure, you also need to configure ``package_dir``: + +.. tab:: setup.cfg + + .. code-block:: ini + + [options] + # ... + package_dir = + = src + # directory containing all the packages (e.g. src/mypkg, src/mypkg/subpkg1, ...) + # OR + package_dir = + mypkg = lib + # mypkg.module corresponds to lib/module.py + mypkg.subpkg1 = lib1 + # mypkg.subpkg1.module1 corresponds to lib1/module1.py + mypkg.subpkg2 = lib2 + # mypkg.subpkg2.module2 corresponds to lib2/module2.py + # ... + +.. tab:: setup.py + + .. code-block:: python + + setup( + # ... + package_dir = {"": "src"} + # directory containing all the packages (e.g. src/mypkg, src/mypkg/subpkg1, ...) + ) + + # OR + + setup( + # ... + package_dir = { + "mypkg": "lib", # mypkg.module corresponds to lib/mod.py + "mypkg.subpkg1": "lib1", # mypkg.subpkg1.module1 corresponds to lib1/module1.py + "mypkg.subpkg2": "lib2" # mypkg.subpkg2.module2 corresponds to lib2/module2.py + # ... + ) + +.. tab:: pyproject.toml (**BETA**) [#beta]_ + + .. code-block:: toml + + [tool.setuptools] + # ... + package-dir = {"" = "src"} + # directory containing all the packages (e.g. src/mypkg1, src/mypkg2) + + # OR + + [tool.setuptools.package-dir] + mypkg = "lib" + # mypkg.module corresponds to lib/module.py + "mypkg.subpkg1" = "lib1" + # mypkg.subpkg1.module1 corresponds to lib1/module1.py + "mypkg.subpkg2" = "lib2" + # mypkg.subpkg2.module2 corresponds to lib2/module2.py + # ... + +This can get tiresome really quickly. To speed things up, you can rely on +setuptools automatic discovery, or use the provided tools, as explained in +the following sections. + +.. important:: + Although ``setuptools`` allows developers to create a very complex mapping + between directory names and package names, it is better to *keep it simple* + and reflect the desired package hierarchy in the directory structure, + preserving the same names. + +.. _auto-discovery: + +Automatic discovery +=================== + +.. warning:: Automatic discovery is a **beta** feature and might change in the future. + See :ref:`custom-discovery` for other methods of discovery. + +By default ``setuptools`` will consider 2 popular project layouts, each one with +its own set of advantages and disadvantages [#layout1]_ [#layout2]_ as +discussed in the following sections. + +Setuptools will automatically scan your project directory looking for these +layouts and try to guess the correct values for the :ref:`packages ` and :doc:`py_modules ` configuration. + +.. important:: + Automatic discovery will **only** be enabled if you **don't** provide any + configuration for ``packages`` and ``py_modules``. + If at least one of them is explicitly set, automatic discovery will not take place. + + **Note**: specifying ``ext_modules`` might also prevent auto-discover from + taking place, unless your opt into :doc:`pyproject_config` (which will + disable the backward compatible behaviour). + +.. _src-layout: + +src-layout +---------- +The project should contain a ``src`` directory under the project root and +all modules and packages meant for distribution are placed inside this +directory:: + + project_root_directory + ├── pyproject.toml # AND/OR setup.cfg, setup.py + ├── ... + └── src/ + └── mypkg/ + ├── __init__.py + ├── ... + ├── module.py + ├── subpkg1/ + │   ├── __init__.py + │   ├── ... + │   └── module1.py + └── subpkg2/ + ├── __init__.py + ├── ... + └── module2.py + +This layout is very handy when you wish to use automatic discovery, +since you don't have to worry about other Python files or folders in your +project root being distributed by mistake. In some circumstances it can be +also less error-prone for testing or when using :pep:`420`-style packages. +On the other hand you cannot rely on the implicit ``PYTHONPATH=.`` to fire +up the Python REPL and play with your package (you will need an +`editable install`_ to be able to do that). + +.. _flat-layout: + +flat-layout +----------- +*(also known as "adhoc")* + +The package folder(s) are placed directly under the project root:: + + project_root_directory + ├── pyproject.toml # AND/OR setup.cfg, setup.py + ├── ... + └── mypkg/ + ├── __init__.py + ├── ... + ├── module.py + ├── subpkg1/ + │   ├── __init__.py + │   ├── ... + │   └── module1.py + └── subpkg2/ + ├── __init__.py + ├── ... + └── module2.py + +This layout is very practical for using the REPL, but in some situations +it can be more error-prone (e.g. during tests or if you have a bunch +of folders or Python files hanging around your project root). + +To avoid confusion, file and folder names that are used by popular tools (or +that correspond to well-known conventions, such as distributing documentation +alongside the project code) are automatically filtered out in the case of +*flat-layout*: + +.. autoattribute:: setuptools.discovery.FlatLayoutPackageFinder.DEFAULT_EXCLUDE + +.. autoattribute:: setuptools.discovery.FlatLayoutModuleFinder.DEFAULT_EXCLUDE + +.. warning:: + If you are using auto-discovery with *flat-layout*, ``setuptools`` will + refuse to create :term:`distribution archives ` with + multiple top-level packages or modules. + + This is done to prevent common errors such as accidentally publishing code + not meant for distribution (e.g. maintenance-related scripts). + + Users that purposefully want to create multi-package distributions are + advised to use :ref:`custom-discovery` or the ``src-layout``. + +There is also a handy variation of the *flat-layout* for utilities/libraries +that can be implemented with a single Python file: + +single-module distribution +^^^^^^^^^^^^^^^^^^^^^^^^^^ + +A standalone module is placed directly under the project root, instead of +inside a package folder:: + + project_root_directory + ├── pyproject.toml # AND/OR setup.cfg, setup.py + ├── ... + └── single_file_lib.py + + +.. _custom-discovery: + +Custom discovery +================ + +If the automatic discovery does not work for you +(e.g., you want to *include* in the distribution top-level packages with +reserved names such as ``tasks``, ``example`` or ``docs``, or you want to +*exclude* nested packages that would be otherwise included), you can use +the provided tools for package discovery: + +.. tab:: setup.cfg + + .. code-block:: ini + + [options] + packages = find: + #or + packages = find_namespace: + +.. tab:: setup.py + + .. code-block:: python + + from setuptools import find_packages + # or + from setuptools import find_namespace_packages + +.. tab:: pyproject.toml (**BETA**) [#beta]_ + + .. code-block:: toml + + # ... + [tool.setuptools.packages] + find = {} # Scanning implicit namespaces is active by default + # OR + find = {namespaces = false} # Disable implicit namespaces + + +Finding simple packages +----------------------- +Let's start with the first tool. ``find:`` (``find_packages()``) takes a source +directory and two lists of package name patterns to exclude and include, and +then returns a list of ``str`` representing the packages it could find. To use +it, consider the following directory:: + + mypkg + ├── pyproject.toml # AND/OR setup.cfg, setup.py + └── src + ├── pkg1 + │   └── __init__.py + ├── pkg2 + │   └── __init__.py + ├── additional + │   └── __init__.py + └── pkg + └── namespace + └── __init__.py + +To have setuptools to automatically include packages found +in ``src`` that start with the name ``pkg`` and not ``additional``: + +.. tab:: setup.cfg + + .. code-block:: ini + + [options] + packages = find: + package_dir = + =src + + [options.packages.find] + where = src + include = pkg* + # alternatively: `exclude = additional*` + + .. note:: + ``pkg`` does not contain an ``__init__.py`` file, therefore + ``pkg.namespace`` is ignored by ``find:`` (see ``find_namespace:`` below). + +.. tab:: setup.py + + .. code-block:: python + + setup( + # ... + packages=find_packages( + where='src', + include=['pkg*'], # alternatively: `exclude=['additional*']` + ), + package_dir={"": "src"} + # ... + ) + + + .. note:: + ``pkg`` does not contain an ``__init__.py`` file, therefore + ``pkg.namespace`` is ignored by ``find_packages()`` + (see ``find_namespace_packages()`` below). + +.. tab:: pyproject.toml (**BETA**) [#beta]_ + + .. code-block:: toml + + [tool.setuptools.packages.find] + where = ["src"] + include = ["pkg*"] # alternatively: `exclude = ["additional*"]` + namespaces = false + + .. note:: + When using ``tool.setuptools.packages.find`` in ``pyproject.toml``, + setuptools will consider :pep:`implicit namespaces <420>` by default when + scanning your project directory. + To avoid ``pkg.namespace`` from being added to your package list + you can set ``namespaces = false``. This will prevent any folder + without an ``__init__.py`` file from being scanned. + +.. important:: + ``include`` and ``exclude`` accept strings representing :mod:`glob` patterns. + These patterns should match the **full** name of the Python module (as if it + was written in an ``import`` statement). + + For example if you have ``util`` pattern, it will match + ``util/__init__.py`` but not ``util/files/__init__.py``. + + The fact that the parent package is matched by the pattern will not dictate + if the submodule will be included or excluded from the distribution. + You will need to explicitly add a wildcard (e.g. ``util*``) + if you want the pattern to also match submodules. + +.. _Namespace Packages: + +Finding namespace packages +-------------------------- +``setuptools`` provides ``find_namespace:`` (``find_namespace_packages()``) +which behaves similarly to ``find:`` but works with namespace packages. + +Before diving in, it is important to have a good understanding of what +:pep:`namespace packages <420>` are. Here is a quick recap. + +When you have two packages organized as follows: + +.. code-block:: bash + + /Users/Desktop/timmins/foo/__init__.py + /Library/timmins/bar/__init__.py + +If both ``Desktop`` and ``Library`` are on your ``PYTHONPATH``, then a +namespace package called ``timmins`` will be created automatically for you when +you invoke the import mechanism, allowing you to accomplish the following: + +.. code-block:: pycon + + >>> import timmins.foo + >>> import timmins.bar + +as if there is only one ``timmins`` on your system. The two packages can then +be distributed separately and installed individually without affecting the +other one. + +Now, suppose you decide to package the ``foo`` part for distribution and start +by creating a project directory organized as follows:: + + foo + ├── pyproject.toml # AND/OR setup.cfg, setup.py + └── src + └── timmins + └── foo + └── __init__.py + +If you want the ``timmins.foo`` to be automatically included in the +distribution, then you will need to specify: + +.. tab:: setup.cfg + + .. code-block:: ini + + [options] + package_dir = + =src + packages = find_namespace: + + [options.packages.find] + where = src + + ``find:`` won't work because ``timmins`` doesn't contain ``__init__.py`` + directly, instead, you have to use ``find_namespace:``. + + You can think of ``find_namespace:`` as identical to ``find:`` except it + would count a directory as a package even if it doesn't contain ``__init__.py`` + file directly. + +.. tab:: setup.py + + .. code-block:: python + + setup( + # ... + packages=find_namespace_packages(where='src'), + package_dir={"": "src"} + # ... + ) + + When you use ``find_packages()``, all directories without an + ``__init__.py`` file will be disconsidered. + On the other hand, ``find_namespace_packages()`` will scan all + directories. + +.. tab:: pyproject.toml (**BETA**) [#beta]_ + + .. code-block:: toml + + [tool.setuptools.packages.find] + where = ["src"] + + When using ``tool.setuptools.packages.find`` in ``pyproject.toml``, + setuptools will consider :pep:`implicit namespaces <420>` by default when + scanning your project directory. + +After installing the package distribution, ``timmins.foo`` would become +available to your interpreter. + +.. warning:: + Please have in mind that ``find_namespace:`` (setup.cfg), + ``find_namespace_packages()`` (setup.py) and ``find`` (pyproject.toml) will + scan **all** folders that you have in your project directory if you use a + :ref:`flat-layout`. + + If used naïvely, this might result in unwanted files being added to your + final wheel. For example, with a project directory organized as follows:: + + foo + ├── docs + │ └── conf.py + ├── timmins + │ └── foo + │ └── __init__.py + └── tests + └── tests_foo + └── __init__.py + + final users will end up installing not only ``timmins.foo``, but also + ``docs`` and ``tests.tests_foo``. + + A simple way to fix this is to adopt the aforementioned :ref:`src-layout`, + or make sure to properly configure the ``include`` and/or ``exclude`` + accordingly. + +.. tip:: + After :ref:`building your package `, you can have a look if all + the files are correct (nothing missing or extra), by running the following + commands: + + .. code-block:: bash + + tar tf dist/*.tar.gz + unzip -l dist/*.whl + + This requires the ``tar`` and ``unzip`` to be installed in your OS. + On Windows you can also use a GUI program such as 7zip_. + + +Legacy Namespace Packages +========================= +The fact you can create namespace packages so effortlessly above is credited +to :pep:`420`. It used to be more +cumbersome to accomplish the same result. Historically, there were two methods +to create namespace packages. One is the ``pkg_resources`` style supported by +``setuptools`` and the other one being ``pkgutils`` style offered by +``pkgutils`` module in Python. Both are now considered *deprecated* despite the +fact they still linger in many existing packages. These two differ in many +subtle yet significant aspects and you can find out more on `Python packaging +user guide `_. + + +``pkg_resource`` style namespace package +---------------------------------------- +This is the method ``setuptools`` directly supports. Starting with the same +layout, there are two pieces you need to add to it. First, an ``__init__.py`` +file directly under your namespace package directory that contains the +following: + +.. code-block:: python + + __import__("pkg_resources").declare_namespace(__name__) + +And the ``namespace_packages`` keyword in your ``setup.cfg`` or ``setup.py``: + +.. tab:: setup.cfg + + .. code-block:: ini + + [options] + namespace_packages = timmins + +.. tab:: setup.py + + .. code-block:: python + + setup( + # ... + namespace_packages=['timmins'] + ) + +And your directory should look like this + +.. code-block:: bash + + foo + ├── pyproject.toml # AND/OR setup.cfg, setup.py + └── src + └── timmins + ├── __init__.py + └── foo + └── __init__.py + +Repeat the same for other packages and you can achieve the same result as +the previous section. + +``pkgutil`` style namespace package +----------------------------------- +This method is almost identical to the ``pkg_resource`` except that the +``namespace_packages`` declaration is omitted and the ``__init__.py`` +file contains the following: + +.. code-block:: python + + __path__ = __import__('pkgutil').extend_path(__path__, __name__) + +The project layout remains the same and ``pyproject.toml/setup.cfg`` remains the same. + + +---- + + +.. [#beta] + Support for adding build configuration options via the ``[tool.setuptools]`` + table in the ``pyproject.toml`` file is still in **beta** stage. + See :doc:`/userguide/pyproject_config`. +.. [#layout1] https://blog.ionelmc.ro/2014/05/25/python-packaging/#the-structure +.. [#layout2] https://blog.ionelmc.ro/2017/09/25/rehashing-the-src-layout/ + +.. _editable install: https://pip.pypa.io/en/stable/cli/pip_install/#editable-installs +.. _7zip: https://www.7-zip.org diff --git a/docs/source/userguide/pyproject_config.rst b/docs/source/userguide/pyproject_config.rst new file mode 100644 index 000000000..c97984ba0 --- /dev/null +++ b/docs/source/userguide/pyproject_config.rst @@ -0,0 +1,260 @@ +.. _pyproject.toml config: + +----------------------------------------------------- +Configuring setuptools using ``pyproject.toml`` files +----------------------------------------------------- + +.. note:: New in 61.0.0 + +.. important:: + If compatibility with legacy builds or versions of tools that don't support + certain packaging standards (e.g. :pep:`517` or :pep:`660`), a simple ``setup.py`` + script can be added to your project [#setupcfg-caveats]_ + (while keeping the configuration in ``pyproject.toml``): + + .. code-block:: python + + from setuptools import setup + + setup() + +Starting with :pep:`621`, the Python community selected ``pyproject.toml`` as +a standard way of specifying *project metadata*. +``Setuptools`` has adopted this standard and will use the information contained +in this file as an input in the build process. + +The example below illustrates how to write a ``pyproject.toml`` file that can +be used with ``setuptools``. It contains two TOML tables (identified by the +``[table-header]`` syntax): ``build-system`` and ``project``. +The ``build-system`` table is used to tell the build frontend (e.g. +:pypi:`build` or :pypi:`pip`) to use ``setuptools`` and any other plugins (e.g. +``setuptools-scm``) to build the package. +The ``project`` table contains metadata fields as described by +:doc:`PyPUG:specifications/declaring-project-metadata` guide. + +.. _example-pyproject-config: + +.. code-block:: toml + + [build-system] + requires = ["setuptools", "setuptools-scm"] + build-backend = "setuptools.build_meta" + + [project] + name = "my_package" + authors = [ + {name = "Josiah Carberry", email = "josiah_carberry@brown.edu"}, + ] + description = "My package description" + readme = "README.rst" + requires-python = ">=3.7" + keywords = ["one", "two"] + license = {text = "BSD-3-Clause"} + classifiers = [ + "Framework :: Django", + "Programming Language :: Python :: 3", + ] + dependencies = [ + "requests", + 'importlib-metadata; python_version<"3.8"', + ] + dynamic = ["version"] + + [project.optional-dependencies] + pdf = ["ReportLab>=1.2", "RXP"] + rest = ["docutils>=0.3", "pack ==1.1, ==1.3"] + + [project.scripts] + my-script = "my_package.module:function" + + # ... other project metadata fields as specified in: + # https://packaging.python.org/en/latest/specifications/declaring-project-metadata/ + +.. _setuptools-table: + +Setuptools-specific configuration +================================= + +.. warning:: + Support for declaring configurations not standardized by :pep:`621` + (i.e. the ``[tool.setuptools]`` table), + is still in **beta** stage and might change in future releases. + +While the standard ``project`` table in the ``pyproject.toml`` file covers most +of the metadata used during the packaging process, there are still some +``setuptools``-specific configurations that can be set by users that require +customization. +These configurations are completely optional and probably can be skipped when +creating simple packages. +They are equivalent to the :doc:`/references/keywords` used by the ``setup.py`` +file, and can be set via the ``tool.setuptools`` table: + +========================= =========================== ========================= +Key Value Type (TOML) Notes +========================= =========================== ========================= +``platforms`` array +``zip-safe`` boolean If not specified, ``setuptools`` will try to guess + a reasonable default for the package +``eager-resources`` array +``py-modules`` array See tip below +``packages`` array or ``find`` directive See tip below +``package-dir`` table/inline-table Used when explicitly listing ``packages`` +``namespace-packages`` array **Deprecated** - Use implicit namespaces instead (:pep:`420`) +``package-data`` table/inline-table See :doc:`/userguide/datafiles` +``include-package-data`` boolean ``True`` by default +``exclude-package-data`` table/inline-table +``license-files`` array of glob patterns **Provisional** - likely to change with :pep:`639` + (by default: ``['LICEN[CS]E*', 'COPYING*', 'NOTICE*', 'AUTHORS*']``) +``data-files`` table/inline-table **Discouraged** - check :doc:`/userguide/datafiles` +``script-files`` array **Deprecated** - equivalent to the ``script`` keyword in ``setup.py`` + (should be avoided in favour of ``project.scripts``) +``provides`` array **Ignored by pip** +``obsoletes`` array **Ignored by pip** +========================= =========================== ========================= + +.. note:: + The `TOML value types`_ ``array`` and ``table/inline-table`` are roughly + equivalent to the Python's :obj:`list` and :obj:`dict` data types, respectively. + +Please note that some of these configurations are deprecated or at least +discouraged, but they are made available to ensure portability. +New packages should avoid relying on deprecated/discouraged fields, and +existing packages should consider alternatives. + +.. tip:: + When both ``py-modules`` and ``packages`` are left unspecified, + ``setuptools`` will attempt to perform :ref:`auto-discovery`, which should + cover most popular project directory organization techniques, such as the + :ref:`src-layout` and the :ref:`flat-layout`. + + However if your project does not follow these conventional layouts + (e.g. you want to use a ``flat-layout`` but at the same time have custom + directories at the root of your project), you might need to use the ``find`` + directive [#directives]_ as shown below: + + .. code-block:: toml + + [tool.setuptools.packages.find] + where = ["src"] # list of folders that contain the packages (["."] by default) + include = ["my_package*"] # package names should match these glob patterns (["*"] by default) + exclude = ["my_package.tests*"] # exclude packages matching these glob patterns (empty by default) + namespaces = false # to disable scanning PEP 420 namespaces (true by default) + + Note that the glob patterns in the example above need to be matched + by the **entire** package name. This means that if you specify ``exclude = ["tests"]``, + modules like ``tests.my_package.test1`` will still be included in the distribution + (to remove them, add a wildcard to the end of the pattern: ``"tests*"``). + + Alternatively, you can explicitly list the packages in modules: + + .. code-block:: toml + + [tool.setuptools] + packages = ["my_package"] + + +.. _dynamic-pyproject-config: + +Dynamic Metadata +================ + +Note that in the first example of this page we use ``dynamic`` to identify +which metadata fields are dynamically computed during the build by either +``setuptools`` itself or the plugins installed via ``build-system.requires`` +(e.g. ``setuptools-scm`` is capable of deriving the current project version +directly from the ``git`` :wiki:`version control` system). + +Currently the following fields can be listed as dynamic: ``version``, +``classifiers``, ``description``, ``entry-points``, ``scripts``, +``gui-scripts`` and ``readme``. +When these fields are expected to be provided by ``setuptools`` a +corresponding entry is required in the ``tool.setuptools.dynamic`` table +[#entry-points]_. For example: + +.. code-block:: toml + + # ... + [project] + name = "my_package" + dynamic = ["version", "readme"] + # ... + [tool.setuptools.dynamic] + version = {attr = "my_package.VERSION"} + readme = {file = ["README.rst", "USAGE.rst"]} + +In the ``dynamic`` table, the ``attr`` directive [#directives]_ will read an +attribute from the given module [#attr]_, while ``file`` will read the contents +of all given files and concatenate them in a single string. + +========================== =================== ================================================================================================= +Key Directive Notes +========================== =================== ================================================================================================= +``version`` ``attr``, ``file`` +``readme`` ``file`` Here you can also set ``"content-type"``: + + ``readme = {file = ["README", "USAGE"], content-type = "text/plain"}`` + + If ``content-type`` is not given, ``"text/x-rst"`` is used by default. +``description`` ``file`` One-line text (no line breaks) +``classifiers`` ``file`` Multi-line text with one classifier per line +``entry-points`` ``file`` INI format following :doc:`PyPUG:specifications/entry-points` + (``console_scripts`` and ``gui_scripts`` can be included) +``dependencies`` ``file`` *subset* of the ``requirements.txt`` format + (``#`` comments and blank lines excluded) **BETA** +``optional-dependencies`` ``file`` *subset* of the ``requirements.txt`` format per group + (``#`` comments and blank lines excluded) **BETA** +========================== =================== ================================================================================================= + +Supporting ``file`` for dependencies is meant for a convenience for packaging +applications with possibly strictly versioned dependencies. + +Library packagers are discouraged from using overly strict (or "locked") +dependency versions in their ``dependencies`` and ``optional-dependencies``. + +Currently, when specifying ``optional-dependencies`` dynamically, all of the groups +must be specified dynamically; one can not specify some of them statically and +some of them dynamically. + +Also note that the file format for specifying dependencies resembles a ``requirements.txt`` file, +however please keep in mind that all non-comment lines must conform with :pep:`508` +(``pip``-specify syntaxes, e.g. ``-c/-r/-e`` flags, are not supported). + + +.. note:: + If you are using an old version of ``setuptools``, you might need to ensure + that all files referenced by the ``file`` directive are included in the ``sdist`` + (you can do that via ``MANIFEST.in`` or using plugins such as ``setuptools-scm``, + please have a look on :doc:`/userguide/miscellaneous` for more information). + + .. versionchanged:: 66.1.0 + Newer versions of ``setuptools`` will automatically add these files to the ``sdist``. + +---- + +.. rubric:: Notes + +.. [#setupcfg-caveats] ``pip`` may allow editable install only with ``pyproject.toml`` + and ``setup.cfg``. However, this behavior may not be consistent over various ``pip`` + versions and other packaging-related tools + (``setup.py`` is more reliable on those scenarios). + +.. [#entry-points] Dynamic ``scripts`` and ``gui-scripts`` are a special case. + When resolving these metadata keys, ``setuptools`` will look for + ``tool.setuptool.dynamic.entry-points``, and use the values of the + ``console_scripts`` and ``gui_scripts`` :doc:`entry-point groups + `. + +.. [#directives] In the context of this document, *directives* are special TOML + values that are interpreted differently by ``setuptools`` (usually triggering an + associated function). Most of the times they correspond to a special TOML table + (or inline-table) with a single top-level key. + For example, you can have the ``{find = {where = ["src"], exclude=["tests*"]}}`` + directive for ``tool.setuptools.packages``, or ``{attr = "mymodule.attr"}`` + directive for ``tool.setuptools.dynamic.version``. + +.. [#attr] ``attr`` is meant to be used when the module attribute is statically + specified (e.g. as a string, list or tuple). As a rule of thumb, the + attribute should be able to be parsed with :func:`ast.literal_eval`, and + should not be modified or re-assigned. + +.. _TOML value types: https://toml.io/en/v1.0.0 diff --git a/docs/source/userguide/quickstart.rst b/docs/source/userguide/quickstart.rst new file mode 100644 index 000000000..ad1d6d9ee --- /dev/null +++ b/docs/source/userguide/quickstart.rst @@ -0,0 +1,134 @@ +========== +Quickstart +========== + +Installation +============ + + +SDK is available on PyPI: + +.. code-block:: bash + + pip install superannotate + +The package officially supports Python 3.6+ and was tested under Linux and +Windows (`Anaconda `_) platforms. + +For certain video related functions to work, ffmpeg package needs to be installed. +It can be installed on Ubuntu with: + +.. code-block:: bash + + sudo apt-get install ffmpeg + +For Windows and Mac OS based installations to use :py:obj:`benchmark` and :py:obj:`consensus` +functions you might also need to install beforehand :py:obj:`shapely` package, +which we found to work properly only under Anaconda distribution, with: + +.. code-block:: bash + + conda install shapely + + +---------- + +Config file +____________________ + +To use the SDK, a config file with team specific authentication token needs to be +created. The token is available to team admins on +team setting page at https://app.superannotate.com/team. + +Default location config file +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +To generate a default location (:file:`~/.superannotate/config.json`) config file:: + + ~/(home directory) + └── .superannotate + ├── config.json + + +:ref:`CLI init ` can be used: + +.. code-block:: bash + + superannotatecli init + +Custom config file +~~~~~~~~~~~~~~~~~~~~~~ + +To create a custom config file a new JSON file with key "token" can be created: + +.. code-block:: json + + { + "token" : "" + } + +---------- + +Initialization and authorization +________________________________ + +Include the package in your Python code: + +.. code-block:: python + + from superannotate import SAClient + +SDK is ready to be used if default location config file was created using +the :ref:`CLI init `. Otherwise to authenticate SDK with the :ref:`custom config file `: + +.. code-block:: python + + sa = SAClient(config_path="") + + + +.. _basic-use: + +Basic Use +========= + +Creating a project +----------------- + +To create a new "Vector" project with name "Example Project 1" and description +"test": + +.. code-block:: python + + project = "Example Project 1" + + sa.create_project(project, "test", "Vector") + + +Uploading images to project +----------------- + + + +To upload all images with extensions "jpg" or "png" from the +:file:`""` to the project "Example Project 1": + +.. code-block:: python + + sa.upload_images_from_folder_to_project(project, "") + +See the full argument options for +:py:func:`upload_images_from_folder_to_project` :ref:`here `. + +For full list of available functions on projects, see :ref:`ref_projects`. + +.. note:: + + Python SDK functions that accept project argument will accept both project + name or :ref:`project metadata ` (returned either by + :ref:`get_project_metadata ` or + :ref:`search_projects ` with argument :py:obj:`return_metadata=True`). + If project name is used it should be unique in team's project list. Using project metadata will give + performance improvement. + + diff --git a/requirements_extra.txt b/requirements_extra.txt index 05bc9b5eb..38867c302 100644 --- a/requirements_extra.txt +++ b/requirements_extra.txt @@ -6,4 +6,8 @@ pytest-xdist==2.3.0 pytest-parallel==0.1.0 pytest-rerunfailures==10.2 sphinx_rtd_theme==1.0.0 +sphinx_inline_tabs==2022.1.2b11 +jaraco.tidelift==1.5.0 +sphinx-notfound-page==0.8.3 +furo==2022.12.7 pytest-cov diff --git a/tox.ini b/tox.ini index 981923d58..5fee427d6 100644 --- a/tox.ini +++ b/tox.ini @@ -1,7 +1,7 @@ [tox] isolated_build = True tox_pyenv_fallback = False -envlist = py37,py310 +envlist = py38,py310 [testenv] usedevelop = true @@ -58,3 +58,11 @@ basepython = python3 commands = pre-commit run --all-files + +[testenv:type] +description = run type checks +deps = + mypy>=0.991 +commands = + mypy {posargs:src tests} + From f769f9c66b698fa3593ca78abf48f075d39bf335 Mon Sep 17 00:00:00 2001 From: Vaghinak Basentsyan Date: Thu, 9 Feb 2023 17:05:42 +0400 Subject: [PATCH 2/2] Docs update --- .../api_reference/api_annotation_class.rst | 10 + docs/source/api_reference/api_client.rst | 155 +---- .../api_reference/api_custom_metadata.rst | 9 + docs/source/api_reference/api_export.rst | 10 + docs/source/api_reference/api_image.rst | 11 + docs/source/api_reference/api_item.rst | 18 + docs/source/api_reference/api_metadata.rst | 2 +- .../api_reference/api_neural_network.rst | 8 + docs/source/api_reference/api_project.rst | 38 ++ docs/source/api_reference/api_subset.rst | 7 + docs/source/api_reference/api_team.rst | 9 + docs/source/api_reference/cli_client.rst | 10 +- docs/source/api_reference/index.rst | 13 +- docs/source/attribute_distribution.png | Bin 66162 -> 0 bytes docs/source/benchmark_projects_box.png | Bin 25861 -> 0 bytes docs/source/class_distribution.png | Bin 48652 -> 0 bytes docs/source/{cli.rst => cli_client.rst} | 28 +- docs/source/conf.py | 94 +-- docs/source/{ => images}/sa_logo.png | Bin docs/source/index.rst | 11 +- docs/source/sa_server.rst | 121 ++++ docs/source/superannotate.sdk.rst | 421 ------------- docs/source/tutorial.sdk.rst | 428 ------------- docs/source/userguide/datafiles.rst | 555 ----------------- docs/source/userguide/declarative_config.rst | 326 ---------- .../userguide/dependency_management.rst | 422 ------------- docs/source/userguide/development_mode.rst | 271 -------- docs/source/userguide/distribution.rst | 198 ------ docs/source/userguide/entry_point.rst | 571 ----------------- docs/source/userguide/ext_modules.rst | 172 ----- docs/source/userguide/extension.rst | 308 --------- .../images}/benchmark_annotators_box.png | Bin .../images}/benchmark_scatter.png | Bin .../images}/consensus_annotators_box.png | Bin .../images}/consensus_dataframe.png | Bin .../images}/consensus_projects_box.png | Bin .../images}/consensus_scatter.png | Bin .../{ => userguide/images}/pandas_df.png | Bin docs/source/userguide/index.rst | 40 +- docs/source/userguide/miscellaneous.rst | 100 --- docs/source/userguide/package_discovery.rst | 589 ------------------ docs/source/userguide/pyproject_config.rst | 260 -------- docs/source/userguide/quickstart.rst | 50 +- docs/source/userguide/setup_project.rst | 160 +++++ docs/source/userguide/utilities.rst | 197 ++++++ .../lib/app/interface/sdk_interface.py | 3 +- src/superannotate/lib/app/server/core.py | 2 - 47 files changed, 704 insertions(+), 4923 deletions(-) create mode 100644 docs/source/api_reference/api_annotation_class.rst create mode 100644 docs/source/api_reference/api_custom_metadata.rst create mode 100644 docs/source/api_reference/api_export.rst create mode 100644 docs/source/api_reference/api_image.rst create mode 100644 docs/source/api_reference/api_item.rst create mode 100644 docs/source/api_reference/api_neural_network.rst create mode 100644 docs/source/api_reference/api_project.rst create mode 100644 docs/source/api_reference/api_subset.rst create mode 100644 docs/source/api_reference/api_team.rst delete mode 100644 docs/source/attribute_distribution.png delete mode 100644 docs/source/benchmark_projects_box.png delete mode 100644 docs/source/class_distribution.png rename docs/source/{cli.rst => cli_client.rst} (94%) rename docs/source/{ => images}/sa_logo.png (100%) create mode 100644 docs/source/sa_server.rst delete mode 100644 docs/source/superannotate.sdk.rst delete mode 100644 docs/source/tutorial.sdk.rst delete mode 100644 docs/source/userguide/datafiles.rst delete mode 100644 docs/source/userguide/declarative_config.rst delete mode 100644 docs/source/userguide/dependency_management.rst delete mode 100644 docs/source/userguide/development_mode.rst delete mode 100644 docs/source/userguide/distribution.rst delete mode 100644 docs/source/userguide/entry_point.rst delete mode 100644 docs/source/userguide/ext_modules.rst delete mode 100644 docs/source/userguide/extension.rst rename docs/source/{ => userguide/images}/benchmark_annotators_box.png (100%) rename docs/source/{ => userguide/images}/benchmark_scatter.png (100%) rename docs/source/{ => userguide/images}/consensus_annotators_box.png (100%) rename docs/source/{ => userguide/images}/consensus_dataframe.png (100%) rename docs/source/{ => userguide/images}/consensus_projects_box.png (100%) rename docs/source/{ => userguide/images}/consensus_scatter.png (100%) rename docs/source/{ => userguide/images}/pandas_df.png (100%) delete mode 100644 docs/source/userguide/miscellaneous.rst delete mode 100644 docs/source/userguide/package_discovery.rst delete mode 100644 docs/source/userguide/pyproject_config.rst create mode 100644 docs/source/userguide/setup_project.rst create mode 100644 docs/source/userguide/utilities.rst diff --git a/docs/source/api_reference/api_annotation_class.rst b/docs/source/api_reference/api_annotation_class.rst new file mode 100644 index 000000000..81232daa8 --- /dev/null +++ b/docs/source/api_reference/api_annotation_class.rst @@ -0,0 +1,10 @@ +========== +Annotation Classes +========== + +.. automethod:: superannotate.SAClient.create_annotation_class +.. _ref_create_annotation_classes_from_classes_json: +.. automethod:: superannotate.SAClient.create_annotation_classes_from_classes_json +.. automethod:: superannotate.SAClient.search_annotation_classes +.. automethod:: superannotate.SAClient.download_annotation_classes_json +.. automethod:: superannotate.SAClient.delete_annotation_class diff --git a/docs/source/api_reference/api_client.rst b/docs/source/api_reference/api_client.rst index 69a88c8dc..20569f5d2 100644 --- a/docs/source/api_reference/api_client.rst +++ b/docs/source/api_reference/api_client.rst @@ -2,141 +2,20 @@ SAClient interface ========== -Instantiation and authentication -_________________________________ - -.. autoclass:: superannotate.SAClient - - -Projects -________ - -.. _ref_search_projects: -.. automethod:: superannotate.SAClient.search_projects -.. automethod:: superannotate.SAClient.create_project -.. automethod:: superannotate.SAClient.create_project_from_metadata -.. automethod:: superannotate.SAClient.clone_project -.. automethod:: superannotate.SAClient.delete_project -.. automethod:: superannotate.SAClient.rename_project -.. _ref_get_project_metadata: -.. automethod:: superannotate.SAClient.get_project_by_id -.. automethod:: superannotate.SAClient.get_project_metadata -.. automethod:: superannotate.SAClient.get_project_image_count -.. automethod:: superannotate.SAClient.search_folders -.. automethod:: superannotate.SAClient.assign_folder -.. automethod:: superannotate.SAClient.unassign_folder -.. automethod:: superannotate.SAClient.get_folder_by_id -.. automethod:: superannotate.SAClient.get_folder_metadata -.. automethod:: superannotate.SAClient.create_folder -.. automethod:: superannotate.SAClient.delete_folders -.. automethod:: superannotate.SAClient.upload_images_to_project -.. automethod:: superannotate.SAClient.attach_items_from_integrated_storage -.. automethod:: superannotate.SAClient.upload_image_to_project -.. automethod:: superannotate.SAClient.upload_annotations -.. automethod:: superannotate.SAClient.delete_annotations -.. _ref_upload_images_from_folder_to_project: -.. automethod:: superannotate.SAClient.upload_images_from_folder_to_project -.. automethod:: superannotate.SAClient.upload_video_to_project -.. automethod:: superannotate.SAClient.upload_videos_from_folder_to_project -.. _ref_upload_annotations_from_folder_to_project: -.. automethod:: superannotate.SAClient.upload_annotations_from_folder_to_project -.. automethod:: superannotate.SAClient.add_contributors_to_project -.. automethod:: superannotate.SAClient.get_project_settings -.. automethod:: superannotate.SAClient.set_project_default_image_quality_in_editor -.. automethod:: superannotate.SAClient.get_project_workflow -.. automethod:: superannotate.SAClient.set_project_workflow - ----------- - -Exports -_______ - -.. automethod:: superannotate.SAClient.prepare_export -.. automethod:: superannotate.SAClient.get_annotations -.. automethod:: superannotate.SAClient.get_annotations_per_frame -.. _ref_download_export: -.. automethod:: superannotate.SAClient.download_export -.. automethod:: superannotate.SAClient.get_exports - ----------- - -Items -______ - -.. automethod:: superannotate.SAClient.query -.. automethod:: superannotate.SAClient.get_item_by_id -.. automethod:: superannotate.SAClient.search_items -.. automethod:: superannotate.SAClient.download_annotations -.. automethod:: superannotate.SAClient.attach_items -.. automethod:: superannotate.SAClient.copy_items -.. automethod:: superannotate.SAClient.move_items -.. automethod:: superannotate.SAClient.delete_items -.. automethod:: superannotate.SAClient.assign_items -.. automethod:: superannotate.SAClient.unassign_items -.. automethod:: superannotate.SAClient.get_item_metadata -.. automethod:: superannotate.SAClient.set_annotation_statuses -.. automethod:: superannotate.SAClient.set_approval_statuses -.. automethod:: superannotate.SAClient.set_approval - ----------- - -Custom Metadata -______ - -.. automethod:: superannotate.SAClient.create_custom_fields -.. automethod:: superannotate.SAClient.get_custom_fields -.. automethod:: superannotate.SAClient.delete_custom_fields -.. automethod:: superannotate.SAClient.upload_custom_values -.. automethod:: superannotate.SAClient.delete_custom_values - ----------- - -Subsets -______ - -.. automethod:: superannotate.SAClient.get_subsets -.. automethod:: superannotate.SAClient.add_items_to_subset - ----------- - -Images -______ - - -.. _ref_search_images: -.. automethod:: superannotate.SAClient.download_image -.. automethod:: superannotate.SAClient.download_image_annotations -.. automethod:: superannotate.SAClient.upload_image_annotations -.. automethod:: superannotate.SAClient.pin_image -.. automethod:: superannotate.SAClient.upload_priority_scores - ----------- - -Annotation Classes -__________________ - -.. automethod:: superannotate.SAClient.create_annotation_class -.. _ref_create_annotation_classes_from_classes_json: -.. automethod:: superannotate.SAClient.create_annotation_classes_from_classes_json -.. automethod:: superannotate.SAClient.search_annotation_classes -.. automethod:: superannotate.SAClient.download_annotation_classes_json -.. automethod:: superannotate.SAClient.delete_annotation_class - ----------- - -Team -_________________ - -.. automethod:: superannotate.SAClient.get_team_metadata -.. automethod:: superannotate.SAClient.get_integrations -.. automethod:: superannotate.SAClient.invite_contributors_to_team -.. automethod:: superannotate.SAClient.search_team_contributors - ----------- - -Neural Network -_______________ - -.. automethod:: superannotate.SAClient.download_model -.. automethod:: superannotate.SAClient.run_prediction -.. automethod:: superannotate.SAClient.search_models \ No newline at end of file +Contents +======== + +.. toctree:: + :maxdepth: 8 + + api_project + api_item + api_export + api_custom_metadata + api_subset + api_image + api_annotation_class + api_team + api_neural_network + +--- diff --git a/docs/source/api_reference/api_custom_metadata.rst b/docs/source/api_reference/api_custom_metadata.rst new file mode 100644 index 000000000..475421612 --- /dev/null +++ b/docs/source/api_reference/api_custom_metadata.rst @@ -0,0 +1,9 @@ +========== +Custom Metadata +========== + +.. automethod:: superannotate.SAClient.create_custom_fields +.. automethod:: superannotate.SAClient.get_custom_fields +.. automethod:: superannotate.SAClient.delete_custom_fields +.. automethod:: superannotate.SAClient.upload_custom_values +.. automethod:: superannotate.SAClient.delete_custom_values diff --git a/docs/source/api_reference/api_export.rst b/docs/source/api_reference/api_export.rst new file mode 100644 index 000000000..e769107f0 --- /dev/null +++ b/docs/source/api_reference/api_export.rst @@ -0,0 +1,10 @@ +========== +Exports +========== + +.. automethod:: superannotate.SAClient.prepare_export +.. automethod:: superannotate.SAClient.get_annotations +.. automethod:: superannotate.SAClient.get_annotations_per_frame +.. _ref_download_export: +.. automethod:: superannotate.SAClient.download_export +.. automethod:: superannotate.SAClient.get_exports diff --git a/docs/source/api_reference/api_image.rst b/docs/source/api_reference/api_image.rst new file mode 100644 index 000000000..577de31f1 --- /dev/null +++ b/docs/source/api_reference/api_image.rst @@ -0,0 +1,11 @@ +========== +Images +========== + + +.. _ref_search_images: +.. automethod:: superannotate.SAClient.download_image +.. automethod:: superannotate.SAClient.download_image_annotations +.. automethod:: superannotate.SAClient.upload_image_annotations +.. automethod:: superannotate.SAClient.pin_image +.. automethod:: superannotate.SAClient.upload_priority_scores \ No newline at end of file diff --git a/docs/source/api_reference/api_item.rst b/docs/source/api_reference/api_item.rst new file mode 100644 index 000000000..1b4645771 --- /dev/null +++ b/docs/source/api_reference/api_item.rst @@ -0,0 +1,18 @@ +========== +Items +========== + + +.. automethod:: superannotate.SAClient.query +.. automethod:: superannotate.SAClient.get_item_by_id +.. automethod:: superannotate.SAClient.search_items +.. automethod:: superannotate.SAClient.download_annotations +.. automethod:: superannotate.SAClient.attach_items +.. automethod:: superannotate.SAClient.copy_items +.. automethod:: superannotate.SAClient.move_items +.. automethod:: superannotate.SAClient.delete_items +.. automethod:: superannotate.SAClient.assign_items +.. automethod:: superannotate.SAClient.unassign_items +.. automethod:: superannotate.SAClient.get_item_metadata +.. automethod:: superannotate.SAClient.set_annotation_statuses +.. automethod:: superannotate.SAClient.set_approval_statuses diff --git a/docs/source/api_reference/api_metadata.rst b/docs/source/api_reference/api_metadata.rst index 792e76d93..a7aba3382 100644 --- a/docs/source/api_reference/api_metadata.rst +++ b/docs/source/api_reference/api_metadata.rst @@ -4,7 +4,7 @@ Remote metadata reference Projects metadata _________________ - +.. _ref_metadata: Project metadata example: .. code-block:: python diff --git a/docs/source/api_reference/api_neural_network.rst b/docs/source/api_reference/api_neural_network.rst new file mode 100644 index 000000000..3b5ebc49a --- /dev/null +++ b/docs/source/api_reference/api_neural_network.rst @@ -0,0 +1,8 @@ +========== +Neural Network +========== + + +.. automethod:: superannotate.SAClient.download_model +.. automethod:: superannotate.SAClient.run_prediction +.. automethod:: superannotate.SAClient.search_models \ No newline at end of file diff --git a/docs/source/api_reference/api_project.rst b/docs/source/api_reference/api_project.rst new file mode 100644 index 000000000..8ef94a830 --- /dev/null +++ b/docs/source/api_reference/api_project.rst @@ -0,0 +1,38 @@ +========== +Projects +========== +.. _ref_projects: +.. _ref_search_projects: +.. automethod:: superannotate.SAClient.search_projects +.. automethod:: superannotate.SAClient.create_project +.. automethod:: superannotate.SAClient.create_project_from_metadata +.. automethod:: superannotate.SAClient.clone_project +.. automethod:: superannotate.SAClient.delete_project +.. automethod:: superannotate.SAClient.rename_project +.. _ref_get_project_metadata: +.. automethod:: superannotate.SAClient.get_project_by_id +.. automethod:: superannotate.SAClient.get_project_metadata +.. automethod:: superannotate.SAClient.get_project_image_count +.. automethod:: superannotate.SAClient.search_folders +.. automethod:: superannotate.SAClient.assign_folder +.. automethod:: superannotate.SAClient.unassign_folder +.. automethod:: superannotate.SAClient.get_folder_by_id +.. automethod:: superannotate.SAClient.get_folder_metadata +.. automethod:: superannotate.SAClient.create_folder +.. automethod:: superannotate.SAClient.delete_folders +.. automethod:: superannotate.SAClient.upload_images_to_project +.. automethod:: superannotate.SAClient.attach_items_from_integrated_storage +.. automethod:: superannotate.SAClient.upload_image_to_project +.. automethod:: superannotate.SAClient.upload_annotations +.. automethod:: superannotate.SAClient.delete_annotations +.. _ref_upload_images_from_folder_to_project: +.. automethod:: superannotate.SAClient.upload_images_from_folder_to_project +.. automethod:: superannotate.SAClient.upload_video_to_project +.. automethod:: superannotate.SAClient.upload_videos_from_folder_to_project +.. _ref_upload_annotations_from_folder_to_project: +.. automethod:: superannotate.SAClient.upload_annotations_from_folder_to_project +.. automethod:: superannotate.SAClient.add_contributors_to_project +.. automethod:: superannotate.SAClient.get_project_settings +.. automethod:: superannotate.SAClient.set_project_default_image_quality_in_editor +.. automethod:: superannotate.SAClient.get_project_workflow +.. automethod:: superannotate.SAClient.set_project_workflow diff --git a/docs/source/api_reference/api_subset.rst b/docs/source/api_reference/api_subset.rst new file mode 100644 index 000000000..6b61e51a0 --- /dev/null +++ b/docs/source/api_reference/api_subset.rst @@ -0,0 +1,7 @@ +========== +Subsets +========== + + +.. automethod:: superannotate.SAClient.get_subsets +.. automethod:: superannotate.SAClient.add_items_to_subset \ No newline at end of file diff --git a/docs/source/api_reference/api_team.rst b/docs/source/api_reference/api_team.rst new file mode 100644 index 000000000..889fe856a --- /dev/null +++ b/docs/source/api_reference/api_team.rst @@ -0,0 +1,9 @@ +========== +Team +========== + + +.. automethod:: superannotate.SAClient.get_team_metadata +.. automethod:: superannotate.SAClient.get_integrations +.. automethod:: superannotate.SAClient.invite_contributors_to_team +.. automethod:: superannotate.SAClient.search_team_contributors \ No newline at end of file diff --git a/docs/source/api_reference/cli_client.rst b/docs/source/api_reference/cli_client.rst index b668c2ae0..20e7a2cb1 100644 --- a/docs/source/api_reference/cli_client.rst +++ b/docs/source/api_reference/cli_client.rst @@ -1,7 +1,9 @@ .. _ref_cli: -========== + +======================================== CLI Reference -========== +======================================== + With SuperAnnotate CLI, basic tasks can be accomplished using shell commands: @@ -10,13 +12,13 @@ With SuperAnnotate CLI, basic tasks can be accomplished using shell commands: superannotatecli <--arg1 val1> <--arg2 val2> [--optional_arg3 val3] [--optional_arg4] ... To use the CLI a command line initialization step should be performed after the -:ref:`installation `: + +:ref:`installation `: .. code-block:: bash superannotatecli init ----------- Available commands diff --git a/docs/source/api_reference/index.rst b/docs/source/api_reference/index.rst index 5bd4dc170..30dcc8766 100644 --- a/docs/source/api_reference/index.rst +++ b/docs/source/api_reference/index.rst @@ -1,26 +1,15 @@ ================================================== API Reference ================================================== -BLA BLA Contents ======== .. toctree:: - :maxdepth: 1 + :maxdepth: 2 api_client - cli_client api_metadata helpers - --- - -.. rubric:: Notes - -.. [#package-overload] - A :term:`Distribution Package` is also referred in the Python community simply as "package" - Unfortunately, this jargon might be a bit confusing for new users because the term package - can also to refer any :term:`directory ` (or sub directory) used to organize - :term:`modules ` and auxiliary files. diff --git a/docs/source/attribute_distribution.png b/docs/source/attribute_distribution.png deleted file mode 100644 index a5c0bf1f0858bdf911fa840d19c3dde8f2ba91eb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 66162 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

yRuXcvDa& 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 diff --git a/docs/source/benchmark_projects_box.png b/docs/source/benchmark_projects_box.png deleted file mode 100644 index bdc60544059124b26b98dda7d76af319d17bf21e..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 25861 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 A^#A|> diff --git a/docs/source/class_distribution.png b/docs/source/class_distribution.png deleted file mode 100644 index 6036e0628e07362ba63e8bae0dd951e1a1e59a80..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 48652 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

vyEa7KYq*F*`lLBy#W7fI?g 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 bH9WD*!geu=H=q44u$jKLu~voGY}#j diff --git a/docs/source/cli.rst b/docs/source/cli_client.rst similarity index 94% rename from docs/source/cli.rst rename to docs/source/cli_client.rst index c2f77e8f5..357b2400d 100644 --- a/docs/source/cli.rst +++ b/docs/source/cli_client.rst @@ -1,7 +1,8 @@ .. _ref_cli: +========== CLI Reference -====================================== +========== With SuperAnnotate CLI, basic tasks can be accomplished using shell commands: @@ -10,7 +11,8 @@ With SuperAnnotate CLI, basic tasks can be accomplished using shell commands: superannotatecli <--arg1 val1> <--arg2 val2> [--optional_arg3 val3] [--optional_arg4] ... To use the CLI a command line initialization step should be performed after the -:ref:`installation `: + +:ref:`installation <_ref_quickstart>`: .. code-block:: bash @@ -19,7 +21,7 @@ To use the CLI a command line initialization step should be performed after the ---------- -Available commands +Available commands ________________________ @@ -116,7 +118,7 @@ To upload videos from folder to project use: .. code-block:: bash - superannotatecli upload-videos --project --folder + superannotatecli upload-videos --project --folder [--recursive] [--extensions mp4,avi,mov,webm,flv,mpg,ogg] [--target-fps ] [--start-time ] [--end-time ] @@ -133,7 +135,7 @@ If not specified all frames will be uploaded. *start-time* specifies time (in seconds) from which to start extracting frames, default is 0.0. -*end-time* specifies time (in seconds) up to which to extract frames. +*end-time* specifies time (in seconds) up to which to extract frames. If it is not specified, then up to end is assumed. ---------- @@ -147,8 +149,8 @@ To upload preannotations from folder to project use: .. code-block:: bash - superannotatecli upload-preannotations --project --folder - [--format "COCO" or "SuperAnnotate"] + superannotatecli upload-preannotations --project --folder + [--format "COCO" or "SuperAnnotate"] [--dataset-name ""] [--task "] @@ -160,7 +162,7 @@ Only when COCO format is specified *dataset-name* and *task* arguments are requi *dataset-name* specifies JSON filename (without extension) in . -*task* specifies the COCO task for conversion. Please see +*task* specifies the COCO task for conversion. Please see :ref:`import_annotation_format ` for more details. @@ -175,8 +177,8 @@ To upload annotations from folder to project use: .. code-block:: bash - superannotatecli upload-annotations --project --folder - [--format "COCO" or "SuperAnnotate"] + superannotatecli upload-annotations --project --folder + [--format "COCO" or "SuperAnnotate"] [--dataset-name ""] [--task "] @@ -187,7 +189,7 @@ Only when COCO format is specified *dataset-name* and *task* arguments are requi *dataset-name* specifies JSON filename (without extension) in . -*task* specifies the COCO task for conversion. Please see +*task* specifies the COCO task for conversion. Please see :ref:`import_annotation_format ` for more details. ---------- @@ -201,9 +203,9 @@ To export project .. code-block:: bash - superannotatecli export-project --project --folder + superannotatecli export-project --project --folder [--include-fuse] - [--disable-extract-zip-contents] + [--disable-extract-zip-contents] [--annotation-statuses ] ---------- diff --git a/docs/source/conf.py b/docs/source/conf.py index 1fe82bff2..f0b48cacc 100644 --- a/docs/source/conf.py +++ b/docs/source/conf.py @@ -1,15 +1,3 @@ -# Configuration file for the Sphinx documentation builder. -# -# This file only contains a selection of the most common options. For a full -# list see the documentation: -# https://www.sphinx-doc.org/en/master/usage/configuration.html - -# -- Path setup -------------------------------------------------------------- - -# If extensions (or modules to document with autodoc) are in another directory, -# add these directories to sys.path here. If the directory is relative to the -# documentation root, use os.path.abspath to make it absolute, like shown here. -# import os import sys @@ -36,10 +24,25 @@ extensions += ['sphinx_inline_tabs'] -# extensions += ['pip install jaraco.tidelift'] +extensions += ['jaraco.tidelift'] extensions += ['notfound.extension'] + +exclude_patterns = [] + +autodoc_typehints = "description" + +html_show_sourcelink = False + +html_static_path = ['images'] +html_context = { + "display_github": False, # Add 'Edit on Github' link instead of 'View page source' + "last_updated": True, + "commit": False, +} + html_theme = 'furo' +html_logo = "images/sa_logo.png" html_theme_options = { "sidebar_hide_name": True, @@ -52,68 +55,3 @@ "color-brand-content": "#E5B62F", }, } -nitpick_ignore = [ - ('c:func', 'SHGetSpecialFolderPath'), # ref to MS docs - ('envvar', 'DISTUTILS_DEBUG'), # undocumented - ('envvar', 'HOME'), # undocumented - ('envvar', 'PLAT'), # undocumented - ('envvar', 'DIST_EXTRA_CONFIG'), # undocumented - ('py:attr', 'CCompiler.language_map'), # undocumented - ('py:attr', 'CCompiler.language_order'), # undocumented - ('py:class', 'distutils.dist.Distribution'), # undocumented - ('py:class', 'distutils.extension.Extension'), # undocumented - ('py:class', 'BorlandCCompiler'), # undocumented - ('py:class', 'CCompiler'), # undocumented - ('py:class', 'CygwinCCompiler'), # undocumented - ('py:class', 'distutils.dist.DistributionMetadata'), # undocumented - ('py:class', 'FileList'), # undocumented - ('py:class', 'IShellLink'), # ref to MS docs - ('py:class', 'MSVCCompiler'), # undocumented - ('py:class', 'OptionDummy'), # undocumented - ('py:class', 'UnixCCompiler'), # undocumented - ('py:exc', 'CompileError'), # undocumented - ('py:exc', 'DistutilsExecError'), # undocumented - ('py:exc', 'DistutilsFileError'), # undocumented - ('py:exc', 'LibError'), # undocumented - ('py:exc', 'LinkError'), # undocumented - ('py:exc', 'PreprocessError'), # undocumented - ('py:exc', 'setuptools.errors.PlatformError'), # sphinx cannot find it - ('py:func', 'distutils.CCompiler.new_compiler'), # undocumented - # undocumented: - ('py:func', 'distutils.dist.DistributionMetadata.read_pkg_file'), - ('py:func', 'distutils.file_util._copy_file_contents'), # undocumented - ('py:func', 'distutils.log.debug'), # undocumented - ('py:func', 'distutils.spawn.find_executable'), # undocumented - ('py:func', 'distutils.spawn.spawn'), # undocumented - # TODO: check https://docutils.rtfd.io in the future - ('py:mod', 'docutils'), # there's no Sphinx site documenting this -] - - -# Add any paths that contain templates here, relative to this directory. -templates_path = ['_templates'] - -# List of patterns, relative to source directory, that match files and -# directories to ignore when looking for source files. -# This pattern also affects html_static_path and html_extra_path. -exclude_patterns = [] - -# -- Options for HTML output ------------------------------------------------- - -# The theme to use for HTML and HTML Help pages. See the documentation for -# a list of builtin themes. -# - -# Add any paths that contain custom static files (such as style sheets) here, -# relative to this directory. They are copied after the builtin static files, -# so a file named "default.css" will overwrite the builtin "default.css". -html_static_path = [] - -autodoc_typehints = "description" -html_show_sourcelink = False - -html_context = { - "display_github": False, # Add 'Edit on Github' link instead of 'View page source' - "last_updated": True, - "commit": False, -} diff --git a/docs/source/sa_logo.png b/docs/source/images/sa_logo.png similarity index 100% rename from docs/source/sa_logo.png rename to docs/source/images/sa_logo.png diff --git a/docs/source/index.rst b/docs/source/index.rst index fd68a1fb6..c2a733fec 100644 --- a/docs/source/index.rst +++ b/docs/source/index.rst @@ -3,12 +3,12 @@ You can adapt this file completely to your liking, but it should at least contain the root `toctree` directive. -.. image:: sa_logo.png +.. image:: images/sa_logo.png :width: 200 :alt: SuperAnnotate AI :target: https://app.superannotate.com -| + .. toctree:: :caption: Table of Contents @@ -17,8 +17,8 @@ User guide API Reference - server.rst - cli.rst + CLI Reference + SA Server LICENSE.rst ---------- @@ -90,9 +90,6 @@ SDK is available on PyPI: The package officially supports Python 3.7+ and was tested under Linux and Windows (`Anaconda `_) platforms. -For more detailed installation steps and package usage please have a look at -the :ref:`tutorial `. - ---------- Supported Features diff --git a/docs/source/sa_server.rst b/docs/source/sa_server.rst new file mode 100644 index 000000000..5fcbc7342 --- /dev/null +++ b/docs/source/sa_server.rst @@ -0,0 +1,121 @@ +.. _ref_cli: + +SAServer Reference +====================================== + + + +The SAServer provides interface to create web API and run in development or production servers. + +This will create a directory by the given name in your current or provided directory: + +.. code-block:: bash + + superannotatecli create-server --name --path + + +Usage +________________________ + +SuperAnnotate Python SDK allows access to the platform without web browser: + +.. code-block:: python + + import random + from superannotate import SAClient + from superannotate import SAServer + + + app = SAServer() + sa_client = SAClient() + QA_EMAILS = [ + 'qa1@superannotate.com', 'qa2@superannotate.com', + 'qa3@superannotate.com', 'qa4@superannotate.com' + ] + + + @app.route("item_completed", methods=["POST"]) + def index(request): + """ + Listening webhooks on items completed events form Superannotate automation + and is randomly assigned to qa + """ + project_id, folder_id = request.data['project_id'], request.data['folder_id'] + project = sa_client.get_project_by_id(project_id) + folder = sa_client.get_folder_by_id(project_id=project_id, folder_id=folder_id) + sa_client.assign_items( + f"{project['name']}/{folder['name']}", + items=[request.data['name']], + user=random.choice(QA_EMAILS) + ) + + + if __name__ == '__main__': + app.run(host='0.0.0.0', port=5002) + +Interface +________________________ + +.. automethod:: superannotate.SAServer.route +.. automethod:: superannotate.SAServer.add_url_rule +.. automethod:: superannotate.SAServer.run + + +uWSGI +________________________ + +`uWSGI`_ is a fast, compiled server suite with extensive configuration +and capabilities beyond a basic server. + +* It can be very performant due to being a compiled program. +* It is complex to configure beyond the basic application, and has so + many options that it can be difficult for beginners to understand. +* It does not support Windows (but does run on WSL). +* It requires a compiler to install in some cases. + +This page outlines the basics of running uWSGI. Be sure to read its +documentation to understand what features are available. + +.. _uWSGI: https://uwsgi-docs.readthedocs.io/en/latest/ + +uWSGI has multiple ways to install it. The most straightforward is to +install the ``pyuwsgi`` package, which provides precompiled wheels for +common platforms. However, it does not provide SSL support, which can be +provided with a reverse proxy instead. + +Install ``pyuwsgi``. + +.. code-block:: text + + $ pip install pyuwsgi + +If you have a compiler available, you can install the ``uwsgi`` package +instead. Or install the ``pyuwsgi`` package from sdist instead of wheel. +Either method will include SSL support. + +.. code-block:: text + + $ pip install uwsgi + + # or + $ pip install --no-binary pyuwsgi pyuwsgi + + +Running +________________________ + +The most basic way to run uWSGI is to tell it to start an HTTP server +and import your application. + +.. code-block:: text + + $ uwsgi --http 127.0.0.1:8000 --master -p 4 -w wsgi:app + + *** Starting uWSGI 2.0.20 (64bit) on [x] *** + *** Operational MODE: preforking *** + spawned uWSGI master process (pid: x) + spawned uWSGI worker 1 (pid: x, cores: 1) + spawned uWSGI worker 2 (pid: x, cores: 1) + spawned uWSGI worker 3 (pid: x, cores: 1) + spawned uWSGI worker 4 (pid: x, cores: 1) + spawned uWSGI http 1 (pid: x) \ No newline at end of file diff --git a/docs/source/superannotate.sdk.rst b/docs/source/superannotate.sdk.rst deleted file mode 100644 index 197df936c..000000000 --- a/docs/source/superannotate.sdk.rst +++ /dev/null @@ -1,421 +0,0 @@ -.. _ref_sdk: - -API Reference -=================================== - -.. contents:: - -Remote functions ----------------- - -Instantiation and authentication -_________________________________ - -.. autoclass:: superannotate.SAClient - - -Projects -________ - -.. _ref_search_projects: -.. automethod:: superannotate.SAClient.search_projects -.. automethod:: superannotate.SAClient.create_project -.. automethod:: superannotate.SAClient.create_project_from_metadata -.. automethod:: superannotate.SAClient.clone_project -.. automethod:: superannotate.SAClient.delete_project -.. automethod:: superannotate.SAClient.rename_project -.. _ref_get_project_metadata: -.. automethod:: superannotate.SAClient.get_project_by_id -.. automethod:: superannotate.SAClient.get_project_metadata -.. automethod:: superannotate.SAClient.get_project_image_count -.. automethod:: superannotate.SAClient.search_folders -.. automethod:: superannotate.SAClient.assign_folder -.. automethod:: superannotate.SAClient.unassign_folder -.. automethod:: superannotate.SAClient.get_folder_by_id -.. automethod:: superannotate.SAClient.get_folder_metadata -.. automethod:: superannotate.SAClient.create_folder -.. automethod:: superannotate.SAClient.delete_folders -.. automethod:: superannotate.SAClient.upload_images_to_project -.. automethod:: superannotate.SAClient.attach_items_from_integrated_storage -.. automethod:: superannotate.SAClient.upload_image_to_project -.. automethod:: superannotate.SAClient.upload_annotations -.. automethod:: superannotate.SAClient.delete_annotations -.. _ref_upload_images_from_folder_to_project: -.. automethod:: superannotate.SAClient.upload_images_from_folder_to_project -.. automethod:: superannotate.SAClient.upload_video_to_project -.. automethod:: superannotate.SAClient.upload_videos_from_folder_to_project -.. _ref_upload_annotations_from_folder_to_project: -.. automethod:: superannotate.SAClient.upload_annotations_from_folder_to_project -.. automethod:: superannotate.SAClient.add_contributors_to_project -.. automethod:: superannotate.SAClient.get_project_settings -.. automethod:: superannotate.SAClient.set_project_default_image_quality_in_editor -.. automethod:: superannotate.SAClient.get_project_workflow -.. automethod:: superannotate.SAClient.set_project_workflow - ----------- - -Exports -_______ - -.. automethod:: superannotate.SAClient.prepare_export -.. automethod:: superannotate.SAClient.get_annotations -.. automethod:: superannotate.SAClient.get_annotations_per_frame -.. _ref_download_export: -.. automethod:: superannotate.SAClient.download_export -.. automethod:: superannotate.SAClient.get_exports - ----------- - -Items -______ - -.. automethod:: superannotate.SAClient.query -.. automethod:: superannotate.SAClient.get_item_by_id -.. automethod:: superannotate.SAClient.search_items -.. automethod:: superannotate.SAClient.download_annotations -.. automethod:: superannotate.SAClient.attach_items -.. automethod:: superannotate.SAClient.copy_items -.. automethod:: superannotate.SAClient.move_items -.. automethod:: superannotate.SAClient.delete_items -.. automethod:: superannotate.SAClient.assign_items -.. automethod:: superannotate.SAClient.unassign_items -.. automethod:: superannotate.SAClient.get_item_metadata -.. automethod:: superannotate.SAClient.set_annotation_statuses -.. automethod:: superannotate.SAClient.set_approval_statuses -.. automethod:: superannotate.SAClient.set_approval - ----------- - -Custom Metadata -______ - -.. automethod:: superannotate.SAClient.create_custom_fields -.. automethod:: superannotate.SAClient.get_custom_fields -.. automethod:: superannotate.SAClient.delete_custom_fields -.. automethod:: superannotate.SAClient.upload_custom_values -.. automethod:: superannotate.SAClient.delete_custom_values - ----------- - -Subsets -__________________ - -.. automethod:: superannotate.SAClient.get_subsets -.. automethod:: superannotate.SAClient.add_items_to_subset - ----------- - -Images -______ - - -.. _ref_search_images: -.. automethod:: superannotate.SAClient.download_image -.. automethod:: superannotate.SAClient.download_image_annotations -.. automethod:: superannotate.SAClient.upload_image_annotations -.. automethod:: superannotate.SAClient.pin_image -.. automethod:: superannotate.SAClient.upload_priority_scores - ----------- - -Annotation Classes -__________________ - -.. automethod:: superannotate.SAClient.create_annotation_class -.. _ref_create_annotation_classes_from_classes_json: -.. automethod:: superannotate.SAClient.create_annotation_classes_from_classes_json -.. automethod:: superannotate.SAClient.search_annotation_classes -.. automethod:: superannotate.SAClient.download_annotation_classes_json -.. automethod:: superannotate.SAClient.delete_annotation_class - ----------- - -Team -_________________ - -.. automethod:: superannotate.SAClient.get_team_metadata -.. automethod:: superannotate.SAClient.get_integrations -.. automethod:: superannotate.SAClient.invite_contributors_to_team -.. automethod:: superannotate.SAClient.search_team_contributors - ----------- - -Neural Network -_______________ - -.. automethod:: superannotate.SAClient.download_model -.. automethod:: superannotate.SAClient.run_prediction -.. automethod:: superannotate.SAClient.search_models - ----------- - - -.. _ref_metadata: - -Remote metadata reference -------------------------- - - -Projects metadata -_________________ - -Project metadata example: - -.. code-block:: python - - { - "name": "Example Project test", - "description": "test vector", - "creator_id": "admin@superannotate.com", - "updatedAt": "2020-08-31T05:43:43.118Z", - "createdAt": "2020-08-31T05:43:43.118Z" - "type": "Vector", - "attachment_name": None, - "attachment_path": None, - "entropy_status": 1, - "status": "NotStarted", - "...": "..." - } - - ----------- - -Setting metadata -_________________ - -Setting metadata example: - -.. code-block:: python - - { - "attribute": "FrameRate", - "value": 3 - } - - ----------- - -Export metadata -_______________ - -Export metadata example: - -.. code-block:: python - - { - "name": "Aug 17 2020 15:44 First Name.zip", - "user_id": "user@gmail.com", - "status": 2, - "createdAt": "2020-08-17T11:44:26.000Z", - "...": "..." - } - - ----------- - - -Integration metadata -______________________ - -Integration metadata example: - -.. code-block:: python - - { - "name": "My S3 Bucket", - "type": "aws", - "root": "test-openseadragon-1212" - } - - ----------- - - -Item metadata -_______________ - -Item metadata example: - -.. code-block:: python - - { - "name": "example.jpeg", - "path": "project/folder_1/meow.jpeg", - "url": "https://sa-public-files.s3.../text_file_example_1.jpeg", - "annotation_status": "NotStarted", - "annotator_name": None, - "qa_name": None, - "entropy_value": None, - "createdAt": "2022-02-15T20:46:44.000Z", - "updatedAt": "2022-02-15T20:46:44.000Z" - } - ----------- - - -Image metadata -_______________ - - -Image metadata example: - -.. code-block:: python - - { - "name": "000000000001.jpg", - "annotation_status": "Completed", - "prediction_status": "NotStarted", - "segmentation_status": "NotStarted", - "annotator_id": None, - "annotator_name": None, - "qa_id": None, - "qa_name": None, - "entropy_value": None, - "approval_status": None, - "createdAt": "2020-08-18T07:30:06.000Z", - "updatedAt": "2020-08-18T07:30:06.000Z" - "is_pinned": 0, - "...": "...", - } - - ----------- - -Priority score -_______________ - - -Priority score example: - -.. code-block:: python - - { - "name" : "image1.png", - "priority": 0.567 - } - - ----------- - -Attachment -_______________ - - -Attachment example: - -.. code-block:: python - - { - "url": "https://sa-public-files.s3.../text_file_example_1.jpeg", - "name": "example.jpeg" - } - - ----------- - -.. _ref_class: - -Annotation class metadata -_________________________ - - -Annotation class metadata example: - -.. code-block:: python - - { - "id": 4444, - "name": "Human", - "color": "#e4542b", - "attribute_groups": [ - { - "name": "tall", - "attributes": [ - { - "name": "yes" - }, - { - "name": "no" - } - ] - }, - { - "name": "age", - "attributes": [ - { - "name": "young" - }, - { - "name": "old" - } - ] - } - ], - - "...": "..." - } - - - ----------- - -Team contributor metadata -_________________________ - -Team contributor metadata example: - -.. code-block:: python - - { - "id": "admin@superannotate.com", - "first_name": "First Name", - "last_name": "Last Name", - "email": "admin@superannotate.com", - "user_role": 6 - "...": "...", - } - - - ----------- - -Annotation JSON helper functions --------------------------------- - -.. _ref_converter: - -Converting annotation format to and from src.superannotate format -_________________________________________________________________ - - -.. _ref_import_annotation_format: -.. autofunction:: superannotate.import_annotation -.. autofunction:: superannotate.export_annotation -.. autofunction:: superannotate.convert_project_type -.. autofunction:: superannotate.convert_json_version - - - ----------- - -Working with annotations -________________________ - -.. _ref_aggregate_annotations_as_df: -.. automethod:: superannotate.SAClient.validate_annotations -.. automethod:: superannotate.SAClient.aggregate_annotations_as_df - ----------- - -Aggregating class distribution from annotations -_____________________________________________________________ - -.. autofunction:: superannotate.class_distribution - ----------- - -Utility functions --------------------------------- - -.. autofunction:: superannotate.SAClient.consensus -.. autofunction:: superannotate.SAClient.benchmark diff --git a/docs/source/tutorial.sdk.rst b/docs/source/tutorial.sdk.rst deleted file mode 100644 index b6b124c48..000000000 --- a/docs/source/tutorial.sdk.rst +++ /dev/null @@ -1,428 +0,0 @@ -.. _ref_tutorial: - -Tutorial -=========================== - -.. contents:: - -.. _ref_tutorial_installation: - -Installation -____________ - - - -Creating a folder in a project -______________________________ - -To create a new folder "folder1" in the project "Example Project 1": - -.. code-block:: python - - sa.create_folder(project, "folder1") - -After that point almost all SDK functions that use project name as argument can -point to that folder with slash after the project name, e.g., -"Example Project 1/folder1", in this case. - -.. note:: - - CLI command :ref:`upload-images ` can also be used for - image upload. - -.. note:: - - To upload images to the "folder1" instead of the root of the project: - - .. code-block:: python - - sa.upload_images_from_folder_to_project(project + "/folder1", "") - -Working with annotation classes -_______________________________________________ - -An annotation class for a project can be created with SDK's: - -.. code-block:: python - - sa.create_annotation_class(project, "Large car", color="#FFFFAA") - - -To create annotation classes in bulk with SuperAnnotate export format -:file:`classes.json` (documentation at: -https://app.superannotate.com/documentation Management Tools --> Project Workflow part): - -.. code-block:: python - - sa.create_annotation_classes_from_classes_json(project, "") - - -All of the annotation classes of a project are downloaded (as :file:`classes/classes.json`) with -:ref:`download_export ` along with annotations, but they -can also be downloaded separately with: - -.. code-block:: python - - sa.download_annotation_classes_json(project, "") - -The :file:`classes.json` file will be downloaded to :file:`""` folder. - - -Working with annotations -_______________________________________________ - - -The SuperAnnotate format annotation JSONs have the general form: - -.. code-block:: json - - [ - { - "className": "Human", - "points" : "...", - "..." : "..." - }, - { - "className": "Cat", - "points" : "...", - "..." : "..." - }, - { - "..." : "..." - } - ] - -the "className" fields here will identify the annotation class of an annotation -object (polygon, points, etc.). The project -you are uploading to should contain annotation class with that name. - -To upload annotations to platform: - -.. code-block:: python - - sa.upload_annotations_from_folder_to_project(project, "") - -This will try uploading to the project all the JSON files in the folder that have specific -file naming convention. For vector -projects JSONs should be named :file:`"___objects.json"`. For pixel projects -JSON files should be named :file:`"___pixel.json"` and also for -each JSON a mask image file should be present with the name -:file:`"___save.png"`. Image with :file:`` should -already be present in the project for the upload to work. - - -Exporting projects -__________________ - -To export the project annotations we need to prepare the export first: - -.. code-block:: python - - export = sa.prepare_export(project, include_fuse=True) - -We can download the prepared export with: - -.. code-block:: python - - sa.download_export(project, export, "", extract_zip_contents=True) - -:ref:`download_export ` will wait until the export is -finished preparing and download it to the specified folder. - -.. warning:: - - Starting from version 1.9.0 :ref:`download_export ` additionally - requires :py:obj:`project` as first argument. - - -Converting annotation format -______________________________ - - -After exporting project annotations (in SuperAnnotate format), it is possible -to convert them to other annotation formats: - -.. code-block:: python - - sa.export_annotation("", "", "", "", - "", "") - -.. note:: - - Right now we support only SuperAnnotate annotation format to COCO annotation format conversion, but you can convert from "COCO", "Pascal VOC", "DataLoop", "LabelBox", "SageMaker", "Supervisely", "VGG", "VoTT" or "YOLO" annotation formats to SuperAnnotate annotation format. - -.. _git_repo: https://github.com/superannotateai/superannotate-python-sdk - -You can find more information annotation format conversion :ref:`here `. We provide some examples in our `GitHub repository `_. In the root folder of our github repository, you can run following commands to do conversions. - -.. code-block:: python - - from superannotate import export_annotation - from superannotate import import_annotation - - # From SA format to COCO panoptic format - export_annotation( - "tests/converter_test/COCO/input/fromSuperAnnotate/cats_dogs_panoptic_segm", - "tests/converter_test/COCO/output/panoptic", - "COCO", "panoptic_test", "Pixel","panoptic_segmentation" - ) - - # From COCO keypoints detection format to SA annotation format - import_annotation( - "tests/converter_test/COCO/input/toSuperAnnotate/keypoint_detection", - "tests/converter_test/COCO/output/keypoints", - "COCO", "person_keypoints_test", "Vector", "keypoint_detection" - ) - - # Pascal VOC annotation format to SA annotation format - import_annotation( - "tests/converter_test/VOC/input/fromPascalVOCToSuperAnnotate/VOC2012", - "tests/converter_test/VOC/output/instances", - "VOC", "instances_test", "Pixel", "instance_segmentation" - ) - - # YOLO annotation format to SA annotation format - import_annotation( - 'tests/converter_test/YOLO/input/toSuperAnnotate', - 'tests/converter_test/YOLO/output', - 'YOLO', '', 'Vector', 'object_detection' - ) - - # LabelBox annotation format to SA annotation format - import_annotation( - "tests/converter_test/LabelBox/input/toSuperAnnotate/", - "tests/converter_test/LabelBox/output/objects/", - "LabelBox", "labelbox_example", "Vector", "object_detection" - ) - - # Supervisely annotation format to SA annotation format - import_annotation( - "tests/converter_test/Supervisely/input/toSuperAnnotate", - "tests/converter_test/Supervisely/output", - "Supervisely", "", "Vector", "vector_annotation" - ) - - # DataLoop annotation format to SA annotation format - import_annotation( - "tests/converter_test/DataLoop/input/toSuperAnnotate", - "tests/converter_test/DataLoop/output", - "DataLoop", "", "Vector", "vector_annotation" - ) - - # VGG annotation format to SA annotation format - import_annotation( - "tests/converter_test/VGG/input/toSuperAnnotate", - "tests/converter_test/VGG/output", - "VGG", "vgg_test", "Vector", "instance_segmentation" - ) - - # VoTT annotation format to SA annotation format - import_annotation( - "tests/converter_test/VoTT/input/toSuperAnnotate", - "tests/converter_test/VoTT/output", - "VoTT", "", "Vector", "vector_annotation" - ) - - # GoogleCloud annotation format to SA annotation format - import_annotation( - "tests/converter_test/GoogleCloud/input/toSuperAnnotate", - "tests/converter_test/GoogleCloud/output", - "GoogleCloud", "image_object_detection", "Vector", "object_detection" - ) - - # GoogleCloud annotation format to SA annotation format - import_annotation( - "tests/converter_test/SageMaker/input/toSuperAnnotate", - "tests/converter_test/SageMaker/output", - "SageMaker", "test-obj-detect", "Vector", "object_detection" - ) - - - -Working with images -_____________________ - - -To download the image one can use: - -.. code-block:: python - - image = "example_image1.jpg" - - sa.download_image(project, image, "") - -To download image annotations: - -.. code-block:: python - - sa.download_image_annotations(project, image, "") - -Upload back to the platform with: - -.. code-block:: python - - sa.upload_image_annotations(project, image, "") - ----------- - - -Working with team contributors -______________________________ - -A team contributor can be invited to the team with: - -.. code-block:: python - - sa.invite_contributors_to_team(emails=["admin@superannotate.com"], admin=False) - ----------- - - -pandas DataFrame out of project annotations and annotation instance filtering -_____________________________________________________________________________ - - -To create a `pandas DataFrame `_ from project -SuperAnnotate format annotations: - -.. code-block:: python - - df = sa.aggregate_annotations_as_df("") - -The created DataFrame will have columns specified at -:ref:`aggregate_annotations_as_df `. - -Example of created DataFrame: - -.. image:: pandas_df.png - -Each row represents annotation information. One full annotation with multiple -attribute groups can be grouped under :code:`instanceId` field. - ----------- - - -Aggregating class distribution across multiple projects -_______________________________________________________ - -After exporting annotations from multiple projects, it is possible to aggregate class distribution of annotated instances as follows - -.. code-block:: python - - df = sa.class_distribution("", [project_names]) - -Aggregated distribution is returned as pandas dataframe with columns className and count. Enabling visualize flag plots histogram of obtained distribution. - -.. code-block:: python - - df = sa.class_distribution("", [project_names], visualize = True) - -.. image:: class_distribution.png - ----------- - - -Working with DICOM files -_______________________________________________________ - -JPEG images with names :file:`_.jpg` will be created -in :file:``. Those JPEG images can be uploaded to -SuperAnnotate platform using the regular: - -.. code-block:: python - - sa.upload_images_from_folder_to_project(project, "") - -Some DICOM files can have image frames that are compressed. To load them, `GDCM : -Grassroots DICOM library `_ needs to be installed: - -.. code-block:: bash - - # using conda - conda install -c conda-forge gdcm - - # or on Ubuntu with versions above 19.04 - sudo apt install python3-gdcm - ----------- - - -Computing consensus scores for instances between several projects -_________________________________________________________________ - - -Consensus is a tool to compare the quallity of the annotations of the same image that is present in several projects. -To compute the consensus scores: - -.. code-block:: python - - res_df = sa.consensus([project_names], "", [image_list], "") - -Here pandas DataFrame with following columns is returned: creatorEmail, imageName, instanceId, className, area, attribute, projectName, score - -.. image:: consensus_dataframe.png - -Besides the pandas DataFrame there is an option to get the following plots by setting the show_plots flag to True: - -* Box plot of consensus scores for each annotators -* Box plot of consensus scores for each project -* Scatter plots of consensus score vs instance area for each project - -.. code-block:: python - - sa.consensus([project_names], "", [image_list], "", show_plots=True) - -To the left of each box plot the original score points of that annotator is depicted, the box plots are colored by annotator. - -.. image:: consensus_annotators_box.png - -Analogically the box plots of consensus scores for each project are colored according to project name. - -.. image:: consensus_projects_box.png - -Scatter plot of consensus score vs instance area is separated by projects. Hovering on a point reveals its annotator and image name. -The points are colored according to class name. Each annotator is represented with separate symbol. - -.. image:: consensus_scatter.png - ----------- - - -Computing benchmark scores for instances between ground truth project and given project list -____________________________________________________________________________________________ - - -Benchmark is a tool to compare the quallity of the annotations of the same image that is present in several projects with -the ground truth annotation of the same image that is in a separate project. - -To compute the benchmark scores: - -.. code-block:: python - - res_df = sa.benchmark("",[project_names], "", [image_list], "") - -Here pandas DataFrame with exactly same structure as in case of consensus computation is returned. - -Besides the pandas DataFrame there is an option to get the following plots by setting the show_plots flag to True: - -* Box plot of benchmark scores for each annotators -* Box plot of benchmark scores for each project -* Scatter plots of benchmark score vs instance area for each project - -.. code-block:: python - - sa.benchmark("", [project_names], "", [image_list], "", show_plots=True) - -To the left of each box plot the original score points of that annotator is depicted, the box plots are colored by annotator. - -.. image:: benchmark_annotators_box.png - -Analogically the box plots of benchmark scores for each project are colored according to project name. - -.. image:: benchmark_projects_box.png - -Scatter plot of benchmark score vs instance area is separated by projects. Hovering on a point reveals its annotator and image name. -The points are colored according to class name. Each annotator is represented with separate symbol. - -.. image:: benchmark_scatter.png \ No newline at end of file diff --git a/docs/source/userguide/datafiles.rst b/docs/source/userguide/datafiles.rst deleted file mode 100644 index 44ff74252..000000000 --- a/docs/source/userguide/datafiles.rst +++ /dev/null @@ -1,555 +0,0 @@ -==================== -Data Files Support -==================== - -Old packaging installation methods in the Python ecosystem -have traditionally allowed installation of "data files", which -are placed in a platform-specific location. However, the most common use case -for data files distributed with a package is for use *by* the package, usually -by including the data files **inside the package directory**. - -Setuptools focuses on this most common type of data files and offers three ways -of specifying which files should be included in your packages, as described in -the following sections. - -include_package_data -==================== - -First, you can simply use the ``include_package_data`` keyword. -For example, if the package tree looks like this:: - - project_root_directory - ├── setup.py # and/or setup.cfg, pyproject.toml - └── src - └── mypkg - ├── __init__.py - ├── data1.rst - ├── data2.rst - ├── data1.txt - └── data2.txt - -and you supply this configuration: - -.. tab:: setup.cfg - - .. code-block:: ini - - [options] - # ... - packages = find: - package_dir = - = src - include_package_data = True - - [options.packages.find] - where = src - -.. tab:: setup.py - - .. code-block:: python - - from setuptools import setup, find_packages - setup( - # ..., - packages=find_packages(where="src"), - package_dir={"": "src"}, - include_package_data=True - ) - -.. tab:: pyproject.toml (**BETA**) [#beta]_ - - .. code-block:: toml - - [tool.setuptools] - # ... - # By default, include-package-data is true in pyproject.toml, so you do - # NOT have to specify this line. - include-package-data = true - - [tool.setuptools.packages.find] - where = ["src"] - -then all the ``.txt`` and ``.rst`` files will be automatically installed with -your package, provided: - -1. These files are included via the |MANIFEST.in|_ file, like so:: - - include src/mypkg/*.txt - include src/mypkg/*.rst - -2. OR, they are being tracked by a revision control system such as Git, Mercurial - or SVN, and you have configured an appropriate plugin such as - :pypi:`setuptools-scm` or :pypi:`setuptools-svn`. - (See the section below on :ref:`Adding Support for Revision - Control Systems` for information on how to write such plugins.) - -package_data -============ - -By default, ``include_package_data`` considers **all** non ``.py`` files found inside -the package directory (``src/mypkg`` in this case) as data files, and includes those that -satisfy (at least) one of the above two conditions into the source distribution, and -consequently in the installation of your package. -If you want finer-grained control over what files are included, then you can also use -the ``package_data`` keyword. -For example, if the package tree looks like this:: - - project_root_directory - ├── setup.py # and/or setup.cfg, pyproject.toml - └── src - └── mypkg - ├── __init__.py - ├── data1.rst - ├── data2.rst - ├── data1.txt - └── data2.txt - -then you can use the following configuration to capture the ``.txt`` and ``.rst`` files as -data files: - -.. tab:: setup.cfg - - .. code-block:: ini - - [options] - # ... - packages = find: - package_dir = - = src - - [options.packages.find] - where = src - - [options.package_data] - mypkg = - *.txt - *.rst - -.. tab:: setup.py - - .. code-block:: python - - from setuptools import setup, find_packages - setup( - # ..., - packages=find_packages(where="src"), - package_dir={"": "src"}, - package_data={"mypkg": ["*.txt", "*.rst"]} - ) - -.. tab:: pyproject.toml (**BETA**) [#beta]_ - - .. code-block:: toml - - [tool.setuptools.packages.find] - where = ["src"] - - [tool.setuptools.package-data] - mypkg = ["*.txt", "*.rst"] - -The ``package_data`` argument is a dictionary that maps from package names to -lists of glob patterns. Note that the data files specified using the ``package_data`` -option neither require to be included within a |MANIFEST.in|_ file, nor -require to be added by a revision control system plugin. - -.. note:: - If your glob patterns use paths, you *must* use a forward slash (``/``) as - the path separator, even if you are on Windows. Setuptools automatically - converts slashes to appropriate platform-specific separators at build time. - -.. note:: - Glob patterns do not automatically match dotfiles (directory or file names - starting with a dot (``.``)). To include such files, you must explicitly start - the pattern with a dot, e.g. ``.*`` to match ``.gitignore``. - -If you have multiple top-level packages and a common pattern of data files for all these -packages, for example:: - - project_root_directory - ├── setup.py # and/or setup.cfg, pyproject.toml - └── src - ├── mypkg1 - │   ├── data1.rst - │   ├── data1.txt - │   └── __init__.py - └── mypkg2 - ├── data2.txt - └── __init__.py - -Here, both packages ``mypkg1`` and ``mypkg2`` share a common pattern of having ``.txt`` -data files. However, only ``mypkg1`` has ``.rst`` data files. In such a case, if you want to -use the ``package_data`` option, the following configuration will work: - -.. tab:: setup.cfg - - .. code-block:: ini - - [options] - packages = find: - package_dir = - = src - - [options.packages.find] - where = src - - [options.package_data] - * = - *.txt - mypkg1 = - data1.rst - -.. tab:: setup.py - - .. code-block:: python - - from setuptools import setup, find_packages - setup( - # ..., - packages=find_packages(where="src"), - package_dir={"": "src"}, - package_data={"": ["*.txt"], "mypkg1": ["data1.rst"]}, - ) - -.. tab:: pyproject.toml (**BETA**) [#beta]_ - - .. code-block:: toml - - [tool.setuptools.packages.find] - where = ["src"] - - [tool.setuptools.package-data] - "*" = ["*.txt"] - mypkg1 = ["data1.rst"] - -Notice that if you list patterns in ``package_data`` under the empty string ``""`` in -``setup.py``, and the asterisk ``*`` in ``setup.cfg`` and ``pyproject.toml``, these -patterns are used to find files in every package. For example, we use ``""`` or ``*`` -to indicate that the ``.txt`` files from all packages should be captured as data files. -Also note how we can continue to specify patterns for individual packages, i.e. -we specify that ``data1.rst`` from ``mypkg1`` alone should be captured as well. - -.. note:: - When building an ``sdist``, the datafiles are also drawn from the - ``package_name.egg-info/SOURCES.txt`` file, so make sure that this is removed if - the ``setup.py`` ``package_data`` list is updated before calling ``setup.py``. - -.. note:: - If using the ``include_package_data`` argument, files specified by - ``package_data`` will *not* be automatically added to the manifest unless - they are listed in the |MANIFEST.in|_ file or by a plugin like - :pypi:`setuptools-scm` or :pypi:`setuptools-svn`. - -.. https://docs.python.org/3/distutils/setupscript.html#installing-package-data - -exclude_package_data -==================== - -Sometimes, the ``include_package_data`` or ``package_data`` options alone -aren't sufficient to precisely define what files you want included. For example, -consider a scenario where you have ``include_package_data=True``, and you are using -a revision control system with an appropriate plugin. -Sometimes developers add directory-specific marker files (such as ``.gitignore``, -``.gitkeep``, ``.gitattributes``, or ``.hgignore``), these files are probably being -tracked by the revision control system, and therefore by default they will be -included when the package is installed. - -Supposing you want to prevent these files from being included in the -installation (they are not relevant to Python or the package), then you could -use the ``exclude_package_data`` option: - -.. tab:: setup.cfg - - .. code-block:: ini - - [options] - # ... - packages = find: - package_dir = - = src - include_package_data = True - - [options.packages.find] - where = src - - [options.exclude_package_data] - mypkg = - .gitattributes - -.. tab:: setup.py - - .. code-block:: python - - from setuptools import setup, find_packages - setup( - # ..., - packages=find_packages(where="src"), - package_dir={"": "src"}, - include_package_data=True, - exclude_package_data={"mypkg": [".gitattributes"]}, - ) - -.. tab:: pyproject.toml (**BETA**) [#beta]_ - - .. code-block:: toml - - [tool.setuptools.packages.find] - where = ["src"] - - [tool.setuptools.exclude-package-data] - mypkg = [".gitattributes"] - -The ``exclude_package_data`` option is a dictionary mapping package names to -lists of wildcard patterns, just like the ``package_data`` option. And, just -as with that option, you can use the empty string key ``""`` in ``setup.py`` and the -asterisk ``*`` in ``setup.cfg`` and ``pyproject.toml`` to match all top-level packages. - -Any files that match these patterns will be *excluded* from installation, -even if they were listed in ``package_data`` or were included as a result of using -``include_package_data``. - -Subdirectory for Data Files -=========================== - -A common pattern is where some (or all) of the data files are placed under -a separate subdirectory. For example:: - - project_root_directory - ├── setup.py # and/or setup.cfg, pyproject.toml - └── src - └── mypkg - ├── data - │   ├── data1.rst - │   └── data2.rst - ├── __init__.py - ├── data1.txt - └── data2.txt - -Here, the ``.rst`` files are placed under a ``data`` subdirectory inside ``mypkg``, -while the ``.txt`` files are directly under ``mypkg``. - -In this case, the recommended approach is to treat ``data`` as a namespace package -(refer :pep:`420`). With ``package_data``, -the configuration might look like this: - -.. tab:: setup.cfg - - .. code-block:: ini - - [options] - # ... - packages = find_namespace: - package_dir = - = src - - [options.packages.find] - where = src - - [options.package_data] - mypkg = - *.txt - mypkg.data = - *.rst - -.. tab:: setup.py - - .. code-block:: python - - from setuptools import setup, find_namespace_packages - setup( - # ..., - packages=find_namespace_packages(where="src"), - package_dir={"": "src"}, - package_data={ - "mypkg": ["*.txt"], - "mypkg.data": ["*.rst"], - } - ) - -.. tab:: pyproject.toml (**BETA**) [#beta]_ - - .. code-block:: toml - - [tool.setuptools.packages.find] - # scanning for namespace packages is true by default in pyproject.toml, so - # you do NOT need to include the following line. - namespaces = true - where = ["src"] - - [tool.setuptools.package-data] - mypkg = ["*.txt"] - "mypkg.data" = ["*.rst"] - -In other words, we allow Setuptools to scan for namespace packages in the ``src`` directory, -which enables the ``data`` directory to be identified, and then, we separately specify data -files for the root package ``mypkg``, and the namespace package ``data`` under the package -``mypkg``. - -With ``include_package_data`` the configuration is simpler: you simply need to enable -scanning of namespace packages in the ``src`` directory and the rest is handled by Setuptools. - -.. tab:: setup.cfg - - .. code-block:: ini - - [options] - packages = find_namespace: - package_dir = - = src - include_package_data = True - - [options.packages.find] - where = src - -.. tab:: setup.py - - .. code-block:: python - - from setuptools import setup, find_namespace_packages - setup( - # ... , - packages=find_namespace_packages(where="src"), - package_dir={"": "src"}, - include_package_data=True, - ) - -.. tab:: pyproject.toml (**BETA**) [#beta]_ - - .. code-block:: toml - - [tool.setuptools] - # ... - # By default, include-package-data is true in pyproject.toml, so you do - # NOT have to specify this line. - include-package-data = true - - [tool.setuptools.packages.find] - # scanning for namespace packages is true by default in pyproject.toml, so - # you need NOT include the following line. - namespaces = true - where = ["src"] - -Summary -======= - -In summary, the three options allow you to: - -``include_package_data`` - Accept all data files and directories matched by |MANIFEST.in|_ or added by - a :ref:`plugin `. - -``package_data`` - Specify additional patterns to match files that may or may - not be matched by |MANIFEST.in|_ or added by - a :ref:`plugin `. - -``exclude_package_data`` - Specify patterns for data files and directories that should *not* be - included when a package is installed, even if they would otherwise have - been included due to the use of the preceding options. - -.. note:: - Due to the way the build process works, a data file that you - include in your project and then stop including may be "orphaned" in your - project's build directories, requiring you to run ``setup.py clean --all`` to - fully remove them. This may also be important for your users and contributors - if they track intermediate revisions of your project using Subversion; be sure - to let them know when you make changes that remove files from inclusion so they - can run ``setup.py clean --all``. - - -.. _Accessing Data Files at Runtime: - -Accessing Data Files at Runtime -=============================== - -Typically, existing programs manipulate a package's ``__file__`` attribute in -order to find the location of data files. For example, if you have a structure -like this:: - - project_root_directory - ├── setup.py # and/or setup.cfg, pyproject.toml - └── src - └── mypkg - ├── data - │   └── data1.txt - ├── __init__.py - └── foo.py - -Then, in ``mypkg/foo.py``, you may try something like this in order to access -``mypkg/data/data1.txt``: - -.. code-block:: python - - import os - data_path = os.path.join(os.path.dirname(__file__), 'data', 'data1.txt') - with open(data_path, 'r') as data_file: - ... - -However, this manipulation isn't compatible with :pep:`302`-based import hooks, -including importing from zip files and Python Eggs. It is strongly recommended that, -if you are using data files, you should use :mod:`importlib.resources` to access them. -In this case, you would do something like this: - -.. code-block:: python - - from importlib.resources import files - data_text = files('mypkg.data').joinpath('data1.txt').read_text() - -:mod:`importlib.resources` was added to Python 3.7. However, the API illustrated in -this code (using ``files()``) was added only in Python 3.9, [#files_api]_ and support -for accessing data files via namespace packages was added only in Python 3.10 [#namespace_support]_ -(the ``data`` subdirectory is a namespace package under the root package ``mypkg``). -Therefore, you may find this code to work only in Python 3.10 (and above). For other -versions of Python, you are recommended to use the :pypi:`importlib-resources` backport -which provides the latest version of this library. In this case, the only change that -has to be made to the above code is to replace ``importlib.resources`` with ``importlib_resources``, i.e. - -.. code-block:: python - - from importlib_resources import files - ... - -See :doc:`importlib-resources:using` for detailed instructions. - -.. tip:: Files inside the package directory should be *read-only* to avoid a - series of common problems (e.g. when multiple users share a common Python - installation, when the package is loaded from a zip file, or when multiple - instances of a Python application run in parallel). - - If your Python package needs to write to a file for shared data or configuration, - you can use standard platform/OS-specific system directories, such as - ``~/.local/config/$appname`` or ``/usr/share/$appname/$version`` (Linux specific) [#system-dirs]_. - A common approach is to add a read-only template file to the package - directory that is then copied to the correct system directory if no - pre-existing file is found. - - -Non-Package Data Files -====================== - -Historically, ``setuptools`` by way of ``easy_install`` would encapsulate data -files from the distribution into the egg (see `the old docs -`_). As eggs are deprecated and pip-based installs -fall back to the platform-specific location for installing data files, there is -no supported facility to reliably retrieve these resources. - -Instead, the PyPA recommends that any data files you wish to be accessible at -run time be included **inside the package**. - - ----- - -.. [#beta] - Support for adding build configuration options via the ``[tool.setuptools]`` - table in the ``pyproject.toml`` file. See :doc:`/userguide/pyproject_config`. - -.. [#system-dirs] These locations can be discovered with the help of - third-party libraries such as :pypi:`platformdirs`. - -.. [#files_api] Reference: https://importlib-resources.readthedocs.io/en/latest/using.html#migrating-from-legacy - -.. [#namespace_support] Reference: https://github.com/python/importlib_resources/pull/196#issuecomment-734520374 - - -.. |MANIFEST.in| replace:: ``MANIFEST.in`` -.. _MANIFEST.in: https://packaging.python.org/en/latest/guides/using-manifest-in/ diff --git a/docs/source/userguide/declarative_config.rst b/docs/source/userguide/declarative_config.rst deleted file mode 100644 index fa104b10e..000000000 --- a/docs/source/userguide/declarative_config.rst +++ /dev/null @@ -1,326 +0,0 @@ -.. _declarative config: - ------------------------------------------------- -Configuring setuptools using ``setup.cfg`` files ------------------------------------------------- - -.. note:: New in 30.3.0 (8 Dec 2016). - -.. important:: - If compatibility with legacy builds (i.e. those not using the :pep:`517` - build API) is desired, a ``setup.py`` file containing a ``setup()`` function - call is still required even if your configuration resides in ``setup.cfg``. - -``Setuptools`` allows using configuration files (usually :file:`setup.cfg`) -to define a package’s metadata and other options that are normally supplied -to the ``setup()`` function (declarative config). - -This approach not only allows automation scenarios but also reduces -boilerplate code in some cases. - -.. _example-setup-config: - -.. code-block:: ini - - [metadata] - name = my_package - version = attr: my_package.VERSION - author = Josiah Carberry - author_email = josiah_carberry@brown.edu - description = My package description - long_description = file: README.rst, CHANGELOG.rst, LICENSE.rst - keywords = one, two - license = BSD-3-Clause - classifiers = - Framework :: Django - Programming Language :: Python :: 3 - - [options] - zip_safe = False - include_package_data = True - packages = find: - python_requires = >=3.7 - install_requires = - requests - importlib-metadata; python_version<"3.8" - - [options.package_data] - * = *.txt, *.rst - hello = *.msg - - [options.entry_points] - console_scripts = - executable-name = my_package.module:function - - [options.extras_require] - pdf = ReportLab>=1.2; RXP - rest = docutils>=0.3; pack ==1.1, ==1.3 - - [options.packages.find] - exclude = - examples* - tools* - docs* - my_package.tests* - -Metadata and options are set in the config sections of the same name. - -* Keys are the same as the :doc:`keyword arguments ` one - provides to the ``setup()`` function. - -* Complex values can be written comma-separated or placed one per line - in *dangling* config values. The following are equivalent: - - .. code-block:: ini - - [metadata] - keywords = one, two - - [metadata] - keywords = - one - two - -* In some cases, complex values can be provided in dedicated subsections for - clarity. - -* Some keys allow ``file:``, ``attr:``, ``find:``, and ``find_namespace:`` directives in - order to cover common usecases. - -* Unknown keys are ignored. - - -Using a ``src/`` layout -======================= - -One commonly used configuration has all the Python source code in a -subdirectory (often called the ``src/`` layout), like this:: - - ├── src - │   └── mypackage - │   ├── __init__.py - │   └── mod1.py - ├── setup.py - └── setup.cfg - -You can set up your ``setup.cfg`` to automatically find all your packages in -the subdirectory, using :ref:`package_dir `, like this: - -.. code-block:: ini - - # This example contains just the necessary options for a src-layout, set up - # the rest of the file as described above. - - [options] - package_dir= - =src - packages=find: - - [options.packages.find] - where=src - -In this example, the value for the :ref:`package_dir ` -configuration (i.e. ``=src``) is parsed as ``{"": "src"}``. -The ``""`` key has a special meaning in this context, and indicates that all the -packages are contained inside the given directory. -Also note that the value for ``[options.packages.find] where`` matches the -value associated with ``""`` in the ``package_dir`` dictionary. - -.. - TODO: Add the following tip once the auto-discovery is no longer experimental: - - Starting in version 61, ``setuptools`` can automatically infer the - configurations for both ``packages`` and ``package_dir`` for projects using - a ``src/`` layout (as long as no value is specified for ``py_modules``). - Please see :doc:`package discovery ` for more - details. - -Specifying values -================= - -Some values are treated as simple strings, some allow more logic. - -Type names used below: - -* ``str`` - simple string -* ``list-comma`` - dangling list or string of comma-separated values -* ``list-semi`` - dangling list or string of semicolon-separated values -* ``bool`` - ``True`` is 1, yes, true -* ``dict`` - list-comma where each entry corresponds to a key/value pair, - with keys separated from values by ``=``. - If an entry starts with ``=``, the key is assumed to be an empty string - (e.g. ``=src`` is parsed as ``{"": "src"}``). -* ``section`` - values are read from a dedicated (sub)section - - -Special directives: - -* ``attr:`` - Value is read from a module attribute. ``attr:`` supports - callables and iterables; unsupported types are cast using ``str()``. - - In order to support the common case of a literal value assigned to a variable - in a module containing (directly or indirectly) third-party imports, - ``attr:`` first tries to read the value from the module by examining the - module's AST. If that fails, ``attr:`` falls back to importing the module. - -* ``file:`` - Value is read from a list of files and then concatenated - - .. important:: - The ``file:`` directive is sandboxed and won't reach anything outside the - project directory (i.e. the directory containing ``setup.cfg``/``pyproject.toml``). - - .. note:: - If you are using an old version of ``setuptools``, you might need to ensure - that all files referenced by the ``file:`` directive are included in the ``sdist`` - (you can do that via ``MANIFEST.in`` or using plugins such as ``setuptools-scm``, - please have a look on :doc:`/userguide/miscellaneous` for more information). - - .. versionchanged:: 66.1.0 - Newer versions of ``setuptools`` will automatically add these files to the ``sdist``. - - -Metadata --------- - -.. attention:: - The aliases given below are supported for compatibility reasons, - but their use is not advised. - -============================== ================= ================= =============== ========== -Key Aliases Type Minimum Version Notes -============================== ================= ================= =============== ========== -name str -version attr:, file:, str 39.2.0 [#meta-1]_ -url home-page str -download_url download-url str -project_urls dict 38.3.0 -author str -author_email author-email str -maintainer str -maintainer_email maintainer-email str -classifiers classifier file:, list-comma -license str -license_files license_file list-comma 42.0.0 -description summary file:, str -long_description long-description file:, str -long_description_content_type str 38.6.0 -keywords list-comma -platforms platform list-comma -provides list-comma -requires list-comma -obsoletes list-comma -============================== ================= ================= =============== ========== - -**Notes**: - -.. [#meta-1] The ``version`` file attribute has only been supported since 39.2.0. - - A version loaded using the ``file:`` directive must comply with PEP 440. - It is easy to accidentally put something other than a valid version - string in such a file, so validation is stricter in this case. - - -Options -------- - -======================= =================================== =============== ==================== -Key Type Minimum Version Notes -======================= =================================== =============== ==================== -zip_safe bool -setup_requires list-semi 36.7.0 -install_requires file:, list-semi **BETA** [#opt-2]_, [#opt-6]_ -extras_require file:, section **BETA** [#opt-2]_, [#opt-6]_ -python_requires str 34.4.0 -entry_points file:, section 51.0.0 -scripts list-comma -eager_resources list-comma -dependency_links list-comma -tests_require list-semi -include_package_data bool -packages find:, find_namespace:, list-comma [#opt-3]_ -package_dir dict -package_data section [#opt-1]_ -exclude_package_data section -namespace_packages list-comma [#opt-5]_ -py_modules list-comma 34.4.0 -data_files section 40.6.0 [#opt-4]_ -======================= =================================== =============== ==================== - -**Notes**: - -.. [#opt-1] In the ``package_data`` section, a key named with a single asterisk - (``*``) refers to all packages, in lieu of the empty string used in ``setup.py``. - -.. [#opt-2] In ``install_requires`` and ``extras_require``, values are parsed as ``list-semi``. - This implies that in order to include markers, each requirement **must** be *dangling* - in a new line: - - .. code-block:: ini - - [options] - install_requires = - importlib-metadata; python_version<"3.8" - - [options.extras_require] - all = - importlib-metadata; python_version < "3.8" - -.. [#opt-3] The ``find:`` and ``find_namespace:`` directive can be further configured - in a dedicated subsection ``options.packages.find``. This subsection accepts the - same keys as the ``setuptools.find_packages`` and the - ``setuptools.find_namespace_packages`` function: - ``where``, ``include``, and ``exclude``. - - The ``find_namespace:`` directive is supported since Python >=3.3. - -.. [#opt-4] ``data_files`` is deprecated and should be avoided. - Please check :doc:`/userguide/datafiles` for more information. - -.. [#opt-5] ``namespace_packages`` is deprecated in favour of native/implicit - namespaces (:pep:`420`). Check :doc:`the Python Packaging User Guide - ` for more information. - -.. [#opt-6] ``file:`` directives for reading requirements are supported since version 62.6. - The format for the file resembles a ``requirements.txt`` file, - however please keep in mind that all non-comment lines must conform with :pep:`508` - (``pip``-specify syntaxes, e.g. ``-c/-r/-e`` flags, are not supported). - Library developers should avoid tightly pinning their dependencies to a specific - version (e.g. via a "locked" requirements file). - - -Compatibility with other tools -============================== - -Historically, several tools explored declarative package configuration -in parallel. And several of them chose to place the packaging -configuration within the project's :file:`setup.cfg` file. -One of the first was ``distutils2``, which development has stopped in -2013. Other include ``pbr`` which is still under active development or -``d2to1``, which was a plug-in that backports declarative configuration -to ``distutils``, but has had no release since Oct. 2015. -As a way to harmonize packaging tools, ``setuptools``, having held the -position of *de facto* standard, has gradually integrated those -features as part of its core features. - -Still this has lead to some confusion and feature incompatibilities: - -- some tools support features others don't; -- some have similar features but the declarative syntax differs; - -The table below tries to summarize the differences. But, please, refer -to each tool documentation for up-to-date information. - -=========================== ========== ========== ===== === -feature setuptools distutils2 d2to1 pbr -=========================== ========== ========== ===== === -[metadata] description-file S Y Y Y -[files] S Y Y Y -entry_points Y Y Y S -[backwards_compat] N Y Y Y -=========================== ========== ========== ===== === - -Y: supported, N: unsupported, S: syntax differs (see -:ref:`above example`). - -Also note that some features were only recently added to ``setuptools``. -Please refer to the previous sections to find out when. diff --git a/docs/source/userguide/dependency_management.rst b/docs/source/userguide/dependency_management.rst deleted file mode 100644 index 33aaf6c65..000000000 --- a/docs/source/userguide/dependency_management.rst +++ /dev/null @@ -1,422 +0,0 @@ -===================================== -Dependencies Management in Setuptools -===================================== - -There are three types of dependency styles offered by setuptools: -1) build system requirement, 2) required dependency and 3) optional -dependency. - -Each dependency, regardless of type, needs to be specified according to :pep:`508` -and :pep:`440`. -This allows adding version :pep:`range restrictions <440#version-specifiers>` -and :ref:`environment markers `. - - -.. _build-requires: - -Build system requirement -======================== - -After organizing all the scripts and files and getting ready for packaging, -there needs to be a way to specify what programs and libraries are actually needed -do the packaging (in our case, ``setuptools`` of course). -This needs to be specified in your ``pyproject.toml`` file -(if you have forgot what this is, go to :doc:`/userguide/quickstart` or :doc:`/build_meta`): - -.. code-block:: toml - - [build-system] - requires = ["setuptools"] - #... - -Please note that you should also include here any other ``setuptools`` plugin -(e.g., :pypi:`setuptools-scm`, :pypi:`setuptools-golang`, :pypi:`setuptools-rust`) -or build-time dependency (e.g., :pypi:`Cython`, :pypi:`cppy`, :pypi:`pybind11`). - -.. note:: - In previous versions of ``setuptools``, - this used to be accomplished with the ``setup_requires`` keyword but is - now considered deprecated in favor of the :pep:`517` style described above. - To peek into how this legacy keyword is used, consult our :doc:`guide on - deprecated practice (WIP) `. - - -.. _Declaring Dependencies: - -Declaring required dependency -============================= -This is where a package declares its core dependencies, without which it won't -be able to run. ``setuptools`` supports automatically downloading and installing -these dependencies when the package is installed. Although there is more -finesse to it, let's start with a simple example. - -.. tab:: pyproject.toml - - .. code-block:: toml - - [project] - # ... - dependencies = [ - "docutils", - "BazSpam == 1.1", - ] - # ... - -.. tab:: setup.cfg - - .. code-block:: ini - - [options] - #... - install_requires = - docutils - BazSpam ==1.1 - -.. tab:: setup.py - - .. code-block:: python - - setup( - ..., - install_requires=[ - 'docutils', - 'BazSpam ==1.1', - ], - ) - - -When your project is installed (e.g., using :pypi:`pip`), all of the dependencies not -already installed will be located (via `PyPI`_), downloaded, built (if necessary), -and installed and 2) Any scripts in your project will be installed with wrappers -that verify the availability of the specified dependencies at runtime. - - -.. _environment-markers: - -Platform specific dependencies ------------------------------- -Setuptools offers the capability to evaluate certain conditions before blindly -installing everything listed in ``install_requires``. This is great for platform -specific dependencies. For example, the ``enum`` package was added in Python -3.4, therefore, package that depends on it can elect to install it only when -the Python version is older than 3.4. To accomplish this - -.. tab:: pyproject.toml - - .. code-block:: toml - - [project] - # ... - dependencies = [ - "enum34; python_version<'3.4'", - ] - # ... - -.. tab:: setup.cfg - - .. code-block:: ini - - [options] - #... - install_requires = - enum34;python_version<'3.4' - -.. tab:: setup.py - - .. code-block:: python - - setup( - ..., - install_requires=[ - "enum34;python_version<'3.4'", - ], - ) - -Similarly, if you also wish to declare ``pywin32`` with a minimal version of 1.0 -and only install it if the user is using a Windows operating system: - -.. tab:: pyproject.toml - - .. code-block:: toml - - [project] - # ... - dependencies = [ - "enum34; python_version<'3.4'", - "pywin32 >= 1.0; platform_system=='Windows'", - ] - # ... - -.. tab:: setup.cfg - - .. code-block:: ini - - [options] - #... - install_requires = - enum34;python_version<'3.4' - pywin32 >= 1.0;platform_system=='Windows' - -.. tab:: setup.py - - .. code-block:: python - - setup( - ..., - install_requires=[ - "enum34;python_version<'3.4'", - "pywin32 >= 1.0;platform_system=='Windows'", - ], - ) - -The environmental markers that may be used for testing platform types are -detailed in :pep:`508`. - -.. seealso:: - If environment markers are not enough an specific use case, - you can also consider creating a :ref:`backend wrapper ` - to implement custom detection logic. - - -Direct URL dependencies ------------------------ - -.. attention:: - `PyPI`_ and other standards-conformant package indices **do not** accept - packages that declare dependencies using direct URLs. ``pip`` will accept them - when installing packages from the local filesystem or from another URL, - however. - -Dependencies that are not available on a package index but can be downloaded -elsewhere in the form of a source repository or archive may be specified -using a variant of :pep:`PEP 440's direct references <440#direct-references>`: - -.. tab:: pyproject.toml - - .. code-block:: toml - - [project] - # ... - dependencies = [ - "Package-A @ git+https://example.net/package-a.git@main", - "Package-B @ https://example.net/archives/package-b.whl", - ] - -.. tab:: setup.cfg - - .. code-block:: ini - - [options] - #... - install_requires = - Package-A @ git+https://example.net/package-a.git@main - Package-B @ https://example.net/archives/package-b.whl - -.. tab:: setup.py - - .. code-block:: python - - setup( - install_requires=[ - "Package-A @ git+https://example.net/package-a.git@main", - "Package-B @ https://example.net/archives/package-b.whl", - ], - ..., - ) - -For source repository URLs, a list of supported protocols and VCS-specific -features such as selecting certain branches or tags can be found in pip's -documentation on `VCS support `_. -Supported formats for archive URLs are sdists and wheels. - - -Optional dependencies -===================== -Setuptools allows you to declare dependencies that are not installed by default. -This effectively means that you can create a "variant" of your package with a -set of extra functionalities. - -For example, let's consider a ``Package-A`` that offers -optional PDF support and requires two other dependencies for it to work: - -.. tab:: pyproject.toml - - .. code-block:: toml - - [project] - name = "Package-A" - # ... - [project.optional-dependencies] - PDF = ["ReportLab>=1.2", "RXP"] - -.. tab:: setup.cfg - - .. code-block:: ini - - [metadata] - name = Package-A - - [options.extras_require] - PDF = - ReportLab>=1.2 - RXP - - -.. tab:: setup.py - - .. code-block:: python - - setup( - name="Package-A", - ..., - extras_require={ - "PDF": ["ReportLab>=1.2", "RXP"], - }, - ) - -.. sidebar:: - - .. tip:: - It is also convenient to declare optional requirements for - ancillary tasks such as running tests and or building docs. - -The name ``PDF`` is an arbitrary :pep:`identifier <685>` of such a list of dependencies, to -which other components can refer and have them installed. - -A use case for this approach is that other package can use this "extra" for their -own dependencies. For example, if ``Package-B`` needs ``Package-A`` with PDF support -installed, it might declare the dependency like this: - -.. tab:: pyproject.toml - - .. code-block:: toml - - [project] - name = "Package-B" - # ... - dependencies = [ - "Package-A[PDF]" - ] - -.. tab:: setup.cfg - - .. code-block:: ini - - [metadata] - name = Package-B - #... - - [options] - #... - install_requires = - Package-A[PDF] - -.. tab:: setup.py - - .. code-block:: python - - setup( - name="Package-B", - install_requires=["Package-A[PDF]"], - ..., - ) - -This will cause ``ReportLab`` to be installed along with ``Package-A``, if ``Package-B`` is -installed -- even if ``Package-A`` was already installed. In this way, a project -can encapsulate groups of optional "downstream dependencies" under a feature -name, so that packages that depend on it don't have to know what the downstream -dependencies are. If a later version of ``Package-A`` builds in PDF support and -no longer needs ``ReportLab``, or if it ends up needing other dependencies besides -``ReportLab`` in order to provide PDF support, ``Package-B``'s setup information does -not need to change, but the right packages will still be installed if needed. - -.. tip:: - Best practice: if a project ends up no longer needing any other packages to - support a feature, it should keep an empty requirements list for that feature - in its ``extras_require`` argument, so that packages depending on that feature - don't break (due to an invalid feature name). - -.. warning:: - Historically ``setuptools`` also used to support extra dependencies in console - scripts, for example: - - .. tab:: setup.cfg - - .. code-block:: ini - - [metadata] - name = Package-A - #... - - [options] - #... - entry_points= - [console_scripts] - rst2pdf = project_a.tools.pdfgen [PDF] - rst2html = project_a.tools.htmlgen - - .. tab:: setup.py - - .. code-block:: python - - setup( - name="Package-A", - ..., - entry_points={ - "console_scripts": [ - "rst2pdf = project_a.tools.pdfgen [PDF]", - "rst2html = project_a.tools.htmlgen", - ], - }, - ) - - This syntax indicates that the entry point (in this case a console script) - is only valid when the PDF extra is installed. It is up to the installer - to determine how to handle the situation where PDF was not indicated - (e.g., omit the console script, provide a warning when attempting to load - the entry point, assume the extras are present and let the implementation - fail later). - - **However**, ``pip`` and other tools might not support this use case for extra - dependencies, therefore this practice is considered **deprecated**. - See :doc:`PyPUG:specifications/entry-points`. - - -Python requirement -================== -In some cases, you might need to specify the minimum required python version. -This can be configured as shown in the example below. - -.. tab:: pyproject.toml - - .. code-block:: toml - - [project] - name = "Package-B" - requires-python = ">=3.6" - # ... - -.. tab:: setup.cfg - - .. code-block:: ini - - [metadata] - name = Package-B - #... - - [options] - #... - python_requires = >=3.6 - -.. tab:: setup.py - - .. code-block:: python - - setup( - name="Package-B", - python_requires=">=3.6", - ..., - ) - - -.. _PyPI: https://pypi.org diff --git a/docs/source/userguide/development_mode.rst b/docs/source/userguide/development_mode.rst deleted file mode 100644 index 6f9f5417f..000000000 --- a/docs/source/userguide/development_mode.rst +++ /dev/null @@ -1,271 +0,0 @@ -Development Mode (a.k.a. "Editable Installs") -============================================= - -When creating a Python project, developers usually want to implement and test -changes iteratively, before cutting a release and preparing a distribution archive. - -In normal circumstances this can be quite cumbersome and require the developers -to manipulate the ``PYTHONPATH`` environment variable or to continuously re-build -and re-install the project. - -To facilitate iterative exploration and experimentation, setuptools allows -users to instruct the Python interpreter and its import machinery to load the -code under development directly from the project folder without having to -copy the files to a different location in the disk. -This means that changes in the Python source code can immediately take place -without requiring a new installation. - -You can enter this "development mode" by performing an :doc:`editable installation -` inside of a :term:`virtual environment`, -using :doc:`pip's ` ``-e/--editable`` flag, as shown below: - -.. code-block:: bash - - $ cd your-python-project - $ python -m venv .venv - # Activate your environemt with: - # `source .venv/bin/activate` on Unix/macOS - # or `.venv\Scripts\activate` on Windows - - $ pip install --editable . - - # Now you have access to your package - # as if it was installed in .venv - $ python -c "import your_python_project" - - -An "editable installation" works very similarly to a regular install with -``pip install .``, except that it only installs your package dependencies, -metadata and wrappers for :ref:`console and GUI scripts `. -Under the hood, setuptools will try to create a special :mod:`.pth file ` -in the target directory (usually ``site-packages``) that extends the -``PYTHONPATH`` or install a custom :doc:`import hook `. - -When you're done with a given development task, you can simply uninstall your -package (as you would normally do with ``pip uninstall ``). - -Please note that, by default an editable install will expose at least all the -files that would be available in a regular installation. However, depending on -the file and directory organization in your project, it might also expose -as a side effect files that would not be normally available. -This is allowed so you can iteratively create new Python modules. -Please have a look on the following section if you are looking for a different behaviour. - -.. admonition:: Virtual Environments - - You can think about virtual environments as "isolated Python runtime deployments" - that allow users to install different sets of libraries and tools without - messing with the global behaviour of the system. - - They are a safe way of testing new projects and can be created easily - with the :mod:`venv` module from the standard library. - - Please note however that depending on your operating system or distribution, - ``venv`` might not come installed by default with Python. For those cases, - you might need to use the OS package manager to install it. - For example, in Debian/Ubuntu-based systems you can obtain it via: - - .. code-block:: bash - - sudo apt install python3-venv - - Alternatively, you can also try installing :pypi:`virtualenv`. - More information is available on the Python Packaging User Guide on - :doc:`PyPUG:guides/installing-using-pip-and-virtual-environments`. - -.. note:: - .. versionchanged:: v64.0.0 - Editable installation hooks implemented according to :pep:`660`. - Support for :pep:`namespace packages <420>` is still **EXPERIMENTAL**. - - -"Strict" editable installs --------------------------- - -When thinking about editable installations, users might have the following -expectations: - -1. It should allow developers to add new files (or split/rename existing ones) - and have them automatically exposed. -2. It should behave as close as possible to a regular installation and help - users to detect problems (e.g. new files not being included in the distribution). - -Unfortunately these expectations are in conflict with each other. -To solve this problem ``setuptools`` allows developers to choose a more -*"strict"* mode for the editable installation. This can be done by passing -a special *configuration setting* via :pypi:`pip`, as indicated below: - -.. code-block:: bash - - pip install -e . --config-settings editable_mode=strict - -In this mode, new files **won't** be exposed and the editable installs will -try to mimic as much as possible the behavior of a regular install. -Under the hood, ``setuptools`` will create a tree of file links in an auxiliary -directory (``$your_project_dir/build``) and add it to ``PYTHONPATH`` via a -:mod:`.pth file `. (Please be careful to not delete this repository -by mistake otherwise your files may stop being accessible). - -.. warning:: - Strict editable installs require auxiliary files to be placed in a - ``build/__editable__.*`` directory (relative to your project root). - - Please be careful to not remove this directory while testing your project, - otherwise your editable installation may be compromised. - - You can remove the ``build/__editable__.*`` directory after uninstalling. - - -.. note:: - .. versionadded:: v64.0.0 - Added new *strict* mode for editable installations. - The exact details of how this mode is implemented may vary. - - -Limitations ------------ - -- The *editable* term is used to refer only to Python modules - inside the package directories. Non-Python files, external (data) files, - executable script files, binary extensions, headers and metadata may be - exposed as a *snapshot* of the version they were at the moment of the - installation. -- Adding new dependencies, entry-points or changing your project's metadata - require a fresh "editable" re-installation. -- Console scripts and GUI scripts **MUST** be specified via :doc:`entry-points - ` to work properly. -- *Strict* editable installs require the file system to support - either :wiki:`symbolic ` or :wiki:`hard links `. - This installation mode might also generate auxiliary files under the project directory. -- There is *no guarantee* that the editable installation will be performed - using a specific technique. Depending on each project, ``setuptools`` may - select a different approach to ensure the package is importable at runtime. -- There is *no guarantee* that files outside the top-level package directory - will be accessible after an editable install. -- There is *no guarantee* that attributes like ``__path__`` or ``__file__`` - will correspond to the exact location of the original files (e.g., - ``setuptools`` might employ file links to perform the editable installation). - Users are encouraged to use tools like :mod:`importlib.resources` or - :mod:`importlib.metadata` when trying to access package files directly. -- Editable installations may not work with - :doc:`namespaces created with pkgutil or pkg_resources - `. - Please use :pep:`420`-style implicit namespaces [#namespaces]_. -- Support for :pep:`420`-style implicit namespace packages for - projects structured using :ref:`flat-layout` is still **experimental**. - If you experience problems, you can try converting your package structure - to the :ref:`src-layout`. -- File system entries in the current working directory - whose names coincidentally match installed packages - may take precedence in :doc:`Python's import system `. - Users are encouraged to avoid such scenarios [#cwd]_. - -.. attention:: - Editable installs are **not a perfect replacement for regular installs** - in a test environment. When in doubt, please test your projects as - installed via a regular wheel. There are tools in the Python ecosystem, - like :pypi:`tox` or :pypi:`nox`, that can help you with that - (when used with appropriate configuration). - - -Legacy Behavior ---------------- - -If your project is not compatible with the new "editable installs" or you wish -to replicate the legacy behavior, for the time being you can also perform the -installation in the ``compat`` mode: - -.. code-block:: bash - - pip install -e . --config-settings editable_mode=compat - -This installation mode will try to emulate how ``python setup.py develop`` -works (still within the context of :pep:`660`). - -.. warning:: - The ``compat`` mode is *transitional* and will be removed in - future versions of ``setuptools``, it exists only to help during the - migration period. - Also note that support for this mode is limited: - it is safe to assume that the ``compat`` mode is offered "as is", and - improvements are unlikely to be implemented. - Users are encouraged to try out the new editable installation techniques - and make the necessary adaptations. - -If the ``compat`` mode does not work for you, you can also disable the -:pep:`editable install <660>` hooks in ``setuptools`` by setting an environment -variable: - -.. code-block:: - - SETUPTOOLS_ENABLE_FEATURES="legacy-editable" - -This *may* cause the installer (e.g. ``pip``) to effectively run the "legacy" -installation command: ``python setup.py develop`` [#installer]_. - - -How editable installations work -------------------------------- - -*Advanced topic* - -There are many techniques that can be used to expose packages under development -in such a way that they are available as if they were installed. -Depending on the project file structure and the selected mode, ``setuptools`` -will choose one of these approaches for the editable installation [#criteria]_. - -A non-exhaustive list of implementation mechanisms is presented below. -More information is available on the text of :pep:`PEP 660 <660#what-to-put-in-the-wheel>`. - -- A static ``.pth`` file [#static_pth]_ can be added to one of the directories - listed in :func:`site.getsitepackages` or :func:`site.getusersitepackages` to - extend :obj:`sys.path`. -- A directory containing a *farm of file links* that mimic the - project structure and point to the original files can be employed. - This directory can then be added to :obj:`sys.path` using a static ``.pth`` file. -- A dynamic ``.pth`` file [#dynamic_pth]_ can also be used to install an - "import :term:`finder`" (:obj:`~importlib.abc.MetaPathFinder` or - :obj:`~importlib.abc.PathEntryFinder`) that will hook into Python's - :doc:`import system ` machinery. - -.. attention:: - ``Setuptools`` offers **no guarantee** of which technique will be used to - perform an editable installation. This will vary from project to project - and may change depending on the specific version of ``setuptools`` being - used. - - ----- - -.. rubric:: Notes - -.. [#namespaces] - You *may* be able to use *strict* editable installations with namespace - packages created with ``pkgutil`` or ``pkg_namespaces``, however this is not - officially supported. - -.. [#cwd] - Techniques like the :ref:`src-layout` or tooling-specific options like - `tox's changedir `_ - can be used to prevent such kinds of situations (checkout `this blog post - `_ for more - insights). - -.. [#installer] - For this workaround to work, the installer tool needs to support legacy - editable installations. (Future versions of ``pip``, for example, may drop - support for this feature). - -.. [#criteria] - ``setuptools`` strives to find a balance between allowing the user to see - the effects of project files being edited while still trying to keep the - editable installation as similar as possible to a regular installation. - -.. [#static_pth] - i.e., a ``.pth`` file where each line correspond to a path that should be - added to :obj:`sys.path`. See :mod:`Site-specific configuration hook `. - -.. [#dynamic_pth] - i.e., a ``.pth`` file that starts where each line starts with an ``import`` - statement and executes arbitrary Python code. See :mod:`Site-specific - configuration hook `. diff --git a/docs/source/userguide/distribution.rst b/docs/source/userguide/distribution.rst deleted file mode 100644 index ae2dc4a45..000000000 --- a/docs/source/userguide/distribution.rst +++ /dev/null @@ -1,198 +0,0 @@ -.. _Specifying Your Project's Version: - -Specifying Your Project's Version -================================= - -Setuptools can work well with most versioning schemes. Over the years, -setuptools has tried to closely follow the :pep:`440` scheme, but it -also supports legacy versions. There are, however, a -few special things to watch out for, in order to ensure that setuptools and -other tools can always tell what version of your package is newer than another -version. Knowing these things will also help you correctly specify what -versions of other projects your project depends on. - -A version consists of an alternating series of release numbers and -`pre-release `_ -or `post-release `_ tags. A -release number is a series of digits punctuated by -dots, such as ``2.4`` or ``0.5``. Each series of digits is treated -numerically, so releases ``2.1`` and ``2.1.0`` are different ways to spell the -same release number, denoting the first subrelease of release 2. But ``2.10`` -is the *tenth* subrelease of release 2, and so is a different and newer release -from ``2.1`` or ``2.1.0``. Leading zeros within a series of digits are also -ignored, so ``2.01`` is the same as ``2.1``, and different from ``2.0.1``. - -Following a release number, you can have either a pre-release or post-release -tag. Pre-release tags make a version be considered *older* than the version -they are appended to. So, revision ``2.4`` is *newer* than release candidate -``2.4rc1``, which in turn is newer than beta release ``2.4b1`` or -alpha release ``2.4a1``. Postrelease tags make -a version be considered *newer* than the version they are appended to. So, -revisions like ``2.4.post1`` are newer than ``2.4``, but *older* -than ``2.4.1`` (which has a higher release number). - -In the case of legacy versions (for example, ``2.4pl1``), they are considered -older than non-legacy versions. Taking that in count, a revision ``2.4pl1`` -is *older* than ``2.4``. Note that ``2.4pl1`` is not :pep:`440`-compliant. - -A pre-release tag is a series of letters that are alphabetically before -"final". Some examples of prerelease tags would include ``alpha``, ``beta``, -``a``, ``c``, ``dev``, and so on. You do not have to place a dot or dash -before the prerelease tag if it's immediately after a number, but it's okay to -do so if you prefer. Thus, ``2.4c1`` and ``2.4.c1`` and ``2.4-c1`` all -represent release candidate 1 of version ``2.4``, and are treated as identical -by setuptools. Note that only ``a``, ``b``, and ``rc`` are :pep:`440`-compliant -pre-release tags. - -In addition, there are three special prerelease tags that are treated as if -they were ``rc``: ``c``, ``pre``, and ``preview``. So, version -``2.4c1``, ``2.4pre1`` and ``2.4preview1`` are all the exact same version as -``2.4rc1``, and are treated as identical by setuptools. - -A post-release tag is the string ``.post``, followed by a non-negative integer -value. Post-release tags are generally used to separate patch numbers, port -numbers, build numbers, revision numbers, or date stamps from the release -number. For example, the version ``2.4.post1263`` might denote Subversion -revision 1263 of a post-release patch of version ``2.4``. Or you might use -``2.4.post20051127`` to denote a date-stamped post-release. Legacy post-release -tags could be either a series of letters that are alphabetically greater than or -equal to "final", or a dash (``-``) - for example ``2.4-r1263`` or -``2.4-20051127``. - -Notice that after each legacy pre or post-release tag, you are free to place -another release number, followed again by more pre- or post-release tags. For -example, ``0.6a9.dev41475`` could denote Subversion revision 41475 of the in- -development version of the ninth alpha of release 0.6. Notice that ``dev`` is -a pre-release tag, so this version is a *lower* version number than ``0.6a9``, -which would be the actual ninth alpha of release 0.6. But the ``41475`` is -a post-release tag, so this version is *newer* than ``0.6a9.dev``. - -For the most part, setuptools' interpretation of version numbers is intuitive, -but here are a few tips that will keep you out of trouble in the corner cases: - -* Don't stick adjoining pre-release tags together without a dot or number - between them. Version ``1.9adev`` is the ``adev`` prerelease of ``1.9``, - *not* a development pre-release of ``1.9a``. Use ``.dev`` instead, as in - ``1.9a.dev``, or separate the prerelease tags with a number, as in - ``1.9a0dev``. ``1.9a.dev``, ``1.9a0dev``, and even ``1.9a0.dev0`` are - identical versions from setuptools' point of view, so you can use whatever - scheme you prefer. Of these examples, only ``1.9a0.dev0`` is - :pep:`440`-compliant. - -* If you want to be certain that your chosen numbering scheme works the way - you think it will, you can use the ``pkg_resources.parse_version()`` function - to compare different version numbers:: - - >>> from pkg_resources import parse_version - >>> parse_version("1.9.a.dev") == parse_version("1.9a0dev") - True - >>> parse_version("2.1-rc2") < parse_version("2.1") - True - >>> parse_version("0.6a9dev-r41475") < parse_version("0.6a9") - True - -Once you've decided on a version numbering scheme for your project, you can -have setuptools automatically tag your in-development releases with various -pre- or post-release tags. See the following section for more details. - - -Tagging and "Daily Build" or "Snapshot" Releases ------------------------------------------------- - -.. warning:: - Please note that running ``python setup.py ...`` directly is no longer - considered a good practice and that in the future the commands ``egg_info`` - and ``rotate`` will be deprecated. - - As a result, the instructions and information presented in this section - should be considered **transitional** while setuptools don't provide a - mechanism for tagging releases. - - Meanwhile, if you can also consider using :pypi:`setuptools-scm` to achieve - similar objectives. - - -When a set of related projects are under development, it may be important to -track finer-grained version increments than you would normally use for e.g. -"stable" releases. While stable releases might be measured in dotted numbers -with alpha/beta/etc. status codes, development versions of a project often -need to be tracked by revision or build number or even build date. This is -especially true when projects in development need to refer to one another, and -therefore may literally need an up-to-the-minute version of something! - -To support these scenarios, ``setuptools`` allows you to "tag" your source and -egg distributions by adding one or more of the following to the project's -"official" version identifier: - -* A manually-specified pre-release tag, such as "build" or "dev", or a - manually-specified post-release tag, such as a build or revision number - (``--tag-build=STRING, -bSTRING``) - -* An 8-character representation of the build date (``--tag-date, -d``), as - a postrelease tag - -You can add these tags by adding ``egg_info`` and the desired options to -the command line ahead of the ``sdist`` or ``bdist`` commands that you want -to generate a daily build or snapshot for. See the section below on the -:ref:`egg_info ` command for more details. - -(Also, before you release your project, be sure to see the section on -:ref:`Specifying Your Project's Version` for more information about how pre- and -post-release tags affect how version numbers are interpreted. This is -important in order to make sure that dependency processing tools will know -which versions of your project are newer than others). - -Finally, if you are creating builds frequently, and either building them in a -downloadable location or are copying them to a distribution server, you should -probably also check out the :ref:`rotate ` command, which lets you automatically -delete all but the N most-recently-modified distributions matching a glob -pattern. So, you can use a command line like:: - - setup.py egg_info -rbDEV bdist_egg rotate -m.egg -k3 - -to build an egg whose version info includes "DEV-rNNNN" (where NNNN is the -most recent Subversion revision that affected the source tree), and then -delete any egg files from the distribution directory except for the three -that were built most recently. - -If you have to manage automated builds for multiple packages, each with -different tagging and rotation policies, you may also want to check out the -:ref:`alias ` command, which would let each package define an alias like ``daily`` -that would perform the necessary tag, build, and rotate commands. Then, a -simpler script or cron job could just run ``setup.py daily`` in each project -directory. (And, you could also define sitewide or per-user default versions -of the ``daily`` alias, so that projects that didn't define their own would -use the appropriate defaults.) - -Making "Official" (Non-Snapshot) Releases ------------------------------------------ - -When you make an official release, creating source or binary distributions, -you will need to override the tag settings from ``setup.cfg``, so that you -don't end up registering versions like ``foobar-0.7a1.dev-r34832``. This is -easy to do if you are developing on the trunk and using tags or branches for -your releases - just make the change to ``setup.cfg`` after branching or -tagging the release, so the trunk will still produce development snapshots. - -Alternately, if you are not branching for releases, you can override the -default version options on the command line, using something like:: - - setup.py egg_info -Db "" sdist bdist_egg - -The first part of this command (``egg_info -Db ""``) will override the -configured tag information, before creating source and binary eggs. Thus, these -commands will use the plain version from your ``setup.py``, without adding the -build designation string. - -Of course, if you will be doing this a lot, you may wish to create a personal -alias for this operation, e.g.:: - - setup.py alias -u release egg_info -Db "" - -You can then use it like this:: - - setup.py release sdist bdist_egg - -Or of course you can create more elaborate aliases that do all of the above. -See the sections below on the :ref:`egg_info ` and -:ref:`alias ` commands for more ideas. diff --git a/docs/source/userguide/entry_point.rst b/docs/source/userguide/entry_point.rst deleted file mode 100644 index 163ce1d9d..000000000 --- a/docs/source/userguide/entry_point.rst +++ /dev/null @@ -1,571 +0,0 @@ -.. _`entry_points`: - -============ -Entry Points -============ - -Entry points are a type of metadata that can be exposed by packages on installation. -They are a very useful feature of the Python ecosystem, -and come specially handy in two scenarios: - -1. The package would like to provide commands to be run at the terminal. -This functionality is known as *console* scripts. The command may also -open up a GUI, in which case it is known as a *GUI* script. An example -of a console script is the one provided by the :pypi:`pip` package, which -allows you to run commands like ``pip install`` in the terminal. - -2. A package would like to enable customization of its functionalities -via *plugins*. For example, the test framework :pypi:`pytest` allows -customization via the ``pytest11`` entry point, and the syntax -highlighting tool :pypi:`pygments` allows specifying additional styles -using the entry point ``pygments.styles``. - - -.. _console-scripts: - -Console Scripts -=============== - -Let us start with console scripts. -First consider an example without entry points. Imagine a package -defined thus:: - - project_root_directory - ├── pyproject.toml # and/or setup.cfg, setup.py - └── src - └── timmins - ├── __init__.py - └── ... - -with ``__init__.py`` as: - -.. code-block:: python - - def hello_world(): - print("Hello world") - -Now, suppose that we would like to provide some way of executing the -function ``hello_world()`` from the command-line. One way to do this -is to create a file ``src/timmins/__main__.py`` providing a hook as -follows: - -.. code-block:: python - - from . import hello_world - - if __name__ == '__main__': - hello_world() - -Then, after installing the package ``timmins``, we may invoke the ``hello_world()`` -function as follows, through the `runpy `_ -module: - -.. code-block:: bash - - $ python -m timmins - Hello world - -Instead of this approach using ``__main__.py``, you can also create a -user-friendly CLI executable that can be called directly without ``python -m``. -In the above example, to create a command ``hello-world`` that invokes -``timmins.hello_world``, add a console script entry point to your -configuration: - -.. tab:: pyproject.toml - - .. code-block:: toml - - [project.scripts] - hello-world = "timmins:hello_world" - -.. tab:: setup.cfg - - .. code-block:: ini - - [options.entry_points] - console_scripts = - hello-world = timmins:hello_world - -.. tab:: setup.py - - .. code-block:: python - - from setuptools import setup - - setup( - # ..., - entry_points={ - 'console_scripts': [ - 'hello-world = timmins:hello_world', - ] - } - ) - - -After installing the package, a user may invoke that function by simply calling -``hello-world`` on the command line: - -.. code-block:: bash - - $ hello-world - Hello world - -Note that any function configured as a console script, i.e. ``hello_world()`` in -this example, should not accept any arguments. If your function requires any input -from the user, you can use regular command-line argument parsing utilities like -:mod:`argparse` within the body of -the function to parse user input given via :obj:`sys.argv`. - -You may have noticed that we have used a special syntax to specify the function -that must be invoked by the console script, i.e. we have written ``timmins:hello_world`` -with a colon ``:`` separating the package name and the function name. The full -specification of this syntax is discussed in the `last section <#entry-points-syntax>`_ -of this document, and this can be used to specify a function located anywhere in -your package, not just in ``__init__.py``. - -GUI Scripts -=========== - -In addition to ``console_scripts``, Setuptools supports ``gui_scripts``, which -will launch a GUI application without running in a terminal window. - -For example, if we have a project with the same directory structure as before, -with an ``__init__.py`` file containing the following: - -.. code-block:: python - - import PySimpleGUI as sg - - def hello_world(): - sg.Window(title="Hello world", layout=[[]], margins=(100, 50)).read() - -Then, we can add a GUI script entry point: - -.. tab:: pyproject.toml - - .. code-block:: toml - - [project.gui-scripts] - hello-world = "timmins:hello_world" - -.. tab:: setup.cfg - - .. code-block:: ini - - [options.entry_points] - gui_scripts = - hello-world = timmins:hello_world - -.. tab:: setup.py - - .. code-block:: python - - from setuptools import setup - - setup( - # ..., - entry_points={ - 'gui_scripts': [ - 'hello-world = timmins:hello_world', - ] - } - ) - -.. note:: - To be able to import ``PySimpleGUI``, you need to add ``pysimplegui`` to your package dependencies. - See :doc:`/userguide/dependency_management` for more information. - -Now, running: - -.. code-block:: bash - - $ hello-world - -will open a small application window with the title 'Hello world'. - -Note that just as with console scripts, any function configured as a GUI script -should not accept any arguments, and any user input can be parsed within the -body of the function. GUI scripts also use the same syntax (discussed in the -`last section <#entry-points-syntax>`_) for specifying the function to be invoked. - -.. note:: - - The difference between ``console_scripts`` and ``gui_scripts`` only affects - Windows systems. [#use_for_scripts]_ ``console_scripts`` are wrapped in a console - executable, so they are attached to a console and can use ``sys.stdin``, - ``sys.stdout`` and ``sys.stderr`` for input and output. ``gui_scripts`` are - wrapped in a GUI executable, so they can be started without a console, but - cannot use standard streams unless application code redirects them. Other - platforms do not have the same distinction. - -.. note:: - - Console and GUI scripts work because behind the scenes, installers like :pypi:`pip` - create wrapper scripts around the function(s) being invoked. For example, - the ``hello-world`` entry point in the above two examples would create a - command ``hello-world`` launching a script like this: [#use_for_scripts]_ - - .. code-block:: python - - import sys - from timmins import hello_world - sys.exit(hello_world()) - -.. _dynamic discovery of services and plugins: - -Advertising Behavior -==================== - -Console/GUI scripts are one use of the more general concept of entry points. Entry -points more generally allow a packager to advertise behavior for discovery by -other libraries and applications. This feature enables "plug-in"-like -functionality, where one library solicits entry points and any number of other -libraries provide those entry points. - -A good example of this plug-in behavior can be seen in -`pytest plugins `_, -where pytest is a test framework that allows other libraries to extend -or modify its functionality through the ``pytest11`` entry point. - -The console/GUI scripts work similarly, where libraries advertise their commands -and tools like ``pip`` create wrapper scripts that invoke those commands. - -Entry Points for Plugins -======================== - -Let us consider a simple example to understand how we can implement entry points -corresponding to plugins. Say we have a package ``timmins`` with the following -directory structure:: - - timmins - ├── pyproject.toml # and/or setup.cfg, setup.py - └── src - └── timmins - └── __init__.py - -and in ``src/timmins/__init__.py`` we have the following code: - -.. code-block:: python - - def hello_world(): - print('Hello world') - -Basically, we have defined a ``hello_world()`` function which will print the text -'Hello world'. Now, let us say we want to print the text 'Hello world' in different -ways. The current function just prints the text as it is - let us say we want another -style in which the text is enclosed within exclamation marks:: - - !!! Hello world !!! - -Let us see how this can be done using plugins. First, let us separate the style of -printing the text from the text itself. In other words, we can change the code in -``src/timmins/__init__.py`` to something like this: - -.. code-block:: python - - def display(text): - print(text) - - def hello_world(): - display('Hello world') - -Here, the ``display()`` function controls the style of printing the text, and the -``hello_world()`` function calls the ``display()`` function to print the text 'Hello -world`. - -Right now the ``display()`` function just prints the text as it is. In order to be able -to customize it, we can do the following. Let us introduce a new *group* of entry points -named ``timmins.display``, and expect plugin packages implementing this entry point -to supply a ``display()``-like function. Next, to be able to automatically discover plugin -packages that implement this entry point, we can use the -:mod:`importlib.metadata` module, -as follows: - -.. code-block:: python - - from importlib.metadata import entry_points - display_eps = entry_points(group='timmins.display') - -.. note:: - Each ``importlib.metadata.EntryPoint`` object is an object containing a ``name``, a - ``group``, and a ``value``. For example, after setting up the plugin package as - described below, ``display_eps`` in the above code will look like this: [#package_metadata]_ - - .. code-block:: python - - ( - EntryPoint(name='excl', value='timmins_plugin_fancy:excl_display', group='timmins.display'), - ..., - ) - -``display_eps`` will now be a list of ``EntryPoint`` objects, each referring to ``display()``-like -functions defined by one or more installed plugin packages. Then, to import a specific -``display()``-like function - let us choose the one corresponding to the first discovered -entry point - we can use the ``load()`` method as follows: - -.. code-block:: python - - display = display_eps[0].load() - -Finally, a sensible behaviour would be that if we cannot find any plugin packages customizing -the ``display()`` function, we should fall back to our default implementation which prints -the text as it is. With this behaviour included, the code in ``src/timmins/__init__.py`` -finally becomes: - -.. code-block:: python - - from importlib.metadata import entry_points - display_eps = entry_points(group='timmins.display') - try: - display = display_eps[0].load() - except IndexError: - def display(text): - print(text) - - def hello_world(): - display('Hello world') - -That finishes the setup on ``timmins``'s side. Next, we need to implement a plugin -which implements the entry point ``timmins.display``. Let us name this plugin -``timmins-plugin-fancy``, and set it up with the following directory structure:: - - timmins-plugin-fancy - ├── pyproject.toml # and/or setup.cfg, setup.py - └── src - └── timmins_plugin_fancy - └── __init__.py - -And then, inside ``src/timmins_plugin_fancy/__init__.py``, we can put a function -named ``excl_display()`` that prints the given text surrounded by exclamation marks: - -.. code-block:: python - - def excl_display(text): - print('!!!', text, '!!!') - -This is the ``display()``-like function that we are looking to supply to the -``timmins`` package. We can do that by adding the following in the configuration -of ``timmins-plugin-fancy``: - -.. tab:: pyproject.toml - - .. code-block:: toml - - # Note the quotes around timmins.display in order to escape the dot . - [project.entry-points."timmins.display"] - excl = "timmins_plugin_fancy:excl_display" - -.. tab:: setup.cfg - - .. code-block:: ini - - [options.entry_points] - timmins.display = - excl = timmins_plugin_fancy:excl_display - -.. tab:: setup.py - - .. code-block:: python - - from setuptools import setup - - setup( - # ..., - entry_points = { - 'timmins.display': [ - 'excl = timmins_plugin_fancy:excl_display' - ] - } - ) - -Basically, this configuration states that we are a supplying an entry point -under the group ``timmins.display``. The entry point is named ``excl`` and it -refers to the function ``excl_display`` defined by the package ``timmins-plugin-fancy``. - -Now, if we install both ``timmins`` and ``timmins-plugin-fancy``, we should get -the following: - -.. code-block:: pycon - - >>> from timmins import hello_world - >>> hello_world() - !!! Hello world !!! - -whereas if we only install ``timmins`` and not ``timmins-plugin-fancy``, we should -get the following: - -.. code-block:: pycon - - >>> from timmins import hello_world - >>> hello_world() - Hello world - -Therefore, our plugin works. - -Our plugin could have also defined multiple entry points under the group ``timmins.display``. -For example, in ``src/timmins_plugin_fancy/__init__.py`` we could have two ``display()``-like -functions, as follows: - -.. code-block:: python - - def excl_display(text): - print('!!!', text, '!!!') - - def lined_display(text): - print(''.join(['-' for _ in text])) - print(text) - print(''.join(['-' for _ in text])) - -The configuration of ``timmins-plugin-fancy`` would then change to: - -.. tab:: pyproject.toml - - .. code-block:: toml - - [project.entry-points."timmins.display"] - excl = "timmins_plugin_fancy:excl_display" - lined = "timmins_plugin_fancy:lined_display" - -.. tab:: setup.cfg - - .. code-block:: ini - - [options.entry_points] - timmins.display = - excl = timmins_plugin_fancy:excl_display - lined = timmins_plugin_fancy:lined_display - -.. tab:: setup.py - - .. code-block:: python - - from setuptools import setup - - setup( - # ..., - entry_points = { - 'timmins.display': [ - 'excl = timmins_plugin_fancy:excl_display', - 'lined = timmins_plugin_fancy:lined_display', - ] - } - ) - -On the ``timmins`` side, we can also use a different strategy of loading entry -points. For example, we can search for a specific display style: - -.. code-block:: python - - display_eps = entry_points(group='timmins.display') - try: - display = display_eps['lined'].load() - except KeyError: - # if the 'lined' display is not available, use something else - ... - -Or we can also load all plugins under the given group. Though this might not -be of much use in our current example, there are several scenarios in which this -is useful: - -.. code-block:: python - - display_eps = entry_points(group='timmins.display') - for ep in display_eps: - display = ep.load() - # do something with display - ... - -Another point is that in this particular example, we have used plugins to -customize the behaviour of a function (``display()``). In general, we can use entry -points to enable plugins to not only customize the behaviour of functions, but also -of entire classes and modules. This is unlike the case of console/GUI scripts, -where entry points can only refer to functions. The syntax used for specifying the -entry points remains the same as for console/GUI scripts, and is discussed in the -`last section <#entry-points-syntax>`_. - -.. tip:: - The recommended approach for loading and importing entry points is the - :mod:`importlib.metadata` module, - which is a part of the standard library since Python 3.8. For older versions of - Python, its backport :pypi:`importlib_metadata` should be used. While using the - backport, the only change that has to be made is to replace ``importlib.metadata`` - with ``importlib_metadata``, i.e. - - .. code-block:: python - - from importlib_metadata import entry_points - ... - -In summary, entry points allow a package to open its functionalities for -customization via plugins. -The package soliciting the entry points need not have any dependency -or prior knowledge about the plugins implementing the entry points, and -downstream users are able to compose functionality by pulling together -plugins implementing the entry points. - -Entry Points Syntax -=================== - -The syntax for entry points is specified as follows:: - - = [:[.[.]*]] - -Here, the square brackets ``[]`` denote optionality and the asterisk ``*`` -denotes repetition. -``name`` is the name of the script/entry point you want to create, the left hand -side of ``:`` is the package or module that contains the object you want to invoke -(think about it as something you would write in an import statement), and the right -hand side is the object you want to invoke (e.g. a function). - -To make this syntax more clear, consider the following examples: - -Package or module - If you supply:: - - = - - as the entry point, where ```` can contain ``.`` in the case - of sub-modules or sub-packages, then, tools in the Python ecosystem will roughly - interpret this value as: - - .. code-block:: python - - import - parsed_value = - -Module-level object - If you supply:: - - = : - - where ```` does not contain any ``.``, this will be roughly interpreted - as: - - .. code-block:: python - - from import - parsed_value = - -Nested object - If you supply:: - - = :.. - - this will be roughly interpreted as: - - .. code-block:: python - - from import - parsed_value = .. - -In the case of console/GUI scripts, this syntax can be used to specify a function, while -in the general case of entry points as used for plugins, it can be used to specify a function, -class or module. - ----- - -.. [#use_for_scripts] - Reference: https://packaging.python.org/en/latest/specifications/entry-points/#use-for-scripts - -.. [#package_metadata] - Reference: https://packaging.python.org/en/latest/guides/creating-and-discovering-plugins/#using-package-metadata diff --git a/docs/source/userguide/ext_modules.rst b/docs/source/userguide/ext_modules.rst deleted file mode 100644 index a59599b27..000000000 --- a/docs/source/userguide/ext_modules.rst +++ /dev/null @@ -1,172 +0,0 @@ -========================== -Building Extension Modules -========================== - -Setuptools can build C/C++ extension modules. The keyword argument -``ext_modules`` of ``setup()`` should be a list of instances of the -:class:`setuptools.Extension` class. - - -For example, let's consider a simple project with only one extension module:: - - - ├── pyproject.toml - └── foo.c - -and all project metadata configuration in the ``pyproject.toml`` file: - -.. code-block:: toml - - # pyproject.toml - [build-system] - requires = ["setuptools"] - build-backend = "setuptools.build_meta" - - [project] - name = "mylib-foo" # as it would appear on PyPI - version = "0.42" - -To instruct setuptools to compile the ``foo.c`` file into the extension module -``mylib.foo``, we need to add a ``setup.py`` file similar to the following: - -.. code-block:: python - - from setuptools import Extension, setup - - setup( - ext_modules=[ - Extension( - name="mylib.foo", # as it would be imported - # may include packages/namespaces separated by `.` - - sources=["foo.c"], # all sources are compiled into a single binary file - ), - ] - ) - -.. seealso:: - You can find more information on the `Python docs about C/C++ extensions`_. - Alternatively, you might also be interested in learn about `Cython`_. - - If you plan to distribute a package that uses extensions across multiple - platforms, :pypi:`cibuildwheel` can also be helpful. - -.. important:: - All files used to compile your extension need to be available on the system - when building the package, so please make sure to include some documentation - on how developers interested in building your package from source - can obtain operating system level dependencies - (e.g. compilers and external binary libraries/artifacts). - - You will also need to make sure that all auxiliary files that are contained - inside your :term:`project` (e.g. C headers authored by you or your team) - are configured to be included in your :term:`sdist `. - Please have a look on our section on :ref:`Controlling files in the distribution`. - - -Compiler and linker options -=========================== - -The command ``build_ext`` builds C/C++ extension modules. It creates -a command line for running the compiler and linker by combining -compiler and linker options from various sources: - -.. Reference: `test_customize_compiler` in distutils/tests/test_sysconfig.py - -* the ``sysconfig`` variables ``CC``, ``CXX``, ``CCSHARED``, - ``LDSHARED``, and ``CFLAGS``, -* the environment variables ``CC``, ``CPP``, - ``CXX``, ``LDSHARED`` and ``CFLAGS``, - ``CPPFLAGS``, ``LDFLAGS``, -* the ``Extension`` attributes ``include_dirs``, - ``library_dirs``, ``extra_compile_args``, ``extra_link_args``, - ``runtime_library_dirs``. - -.. Ignoring AR, ARFLAGS, RANLIB here because they are used by the (obsolete?) build_clib, not build_ext. - -Specifically, if the environment variables ``CC``, ``CPP``, ``CXX``, and ``LDSHARED`` -are set, they will be used instead of the ``sysconfig`` variables of the same names. - -The compiler options appear in the command line in the following order: - -.. Reference: "compiler_so" and distutils.ccompiler.gen_preprocess_options, CCompiler.compile, UnixCCompiler._compile - -* first, the options provided by the ``sysconfig`` variable ``CFLAGS``, -* then, the options provided by the environment variables ``CFLAGS`` and ``CPPFLAGS``, -* then, the options provided by the ``sysconfig`` variable ``CCSHARED``, -* then, a ``-I`` option for each element of ``Extension.include_dirs``, -* finally, the options provided by ``Extension.extra_compile_args``. - -The linker options appear in the command line in the following order: - -.. Reference: "linker_so" and CCompiler.link - -* first, the options provided by environment variables and ``sysconfig`` variables, -* then, a ``-L`` option for each element of ``Extension.library_dirs``, -* then, a linker-specific option like ``-Wl,-rpath`` for each element of ``Extension.runtime_library_dirs``, -* finally, the options provided by ``Extension.extra_link_args``. - -The resulting command line is then processed by the compiler and linker. -According to the GCC manual sections on `directory options`_ and -`environment variables`_, the C/C++ compiler searches for files named in -``#include `` directives in the following order: - -* first, in directories given by ``-I`` options (in left-to-right order), -* then, in directories given by the environment variable ``CPATH`` (in left-to-right order), -* then, in directories given by ``-isystem`` options (in left-to-right order), -* then, in directories given by the environment variable ``C_INCLUDE_PATH`` (for C) and ``CPLUS_INCLUDE_PATH`` (for C++), -* then, in standard system directories, -* finally, in directories given by ``-idirafter`` options (in left-to-right order). - -The linker searches for libraries in the following order: - -* first, in directories given by ``-L`` options (in left-to-right order), -* then, in directories given by the environment variable ``LIBRARY_PATH`` (in left-to-right order). - - -Distributing Extensions compiled with Cython -============================================ - -When your :pypi:`Cython` extension modules *are declared using the* -:class:`setuptools.Extension` *class*, ``setuptools`` will detect at build time -whether Cython is installed or not. - -If Cython is present, then ``setuptools`` will use it to build the ``.pyx`` files. -Otherwise, ``setuptools`` will try to find and compile the equivalent ``.c`` files -(instead of ``.pyx``). These files can be generated using the -`cython command line tool`_. - -You can ensure that Cython is always automatically installed into the build -environment by including it as a :ref:`build dependency ` in -your ``pyproject.toml``: - -.. code-block:: toml - - [build-system] - requires = [..., "cython"] - -Alternatively, you can include the ``.c`` code that is pre-compiled by Cython -into your source distribution, alongside the original ``.pyx`` files (this -might save a few seconds when building from an ``sdist``). -To improve version compatibility, you probably also want to include current -``.c`` files in your :wiki:`revision control system`, and rebuild them whenever -you check changes in for the ``.pyx`` source files. -This will ensure that people tracking your project will be able to build it -without installing Cython, and that there will be no variation due to small -differences in the generate C files. -Please checkout our docs on :ref:`controlling files in the distribution` for -more information. - ----- - -Extension API Reference -======================= - -.. autoclass:: setuptools.Extension - - -.. _Python docs about C/C++ extensions: https://docs.python.org/3/extending/extending.html -.. _Cython: https://cython.readthedocs.io/en/stable/index.html -.. _directory options: https://gcc.gnu.org/onlinedocs/gcc/Directory-Options.html -.. _environment variables: https://gcc.gnu.org/onlinedocs/gcc/Environment-Variables.html -.. _cython command line tool: https://cython.readthedocs.io/en/stable/src/userguide/source_files_and_compilation.html diff --git a/docs/source/userguide/extension.rst b/docs/source/userguide/extension.rst deleted file mode 100644 index 6f8cbbb22..000000000 --- a/docs/source/userguide/extension.rst +++ /dev/null @@ -1,308 +0,0 @@ -.. _Creating ``distutils`` Extensions: - -Extending or Customizing Setuptools -=================================== - -Setuptools design is based on the distutils_ package originally distributed -as part of Python's standard library, effectively serving as its successor -(as established in :pep:`632`). - -This means that ``setuptools`` strives to honor the extension mechanisms -provided by ``distutils``, and allows developers to create third party packages -that modify or augment the build process behavior. - -A simple way of doing that is to hook in new or existing -commands and ``setup()`` arguments just by defining "entry points". These -are mappings from command or argument names to a specification of where to -import a handler from. (See the section on :ref:`Dynamic Discovery of -Services and Plugins` for some more background on entry points). - -The following sections describe the most common procedures for extending -the ``distutils`` functionality used by ``setuptools``. - -.. important:: - Any entry-point defined in your ``setup.cfg``, ``setup.py`` or - ``pyproject.toml`` files are not immediately available for use. Your - package needs to be installed first, then ``setuptools`` will be able to - access these entry points. For example consider a ``Project-A`` that - defines entry points. When building ``Project-A``, these will not be - available. If ``Project-B`` declares a :doc:`build system requirement - ` on ``Project-A``, then ``setuptools`` - will be able to use ``Project-A``' customizations. - -Customizing Commands --------------------- - -Both ``setuptools`` and ``distutils`` are structured around the *command design -pattern*. This means that each main action executed when building a -distribution package (such as creating a :term:`sdist ` -or :term:`wheel`) correspond to the implementation of a Python class. - -Originally in ``distutils``, these commands would correspond to actual CLI -arguments that could be passed to the ``setup.py`` script to trigger a -different aspect of the build. In ``setuptools``, however, these command -objects are just a design abstraction that encapsulate logic and help to -organise the code. - -You can overwrite exiting commands (or add new ones) by defining entry -points in the ``distutils.commands`` group. For example, if you wanted to add -a ``foo`` command, you might add something like this to your project: - -.. code-block:: ini - - # setup.cfg - ... - [options.entry_points] - distutils.commands = - foo = mypackage.some_module:foo - -Assuming, of course, that the ``foo`` class in ``mypackage.some_module`` is -a ``setuptools.Command`` subclass (documented below). - -Once a project containing such entry points has been activated on ``sys.path``, -(e.g. by running ``pip install``) the command(s) will be available to any -``setuptools``-based project. In fact, this is -how setuptools' own commands are installed: the setuptools project's setup -script defines entry points for them! - -The commands ``sdist``, ``build_py`` and ``build_ext`` are especially useful -to customize ``setuptools`` builds. Note however that when overwriting existing -commands, you should be very careful to maintain API compatibility. -Custom commands should try to replicate the same overall behavior as the -original classes, and when possible, even inherit from them. - -You should also consider handling exceptions such as ``CompileError``, -``LinkError``, ``LibError``, among others. These exceptions are available in -the ``setuptools.errors`` module. - -.. autoclass:: setuptools.Command - :members: - - -Supporting sdists and editable installs in ``build`` sub-commands -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ - -``build`` sub-commands (like ``build_py`` and ``build_ext``) -are encouraged to implement the following protocol: - -.. autoclass:: setuptools.command.build.SubCommand - :members: - - -Adding Arguments ----------------- - -.. warning:: Adding arguments to setup is discouraged as such arguments - are only supported through imperative execution and not supported through - declarative config. - -Sometimes, your commands may need additional arguments to the ``setup()`` -call. You can enable this by defining entry points in the -``distutils.setup_keywords`` group. For example, if you wanted a ``setup()`` -argument called ``bar_baz``, you might add something like this to your -extension project: - -.. code-block:: ini - - # setup.cfg - ... - [options.entry_points] - distutils.commands = - foo = mypackage.some_module:foo - distutils.setup_keywords = - bar_baz = mypackage.some_module:validate_bar_baz - -The idea here is that the entry point defines a function that will be called -to validate the ``setup()`` argument, if it's supplied. The ``Distribution`` -object will have the initial value of the attribute set to ``None``, and the -validation function will only be called if the ``setup()`` call sets it to -a non-``None`` value. Here's an example validation function:: - - def assert_bool(dist, attr, value): - """Verify that value is True, False, 0, or 1""" - if bool(value) != value: - raise SetupError( - "%r must be a boolean value (got %r)" % (attr,value) - ) - -Your function should accept three arguments: the ``Distribution`` object, -the attribute name, and the attribute value. It should raise a -``SetupError`` (from the ``setuptools.errors`` module) if the argument -is invalid. Remember, your function will only be called with non-``None`` values, -and the default value of arguments defined this way is always ``None``. So, your -commands should always be prepared for the possibility that the attribute will -be ``None`` when they access it later. - -If more than one active distribution defines an entry point for the same -``setup()`` argument, *all* of them will be called. This allows multiple -extensions to define a common argument, as long as they agree on -what values of that argument are valid. - - -Customizing Distribution Options --------------------------------- - -Plugins may wish to extend or alter the options on a ``Distribution`` object to -suit the purposes of that project. For example, a tool that infers the -``Distribution.version`` from SCM-metadata may need to hook into the -option finalization. To enable this feature, Setuptools offers an entry -point ``setuptools.finalize_distribution_options``. That entry point must -be a callable taking one argument (the ``Distribution`` instance). - -If the callable has an ``.order`` property, that value will be used to -determine the order in which the hook is called. Lower numbers are called -first and the default is zero (0). - -Plugins may read, alter, and set properties on the distribution, but each -plugin is encouraged to load the configuration/settings for their behavior -independently. - - -Defining Additional Metadata ----------------------------- - -Some extensible applications and frameworks may need to define their own kinds -of metadata, which they can then access using the :mod:`importlib.metadata` APIs. -Ordinarily, this is done by having plugin -developers include additional files in their ``ProjectName.egg-info`` -directory. However, since it can be tedious to create such files by hand, you -may want to create an extension that will create the necessary files -from arguments to ``setup()``, in much the same way that ``setuptools`` does -for many of the ``setup()`` arguments it adds. See the section below for more -details. - - -.. _Adding new EGG-INFO Files: - -Adding new EGG-INFO Files -~~~~~~~~~~~~~~~~~~~~~~~~~ - -Some extensible applications or frameworks may want to allow third parties to -develop plugins with application or framework-specific metadata included in -the plugins' EGG-INFO directory, for easy access via the ``pkg_resources`` -metadata API. The easiest way to allow this is to create an extension -to be used from the plugin projects' setup scripts (via ``setup_requires``) -that defines a new setup keyword, and then uses that data to write an EGG-INFO -file when the ``egg_info`` command is run. - -The ``egg_info`` command looks for extension points in an ``egg_info.writers`` -group, and calls them to write the files. Here's a simple example of an -extension defining a setup argument ``foo_bar``, which is a list of -lines that will be written to ``foo_bar.txt`` in the EGG-INFO directory of any -project that uses the argument: - -.. code-block:: ini - - # setup.cfg - ... - [options.entry_points] - distutils.setup_keywords = - foo_bar = setuptools.dist:assert_string_list - egg_info.writers = - foo_bar.txt = setuptools.command.egg_info:write_arg - -This simple example makes use of two utility functions defined by setuptools -for its own use: a routine to validate that a setup keyword is a sequence of -strings, and another one that looks up a setup argument and writes it to -a file. Here's what the writer utility looks like:: - - def write_arg(cmd, basename, filename): - argname = os.path.splitext(basename)[0] - value = getattr(cmd.distribution, argname, None) - if value is not None: - value = "\n".join(value) + "\n" - cmd.write_or_delete_file(argname, filename, value) - -As you can see, ``egg_info.writers`` entry points must be a function taking -three arguments: a ``egg_info`` command instance, the basename of the file to -write (e.g. ``foo_bar.txt``), and the actual full filename that should be -written to. - -In general, writer functions should honor the command object's ``dry_run`` -setting when writing files, and use ``logging`` to do any console output. -The easiest way to conform to this requirement is to use -the ``cmd`` object's ``write_file()``, ``delete_file()``, and -``write_or_delete_file()`` methods exclusively for your file operations. -See those methods' docstrings for more details. - - -.. _Adding Support for Revision Control Systems: - -Adding Support for Revision Control Systems -------------------------------------------------- - -If the files you want to include in the source distribution are tracked using -Git, Mercurial or SVN, you can use the following packages to achieve that: - -- Git and Mercurial: :pypi:`setuptools_scm` -- SVN: :pypi:`setuptools_svn` - -If you would like to create a plugin for ``setuptools`` to find files tracked -by another revision control system, you can do so by adding an entry point to -the ``setuptools.file_finders`` group. The entry point should be a function -accepting a single directory name, and should yield all the filenames within -that directory (and any subdirectories thereof) that are under revision -control. - -For example, if you were going to create a plugin for a revision control system -called "foobar", you would write a function something like this: - -.. code-block:: python - - def find_files_for_foobar(dirname): - ... # loop to yield paths that start with `dirname` - -And you would register it in a setup script using something like this: - -.. code-block:: ini - - # setup.cfg - ... - - [options.entry_points] - setuptools.file_finders = - foobar = my_foobar_module:find_files_for_foobar - -Then, anyone who wants to use your plugin can simply install it, and their -local setuptools installation will be able to find the necessary files. - -It is not necessary to distribute source control plugins with projects that -simply use the other source control system, or to specify the plugins in -``setup_requires``. When you create a source distribution with the ``sdist`` -command, setuptools automatically records what files were found in the -``SOURCES.txt`` file. That way, recipients of source distributions don't need -to have revision control at all. However, if someone is working on a package -by checking out with that system, they will need the same plugin(s) that the -original author is using. - -A few important points for writing revision control file finders: - -* Your finder function MUST return relative paths, created by appending to the - passed-in directory name. Absolute paths are NOT allowed, nor are relative - paths that reference a parent directory of the passed-in directory. - -* Your finder function MUST accept an empty string as the directory name, - meaning the current directory. You MUST NOT convert this to a dot; just - yield relative paths. So, yielding a subdirectory named ``some/dir`` under - the current directory should NOT be rendered as ``./some/dir`` or - ``/somewhere/some/dir``, but *always* as simply ``some/dir`` - -* Your finder function SHOULD NOT raise any errors, and SHOULD deal gracefully - with the absence of needed programs (i.e., ones belonging to the revision - control system itself. It *may*, however, use ``logging.warning()`` to - inform the user of the missing program(s). - - -.. _distutils: https://docs.python.org/3.9/library/distutils.html - - -Final Remarks -------------- - -* To use a ``setuptools`` plugin, your users will need to add your package as a - build requirement to their build-system configuration. Please check out our - guides on :doc:`/userguide/dependency_management` for more information. - -* Directly calling ``python setup.py ...`` is considered a **deprecated** practice. - You should not add new commands to ``setuptools`` expecting them to be run - via this interface. diff --git a/docs/source/benchmark_annotators_box.png b/docs/source/userguide/images/benchmark_annotators_box.png similarity index 100% rename from docs/source/benchmark_annotators_box.png rename to docs/source/userguide/images/benchmark_annotators_box.png diff --git a/docs/source/benchmark_scatter.png b/docs/source/userguide/images/benchmark_scatter.png similarity index 100% rename from docs/source/benchmark_scatter.png rename to docs/source/userguide/images/benchmark_scatter.png diff --git a/docs/source/consensus_annotators_box.png b/docs/source/userguide/images/consensus_annotators_box.png similarity index 100% rename from docs/source/consensus_annotators_box.png rename to docs/source/userguide/images/consensus_annotators_box.png diff --git a/docs/source/consensus_dataframe.png b/docs/source/userguide/images/consensus_dataframe.png similarity index 100% rename from docs/source/consensus_dataframe.png rename to docs/source/userguide/images/consensus_dataframe.png diff --git a/docs/source/consensus_projects_box.png b/docs/source/userguide/images/consensus_projects_box.png similarity index 100% rename from docs/source/consensus_projects_box.png rename to docs/source/userguide/images/consensus_projects_box.png diff --git a/docs/source/consensus_scatter.png b/docs/source/userguide/images/consensus_scatter.png similarity index 100% rename from docs/source/consensus_scatter.png rename to docs/source/userguide/images/consensus_scatter.png diff --git a/docs/source/pandas_df.png b/docs/source/userguide/images/pandas_df.png similarity index 100% rename from docs/source/pandas_df.png rename to docs/source/userguide/images/pandas_df.png diff --git a/docs/source/userguide/index.rst b/docs/source/userguide/index.rst index d631c5d8a..70e089d7c 100644 --- a/docs/source/userguide/index.rst +++ b/docs/source/userguide/index.rst @@ -1,22 +1,8 @@ ================================================== -Building and Distributing Packages with Setuptools +User Guide ================================================== -The first step towards sharing a Python library or program is to build a -distribution package [#package-overload]_. This includes adding a set of -additional files containing metadata and configuration to not only instruct -``setuptools`` on how the distribution should be built but also -to help installer (such as :pypi:`pip`) during the installation process. - -This document contains information to help Python developers through this -process. Please check the :doc:`/userguide/quickstart` for an overview of -the workflow. - -Also note that ``setuptools`` is what is known in the community as :pep:`build -backend <517#terminology-and-goals>`, user facing interfaces are provided by tools -such as :pypi:`pip` and :pypi:`build`. To use ``setuptools``, one must -explicitly create a ``pyproject.toml`` file as described :doc:`/build_meta`. - +The first steps... Contents ======== @@ -25,24 +11,6 @@ Contents :maxdepth: 1 quickstart - package_discovery - dependency_management - development_mode - entry_point - datafiles - ext_modules - distribution - miscellaneous - extension - declarative_config - pyproject_config - + setup_project + utilities --- - -.. rubric:: Notes - -.. [#package-overload] - A :term:`Distribution Package` is also referred in the Python community simply as "package" - Unfortunately, this jargon might be a bit confusing for new users because the term package - can also to refer any :term:`directory ` (or sub directory) used to organize - :term:`modules ` and auxiliary files. diff --git a/docs/source/userguide/miscellaneous.rst b/docs/source/userguide/miscellaneous.rst deleted file mode 100644 index 19908e05a..000000000 --- a/docs/source/userguide/miscellaneous.rst +++ /dev/null @@ -1,100 +0,0 @@ -.. _Controlling files in the distribution: - -Controlling files in the distribution -===================================== - -For the most common use cases, ``setuptools`` will automatically find out which -files are necessary for distributing the package. -These include all :term:`pure Python modules ` in the -``py_modules`` or ``packages`` configuration, and the C sources (but not C -headers) listed as part of extensions when creating a :term:`source -distribution (or "sdist")`. - -However, when building more complex packages (e.g. packages that include -non-Python files, or that need to use custom C headers), you might find that -not all files present in your project folder are included in package -:term:`distribution archive `. - -If you are using a :wiki:`Revision Control System`, such as git_ or mercurial_, -and your source distributions only need to include files that you're -tracking in revision control, you can use a ``setuptools`` :ref:`plugin `, such as :pypi:`setuptools-scm` or -:pypi:`setuptools-svn` to automatically include all tracked files into the ``sdist``. - -.. _Using MANIFEST.in: - -Alternatively, if you need finer control over the files (e.g. you don't want to -distribute :wiki:`CI/CD`-related files) or you need automatically generated files, -you can add a ``MANIFEST.in`` file at the root of your project, -to specify any files that the default file location algorithm doesn't catch. - -This file contains instructions that tell ``setuptools`` which files exactly -should be part of the ``sdist`` (or not). -A comprehensive guide to ``MANIFEST.in`` syntax is available at the -:doc:`PyPA's Packaging User Guide `. - -.. attention:: - Please note that ``setuptools`` supports the ``MANIFEST.in``, - and not ``MANIFEST`` (no extension). Any documentation, tutorial or example - that recommends using ``MANIFEST`` (no extension) is likely outdated. - -.. tip:: - The ``MANIFEST.in`` file contains commands that allow you to discover and - manipulate lists of files. There are many commands that can be used with - different objectives, but you should try to not make your ``MANIFEST.in`` - file too fine grained. - - A good idea is to start with a ``graft`` command (to add all - files inside a set of directories) and then fine tune the file selection - by removing the excess or adding isolated files. - -An example of ``MANIFEST.in`` for a simple project that organized according to a -:ref:`src-layout` is: - -.. code-block:: bash - - # MANIFEST.in -- just for illustration - graft src - graft tests - graft docs - # `-> adds all files inside a directory - - include tox.ini - # `-> matches file paths relative to the root of the project - - global-exclude *~ *.py[cod] *.so - # `-> matches file names (regardless of directory) - -Once the correct files are present in the ``sdist``, they can then be used by -binary extensions during the build process, or included in the final -:term:`wheel ` [#build-process]_ if you configure ``setuptools`` with -``include_package_data=True``. - -.. important:: - Please note that, when using ``include_package_data=True``, only files **inside - the package directory** are included in the final ``wheel``, by default. - - So for example, if you create a :term:`Python project ` that uses - :pypi:`setuptools-scm` and have a ``tests`` directory outside of the package - folder, the ``tests`` directory will be present in the ``sdist`` but not in the - ``wheel`` [#wheel-vs-sdist]_. - - See :doc:`/userguide/datafiles` for more information. - ----- - -.. [#build-process] - You can think about the build process as two stages: first the ``sdist`` - will be created and then the ``wheel`` will be produced from that ``sdist``. - -.. [#wheel-vs-sdist] - This happens because the ``sdist`` can contain files that are useful during - development or the build process itself, but not in runtime (e.g. tests, - docs, examples, etc...). - The ``wheel``, on the other hand, is a file format that has been optimized - and is ready to be unpacked into a running installation of Python or - :term:`Virtual Environment`. - Therefore it only contains items that are required during runtime. - -.. _git: https://git-scm.com -.. _mercurial: https://www.mercurial-scm.org diff --git a/docs/source/userguide/package_discovery.rst b/docs/source/userguide/package_discovery.rst deleted file mode 100644 index 9577a5346..000000000 --- a/docs/source/userguide/package_discovery.rst +++ /dev/null @@ -1,589 +0,0 @@ -.. _`package_discovery`: - -======================================== -Package Discovery and Namespace Packages -======================================== - -.. note:: - a full specification for the keywords supplied to ``setup.cfg`` or - ``setup.py`` can be found at :doc:`keywords reference ` - -.. important:: - The examples provided here are only to demonstrate the functionality - introduced. More metadata and options arguments need to be supplied - if you want to replicate them on your system. If you are completely - new to setuptools, the :doc:`quickstart` section is a good place to start. - -``Setuptools`` provides powerful tools to handle package discovery, including -support for namespace packages. - -Normally, you would specify the packages to be included manually in the following manner: - -.. tab:: setup.cfg - - .. code-block:: ini - - [options] - #... - packages = - mypkg - mypkg.subpkg1 - mypkg.subpkg2 - -.. tab:: setup.py - - .. code-block:: python - - setup( - # ... - packages=['mypkg', 'mypkg.subpkg1', 'mypkg.subpkg2'] - ) - -.. tab:: pyproject.toml (**BETA**) [#beta]_ - - .. code-block:: toml - - # ... - [tool.setuptools] - packages = ["mypkg", "mypkg.subpkg1", "mypkg.subpkg2"] - # ... - - -If your packages are not in the root of the repository or do not correspond -exactly to the directory structure, you also need to configure ``package_dir``: - -.. tab:: setup.cfg - - .. code-block:: ini - - [options] - # ... - package_dir = - = src - # directory containing all the packages (e.g. src/mypkg, src/mypkg/subpkg1, ...) - # OR - package_dir = - mypkg = lib - # mypkg.module corresponds to lib/module.py - mypkg.subpkg1 = lib1 - # mypkg.subpkg1.module1 corresponds to lib1/module1.py - mypkg.subpkg2 = lib2 - # mypkg.subpkg2.module2 corresponds to lib2/module2.py - # ... - -.. tab:: setup.py - - .. code-block:: python - - setup( - # ... - package_dir = {"": "src"} - # directory containing all the packages (e.g. src/mypkg, src/mypkg/subpkg1, ...) - ) - - # OR - - setup( - # ... - package_dir = { - "mypkg": "lib", # mypkg.module corresponds to lib/mod.py - "mypkg.subpkg1": "lib1", # mypkg.subpkg1.module1 corresponds to lib1/module1.py - "mypkg.subpkg2": "lib2" # mypkg.subpkg2.module2 corresponds to lib2/module2.py - # ... - ) - -.. tab:: pyproject.toml (**BETA**) [#beta]_ - - .. code-block:: toml - - [tool.setuptools] - # ... - package-dir = {"" = "src"} - # directory containing all the packages (e.g. src/mypkg1, src/mypkg2) - - # OR - - [tool.setuptools.package-dir] - mypkg = "lib" - # mypkg.module corresponds to lib/module.py - "mypkg.subpkg1" = "lib1" - # mypkg.subpkg1.module1 corresponds to lib1/module1.py - "mypkg.subpkg2" = "lib2" - # mypkg.subpkg2.module2 corresponds to lib2/module2.py - # ... - -This can get tiresome really quickly. To speed things up, you can rely on -setuptools automatic discovery, or use the provided tools, as explained in -the following sections. - -.. important:: - Although ``setuptools`` allows developers to create a very complex mapping - between directory names and package names, it is better to *keep it simple* - and reflect the desired package hierarchy in the directory structure, - preserving the same names. - -.. _auto-discovery: - -Automatic discovery -=================== - -.. warning:: Automatic discovery is a **beta** feature and might change in the future. - See :ref:`custom-discovery` for other methods of discovery. - -By default ``setuptools`` will consider 2 popular project layouts, each one with -its own set of advantages and disadvantages [#layout1]_ [#layout2]_ as -discussed in the following sections. - -Setuptools will automatically scan your project directory looking for these -layouts and try to guess the correct values for the :ref:`packages ` and :doc:`py_modules ` configuration. - -.. important:: - Automatic discovery will **only** be enabled if you **don't** provide any - configuration for ``packages`` and ``py_modules``. - If at least one of them is explicitly set, automatic discovery will not take place. - - **Note**: specifying ``ext_modules`` might also prevent auto-discover from - taking place, unless your opt into :doc:`pyproject_config` (which will - disable the backward compatible behaviour). - -.. _src-layout: - -src-layout ----------- -The project should contain a ``src`` directory under the project root and -all modules and packages meant for distribution are placed inside this -directory:: - - project_root_directory - ├── pyproject.toml # AND/OR setup.cfg, setup.py - ├── ... - └── src/ - └── mypkg/ - ├── __init__.py - ├── ... - ├── module.py - ├── subpkg1/ - │   ├── __init__.py - │   ├── ... - │   └── module1.py - └── subpkg2/ - ├── __init__.py - ├── ... - └── module2.py - -This layout is very handy when you wish to use automatic discovery, -since you don't have to worry about other Python files or folders in your -project root being distributed by mistake. In some circumstances it can be -also less error-prone for testing or when using :pep:`420`-style packages. -On the other hand you cannot rely on the implicit ``PYTHONPATH=.`` to fire -up the Python REPL and play with your package (you will need an -`editable install`_ to be able to do that). - -.. _flat-layout: - -flat-layout ------------ -*(also known as "adhoc")* - -The package folder(s) are placed directly under the project root:: - - project_root_directory - ├── pyproject.toml # AND/OR setup.cfg, setup.py - ├── ... - └── mypkg/ - ├── __init__.py - ├── ... - ├── module.py - ├── subpkg1/ - │   ├── __init__.py - │   ├── ... - │   └── module1.py - └── subpkg2/ - ├── __init__.py - ├── ... - └── module2.py - -This layout is very practical for using the REPL, but in some situations -it can be more error-prone (e.g. during tests or if you have a bunch -of folders or Python files hanging around your project root). - -To avoid confusion, file and folder names that are used by popular tools (or -that correspond to well-known conventions, such as distributing documentation -alongside the project code) are automatically filtered out in the case of -*flat-layout*: - -.. autoattribute:: setuptools.discovery.FlatLayoutPackageFinder.DEFAULT_EXCLUDE - -.. autoattribute:: setuptools.discovery.FlatLayoutModuleFinder.DEFAULT_EXCLUDE - -.. warning:: - If you are using auto-discovery with *flat-layout*, ``setuptools`` will - refuse to create :term:`distribution archives ` with - multiple top-level packages or modules. - - This is done to prevent common errors such as accidentally publishing code - not meant for distribution (e.g. maintenance-related scripts). - - Users that purposefully want to create multi-package distributions are - advised to use :ref:`custom-discovery` or the ``src-layout``. - -There is also a handy variation of the *flat-layout* for utilities/libraries -that can be implemented with a single Python file: - -single-module distribution -^^^^^^^^^^^^^^^^^^^^^^^^^^ - -A standalone module is placed directly under the project root, instead of -inside a package folder:: - - project_root_directory - ├── pyproject.toml # AND/OR setup.cfg, setup.py - ├── ... - └── single_file_lib.py - - -.. _custom-discovery: - -Custom discovery -================ - -If the automatic discovery does not work for you -(e.g., you want to *include* in the distribution top-level packages with -reserved names such as ``tasks``, ``example`` or ``docs``, or you want to -*exclude* nested packages that would be otherwise included), you can use -the provided tools for package discovery: - -.. tab:: setup.cfg - - .. code-block:: ini - - [options] - packages = find: - #or - packages = find_namespace: - -.. tab:: setup.py - - .. code-block:: python - - from setuptools import find_packages - # or - from setuptools import find_namespace_packages - -.. tab:: pyproject.toml (**BETA**) [#beta]_ - - .. code-block:: toml - - # ... - [tool.setuptools.packages] - find = {} # Scanning implicit namespaces is active by default - # OR - find = {namespaces = false} # Disable implicit namespaces - - -Finding simple packages ------------------------ -Let's start with the first tool. ``find:`` (``find_packages()``) takes a source -directory and two lists of package name patterns to exclude and include, and -then returns a list of ``str`` representing the packages it could find. To use -it, consider the following directory:: - - mypkg - ├── pyproject.toml # AND/OR setup.cfg, setup.py - └── src - ├── pkg1 - │   └── __init__.py - ├── pkg2 - │   └── __init__.py - ├── additional - │   └── __init__.py - └── pkg - └── namespace - └── __init__.py - -To have setuptools to automatically include packages found -in ``src`` that start with the name ``pkg`` and not ``additional``: - -.. tab:: setup.cfg - - .. code-block:: ini - - [options] - packages = find: - package_dir = - =src - - [options.packages.find] - where = src - include = pkg* - # alternatively: `exclude = additional*` - - .. note:: - ``pkg`` does not contain an ``__init__.py`` file, therefore - ``pkg.namespace`` is ignored by ``find:`` (see ``find_namespace:`` below). - -.. tab:: setup.py - - .. code-block:: python - - setup( - # ... - packages=find_packages( - where='src', - include=['pkg*'], # alternatively: `exclude=['additional*']` - ), - package_dir={"": "src"} - # ... - ) - - - .. note:: - ``pkg`` does not contain an ``__init__.py`` file, therefore - ``pkg.namespace`` is ignored by ``find_packages()`` - (see ``find_namespace_packages()`` below). - -.. tab:: pyproject.toml (**BETA**) [#beta]_ - - .. code-block:: toml - - [tool.setuptools.packages.find] - where = ["src"] - include = ["pkg*"] # alternatively: `exclude = ["additional*"]` - namespaces = false - - .. note:: - When using ``tool.setuptools.packages.find`` in ``pyproject.toml``, - setuptools will consider :pep:`implicit namespaces <420>` by default when - scanning your project directory. - To avoid ``pkg.namespace`` from being added to your package list - you can set ``namespaces = false``. This will prevent any folder - without an ``__init__.py`` file from being scanned. - -.. important:: - ``include`` and ``exclude`` accept strings representing :mod:`glob` patterns. - These patterns should match the **full** name of the Python module (as if it - was written in an ``import`` statement). - - For example if you have ``util`` pattern, it will match - ``util/__init__.py`` but not ``util/files/__init__.py``. - - The fact that the parent package is matched by the pattern will not dictate - if the submodule will be included or excluded from the distribution. - You will need to explicitly add a wildcard (e.g. ``util*``) - if you want the pattern to also match submodules. - -.. _Namespace Packages: - -Finding namespace packages --------------------------- -``setuptools`` provides ``find_namespace:`` (``find_namespace_packages()``) -which behaves similarly to ``find:`` but works with namespace packages. - -Before diving in, it is important to have a good understanding of what -:pep:`namespace packages <420>` are. Here is a quick recap. - -When you have two packages organized as follows: - -.. code-block:: bash - - /Users/Desktop/timmins/foo/__init__.py - /Library/timmins/bar/__init__.py - -If both ``Desktop`` and ``Library`` are on your ``PYTHONPATH``, then a -namespace package called ``timmins`` will be created automatically for you when -you invoke the import mechanism, allowing you to accomplish the following: - -.. code-block:: pycon - - >>> import timmins.foo - >>> import timmins.bar - -as if there is only one ``timmins`` on your system. The two packages can then -be distributed separately and installed individually without affecting the -other one. - -Now, suppose you decide to package the ``foo`` part for distribution and start -by creating a project directory organized as follows:: - - foo - ├── pyproject.toml # AND/OR setup.cfg, setup.py - └── src - └── timmins - └── foo - └── __init__.py - -If you want the ``timmins.foo`` to be automatically included in the -distribution, then you will need to specify: - -.. tab:: setup.cfg - - .. code-block:: ini - - [options] - package_dir = - =src - packages = find_namespace: - - [options.packages.find] - where = src - - ``find:`` won't work because ``timmins`` doesn't contain ``__init__.py`` - directly, instead, you have to use ``find_namespace:``. - - You can think of ``find_namespace:`` as identical to ``find:`` except it - would count a directory as a package even if it doesn't contain ``__init__.py`` - file directly. - -.. tab:: setup.py - - .. code-block:: python - - setup( - # ... - packages=find_namespace_packages(where='src'), - package_dir={"": "src"} - # ... - ) - - When you use ``find_packages()``, all directories without an - ``__init__.py`` file will be disconsidered. - On the other hand, ``find_namespace_packages()`` will scan all - directories. - -.. tab:: pyproject.toml (**BETA**) [#beta]_ - - .. code-block:: toml - - [tool.setuptools.packages.find] - where = ["src"] - - When using ``tool.setuptools.packages.find`` in ``pyproject.toml``, - setuptools will consider :pep:`implicit namespaces <420>` by default when - scanning your project directory. - -After installing the package distribution, ``timmins.foo`` would become -available to your interpreter. - -.. warning:: - Please have in mind that ``find_namespace:`` (setup.cfg), - ``find_namespace_packages()`` (setup.py) and ``find`` (pyproject.toml) will - scan **all** folders that you have in your project directory if you use a - :ref:`flat-layout`. - - If used naïvely, this might result in unwanted files being added to your - final wheel. For example, with a project directory organized as follows:: - - foo - ├── docs - │ └── conf.py - ├── timmins - │ └── foo - │ └── __init__.py - └── tests - └── tests_foo - └── __init__.py - - final users will end up installing not only ``timmins.foo``, but also - ``docs`` and ``tests.tests_foo``. - - A simple way to fix this is to adopt the aforementioned :ref:`src-layout`, - or make sure to properly configure the ``include`` and/or ``exclude`` - accordingly. - -.. tip:: - After :ref:`building your package `, you can have a look if all - the files are correct (nothing missing or extra), by running the following - commands: - - .. code-block:: bash - - tar tf dist/*.tar.gz - unzip -l dist/*.whl - - This requires the ``tar`` and ``unzip`` to be installed in your OS. - On Windows you can also use a GUI program such as 7zip_. - - -Legacy Namespace Packages -========================= -The fact you can create namespace packages so effortlessly above is credited -to :pep:`420`. It used to be more -cumbersome to accomplish the same result. Historically, there were two methods -to create namespace packages. One is the ``pkg_resources`` style supported by -``setuptools`` and the other one being ``pkgutils`` style offered by -``pkgutils`` module in Python. Both are now considered *deprecated* despite the -fact they still linger in many existing packages. These two differ in many -subtle yet significant aspects and you can find out more on `Python packaging -user guide `_. - - -``pkg_resource`` style namespace package ----------------------------------------- -This is the method ``setuptools`` directly supports. Starting with the same -layout, there are two pieces you need to add to it. First, an ``__init__.py`` -file directly under your namespace package directory that contains the -following: - -.. code-block:: python - - __import__("pkg_resources").declare_namespace(__name__) - -And the ``namespace_packages`` keyword in your ``setup.cfg`` or ``setup.py``: - -.. tab:: setup.cfg - - .. code-block:: ini - - [options] - namespace_packages = timmins - -.. tab:: setup.py - - .. code-block:: python - - setup( - # ... - namespace_packages=['timmins'] - ) - -And your directory should look like this - -.. code-block:: bash - - foo - ├── pyproject.toml # AND/OR setup.cfg, setup.py - └── src - └── timmins - ├── __init__.py - └── foo - └── __init__.py - -Repeat the same for other packages and you can achieve the same result as -the previous section. - -``pkgutil`` style namespace package ------------------------------------ -This method is almost identical to the ``pkg_resource`` except that the -``namespace_packages`` declaration is omitted and the ``__init__.py`` -file contains the following: - -.. code-block:: python - - __path__ = __import__('pkgutil').extend_path(__path__, __name__) - -The project layout remains the same and ``pyproject.toml/setup.cfg`` remains the same. - - ----- - - -.. [#beta] - Support for adding build configuration options via the ``[tool.setuptools]`` - table in the ``pyproject.toml`` file is still in **beta** stage. - See :doc:`/userguide/pyproject_config`. -.. [#layout1] https://blog.ionelmc.ro/2014/05/25/python-packaging/#the-structure -.. [#layout2] https://blog.ionelmc.ro/2017/09/25/rehashing-the-src-layout/ - -.. _editable install: https://pip.pypa.io/en/stable/cli/pip_install/#editable-installs -.. _7zip: https://www.7-zip.org diff --git a/docs/source/userguide/pyproject_config.rst b/docs/source/userguide/pyproject_config.rst deleted file mode 100644 index c97984ba0..000000000 --- a/docs/source/userguide/pyproject_config.rst +++ /dev/null @@ -1,260 +0,0 @@ -.. _pyproject.toml config: - ------------------------------------------------------ -Configuring setuptools using ``pyproject.toml`` files ------------------------------------------------------ - -.. note:: New in 61.0.0 - -.. important:: - If compatibility with legacy builds or versions of tools that don't support - certain packaging standards (e.g. :pep:`517` or :pep:`660`), a simple ``setup.py`` - script can be added to your project [#setupcfg-caveats]_ - (while keeping the configuration in ``pyproject.toml``): - - .. code-block:: python - - from setuptools import setup - - setup() - -Starting with :pep:`621`, the Python community selected ``pyproject.toml`` as -a standard way of specifying *project metadata*. -``Setuptools`` has adopted this standard and will use the information contained -in this file as an input in the build process. - -The example below illustrates how to write a ``pyproject.toml`` file that can -be used with ``setuptools``. It contains two TOML tables (identified by the -``[table-header]`` syntax): ``build-system`` and ``project``. -The ``build-system`` table is used to tell the build frontend (e.g. -:pypi:`build` or :pypi:`pip`) to use ``setuptools`` and any other plugins (e.g. -``setuptools-scm``) to build the package. -The ``project`` table contains metadata fields as described by -:doc:`PyPUG:specifications/declaring-project-metadata` guide. - -.. _example-pyproject-config: - -.. code-block:: toml - - [build-system] - requires = ["setuptools", "setuptools-scm"] - build-backend = "setuptools.build_meta" - - [project] - name = "my_package" - authors = [ - {name = "Josiah Carberry", email = "josiah_carberry@brown.edu"}, - ] - description = "My package description" - readme = "README.rst" - requires-python = ">=3.7" - keywords = ["one", "two"] - license = {text = "BSD-3-Clause"} - classifiers = [ - "Framework :: Django", - "Programming Language :: Python :: 3", - ] - dependencies = [ - "requests", - 'importlib-metadata; python_version<"3.8"', - ] - dynamic = ["version"] - - [project.optional-dependencies] - pdf = ["ReportLab>=1.2", "RXP"] - rest = ["docutils>=0.3", "pack ==1.1, ==1.3"] - - [project.scripts] - my-script = "my_package.module:function" - - # ... other project metadata fields as specified in: - # https://packaging.python.org/en/latest/specifications/declaring-project-metadata/ - -.. _setuptools-table: - -Setuptools-specific configuration -================================= - -.. warning:: - Support for declaring configurations not standardized by :pep:`621` - (i.e. the ``[tool.setuptools]`` table), - is still in **beta** stage and might change in future releases. - -While the standard ``project`` table in the ``pyproject.toml`` file covers most -of the metadata used during the packaging process, there are still some -``setuptools``-specific configurations that can be set by users that require -customization. -These configurations are completely optional and probably can be skipped when -creating simple packages. -They are equivalent to the :doc:`/references/keywords` used by the ``setup.py`` -file, and can be set via the ``tool.setuptools`` table: - -========================= =========================== ========================= -Key Value Type (TOML) Notes -========================= =========================== ========================= -``platforms`` array -``zip-safe`` boolean If not specified, ``setuptools`` will try to guess - a reasonable default for the package -``eager-resources`` array -``py-modules`` array See tip below -``packages`` array or ``find`` directive See tip below -``package-dir`` table/inline-table Used when explicitly listing ``packages`` -``namespace-packages`` array **Deprecated** - Use implicit namespaces instead (:pep:`420`) -``package-data`` table/inline-table See :doc:`/userguide/datafiles` -``include-package-data`` boolean ``True`` by default -``exclude-package-data`` table/inline-table -``license-files`` array of glob patterns **Provisional** - likely to change with :pep:`639` - (by default: ``['LICEN[CS]E*', 'COPYING*', 'NOTICE*', 'AUTHORS*']``) -``data-files`` table/inline-table **Discouraged** - check :doc:`/userguide/datafiles` -``script-files`` array **Deprecated** - equivalent to the ``script`` keyword in ``setup.py`` - (should be avoided in favour of ``project.scripts``) -``provides`` array **Ignored by pip** -``obsoletes`` array **Ignored by pip** -========================= =========================== ========================= - -.. note:: - The `TOML value types`_ ``array`` and ``table/inline-table`` are roughly - equivalent to the Python's :obj:`list` and :obj:`dict` data types, respectively. - -Please note that some of these configurations are deprecated or at least -discouraged, but they are made available to ensure portability. -New packages should avoid relying on deprecated/discouraged fields, and -existing packages should consider alternatives. - -.. tip:: - When both ``py-modules`` and ``packages`` are left unspecified, - ``setuptools`` will attempt to perform :ref:`auto-discovery`, which should - cover most popular project directory organization techniques, such as the - :ref:`src-layout` and the :ref:`flat-layout`. - - However if your project does not follow these conventional layouts - (e.g. you want to use a ``flat-layout`` but at the same time have custom - directories at the root of your project), you might need to use the ``find`` - directive [#directives]_ as shown below: - - .. code-block:: toml - - [tool.setuptools.packages.find] - where = ["src"] # list of folders that contain the packages (["."] by default) - include = ["my_package*"] # package names should match these glob patterns (["*"] by default) - exclude = ["my_package.tests*"] # exclude packages matching these glob patterns (empty by default) - namespaces = false # to disable scanning PEP 420 namespaces (true by default) - - Note that the glob patterns in the example above need to be matched - by the **entire** package name. This means that if you specify ``exclude = ["tests"]``, - modules like ``tests.my_package.test1`` will still be included in the distribution - (to remove them, add a wildcard to the end of the pattern: ``"tests*"``). - - Alternatively, you can explicitly list the packages in modules: - - .. code-block:: toml - - [tool.setuptools] - packages = ["my_package"] - - -.. _dynamic-pyproject-config: - -Dynamic Metadata -================ - -Note that in the first example of this page we use ``dynamic`` to identify -which metadata fields are dynamically computed during the build by either -``setuptools`` itself or the plugins installed via ``build-system.requires`` -(e.g. ``setuptools-scm`` is capable of deriving the current project version -directly from the ``git`` :wiki:`version control` system). - -Currently the following fields can be listed as dynamic: ``version``, -``classifiers``, ``description``, ``entry-points``, ``scripts``, -``gui-scripts`` and ``readme``. -When these fields are expected to be provided by ``setuptools`` a -corresponding entry is required in the ``tool.setuptools.dynamic`` table -[#entry-points]_. For example: - -.. code-block:: toml - - # ... - [project] - name = "my_package" - dynamic = ["version", "readme"] - # ... - [tool.setuptools.dynamic] - version = {attr = "my_package.VERSION"} - readme = {file = ["README.rst", "USAGE.rst"]} - -In the ``dynamic`` table, the ``attr`` directive [#directives]_ will read an -attribute from the given module [#attr]_, while ``file`` will read the contents -of all given files and concatenate them in a single string. - -========================== =================== ================================================================================================= -Key Directive Notes -========================== =================== ================================================================================================= -``version`` ``attr``, ``file`` -``readme`` ``file`` Here you can also set ``"content-type"``: - - ``readme = {file = ["README", "USAGE"], content-type = "text/plain"}`` - - If ``content-type`` is not given, ``"text/x-rst"`` is used by default. -``description`` ``file`` One-line text (no line breaks) -``classifiers`` ``file`` Multi-line text with one classifier per line -``entry-points`` ``file`` INI format following :doc:`PyPUG:specifications/entry-points` - (``console_scripts`` and ``gui_scripts`` can be included) -``dependencies`` ``file`` *subset* of the ``requirements.txt`` format - (``#`` comments and blank lines excluded) **BETA** -``optional-dependencies`` ``file`` *subset* of the ``requirements.txt`` format per group - (``#`` comments and blank lines excluded) **BETA** -========================== =================== ================================================================================================= - -Supporting ``file`` for dependencies is meant for a convenience for packaging -applications with possibly strictly versioned dependencies. - -Library packagers are discouraged from using overly strict (or "locked") -dependency versions in their ``dependencies`` and ``optional-dependencies``. - -Currently, when specifying ``optional-dependencies`` dynamically, all of the groups -must be specified dynamically; one can not specify some of them statically and -some of them dynamically. - -Also note that the file format for specifying dependencies resembles a ``requirements.txt`` file, -however please keep in mind that all non-comment lines must conform with :pep:`508` -(``pip``-specify syntaxes, e.g. ``-c/-r/-e`` flags, are not supported). - - -.. note:: - If you are using an old version of ``setuptools``, you might need to ensure - that all files referenced by the ``file`` directive are included in the ``sdist`` - (you can do that via ``MANIFEST.in`` or using plugins such as ``setuptools-scm``, - please have a look on :doc:`/userguide/miscellaneous` for more information). - - .. versionchanged:: 66.1.0 - Newer versions of ``setuptools`` will automatically add these files to the ``sdist``. - ----- - -.. rubric:: Notes - -.. [#setupcfg-caveats] ``pip`` may allow editable install only with ``pyproject.toml`` - and ``setup.cfg``. However, this behavior may not be consistent over various ``pip`` - versions and other packaging-related tools - (``setup.py`` is more reliable on those scenarios). - -.. [#entry-points] Dynamic ``scripts`` and ``gui-scripts`` are a special case. - When resolving these metadata keys, ``setuptools`` will look for - ``tool.setuptool.dynamic.entry-points``, and use the values of the - ``console_scripts`` and ``gui_scripts`` :doc:`entry-point groups - `. - -.. [#directives] In the context of this document, *directives* are special TOML - values that are interpreted differently by ``setuptools`` (usually triggering an - associated function). Most of the times they correspond to a special TOML table - (or inline-table) with a single top-level key. - For example, you can have the ``{find = {where = ["src"], exclude=["tests*"]}}`` - directive for ``tool.setuptools.packages``, or ``{attr = "mymodule.attr"}`` - directive for ``tool.setuptools.dynamic.version``. - -.. [#attr] ``attr`` is meant to be used when the module attribute is statically - specified (e.g. as a string, list or tuple). As a rule of thumb, the - attribute should be able to be parsed with :func:`ast.literal_eval`, and - should not be modified or re-assigned. - -.. _TOML value types: https://toml.io/en/v1.0.0 diff --git a/docs/source/userguide/quickstart.rst b/docs/source/userguide/quickstart.rst index ad1d6d9ee..1ea1b3913 100644 --- a/docs/source/userguide/quickstart.rst +++ b/docs/source/userguide/quickstart.rst @@ -5,6 +5,7 @@ Quickstart Installation ============ +.. _ref_quickstart: SDK is available on PyPI: @@ -12,7 +13,7 @@ SDK is available on PyPI: pip install superannotate -The package officially supports Python 3.6+ and was tested under Linux and +The package officially supports Python 3.7+ and was tested under Linux and Windows (`Anaconda `_) platforms. For certain video related functions to work, ffmpeg package needs to be installed. @@ -58,6 +59,7 @@ To generate a default location (:file:`~/.superannotate/config.json`) config fil Custom config file ~~~~~~~~~~~~~~~~~~~~~~ +.. _ref_custom_config_file: To create a custom config file a new JSON file with key "token" can be created: @@ -67,10 +69,9 @@ To create a custom config file a new JSON file with key "token" can be created: "token" : "" } ----------- Initialization and authorization -________________________________ +========= Include the package in your Python code: @@ -89,11 +90,8 @@ the :ref:`CLI init `. Otherwise to authenticate SDK with the :ref: .. _basic-use: -Basic Use -========= - Creating a project ------------------ +========= To create a new "Vector" project with name "Example Project 1" and description "test": @@ -106,8 +104,7 @@ To create a new "Vector" project with name "Example Project 1" and description Uploading images to project ------------------ - +========= To upload all images with extensions "jpg" or "png" from the @@ -132,3 +129,38 @@ For full list of available functions on projects, see :ref:`ref_projects`. performance improvement. +Working with images +========= + + +To download the image one can use: + +.. code-block:: python + + image = "example_image1.jpg" + + sa.download_image(project, image, "") + +To download image annotations: + +.. code-block:: python + + sa.download_image_annotations(project, image, "") + +Upload back to the platform with: + +.. code-block:: python + + sa.upload_image_annotations(project, image, "") + + + + +Working with team contributors +========= + +A team contributor can be invited to the team with: + +.. code-block:: python + + sa.invite_contributors_to_team(emails=["admin@superannotate.com"], admin=False) diff --git a/docs/source/userguide/setup_project.rst b/docs/source/userguide/setup_project.rst new file mode 100644 index 000000000..960e42ab0 --- /dev/null +++ b/docs/source/userguide/setup_project.rst @@ -0,0 +1,160 @@ +========== +Setup Project +========== + + +Creating a project +----------------- + +To create a new "Vector" project with name "Example Project 1" and description +"test": + +.. code-block:: python + + project = "Example Project 1" + + sa.create_project(project, "test", "Vector") + + +Uploading images to project +----------------- + + +To upload all images with extensions "jpg" or "png" from the +:file:`""` to the project "Example Project 1": + +.. code-block:: python + + sa.upload_images_from_folder_to_project(project, "") + +See the full argument options for +:py:func:`upload_images_from_folder_to_project` :ref:`here `. + + +.. note:: + + Python SDK functions that accept project argument will accept both project + name or :ref:`project metadata ` (returned either by + :ref:`get_project_metadata ` or + :ref:`search_projects ` with argument :py:obj:`return_metadata=True`). + If project name is used it should be unique in team's project list. Using project metadata will give + performance improvement. + + +Creating a folder in a project +______________________________ + +To create a new folder "folder1" in the project "Example Project 1": + +.. code-block:: python + + sa.create_folder(project, "folder1") + +After that point almost all SDK functions that use project name as argument can +point to that folder with slash after the project name, e.g., +"Example Project 1/folder1", in this case. + +.. note:: + + To upload images to the "folder1" instead of the root of the project: + + .. code-block:: python + + sa.upload_images_from_folder_to_project(project + "/folder1", "") + +Working with annotation classes +_______________________________________________ + +An annotation class for a project can be created with SDK's: + +.. code-block:: python + + sa.create_annotation_class(project, "Large car", color="#FFFFAA") + + +To create annotation classes in bulk with SuperAnnotate export format +:file:`classes.json` (documentation at: +https://app.superannotate.com/documentation Management Tools +-> Project Workflow part): + +.. code-block:: python + + sa.create_annotation_classes_from_classes_json(project, "") + + +All of the annotation classes of a project are downloaded (as :file:`classes/classes.json`) with +:ref:`download_export ` along with annotations, but they +can also be downloaded separately with: + +.. code-block:: python + + sa.download_annotation_classes_json(project, "") + +The :file:`classes.json` file will be downloaded to :file:`""` folder. + + +Working with annotations +_______________________________________________ + + +The SuperAnnotate format annotation JSONs have the general form: + +.. code-block:: json + + { + "metadata": {...} + "annotations": [ + { + "className": "Human", + "points" : "...", + "..." : "..." + }, + { + "className": "Cat", + "points" : "...", + "..." : "..." + }, + { + "..." : "..." + } + ] + + } + +the "className" fields here will identify the annotation class of an annotation +object (polygon, points, etc.). The project +you are uploading to should contain annotation class with that name. + +To upload annotations to platform: + +.. code-block:: python + + sa.upload_annotations_from_folder_to_project(project, "") + +This will try uploading to the project all the JSON files in the folder that have specific +file naming convention. For vector +projects JSONs should be named :file:`"___objects.json"`. For pixel projects +JSON files should be named :file:`"___pixel.json"` and also for +each JSON a mask image file should be present with the name +:file:`"___save.png"`. Image with :file:`` should +already be present in the project for the upload to work. + + +Exporting projects +__________________ + +To export the project annotations we need to prepare the export first: + +.. code-block:: python + + export = sa.prepare_export(project, include_fuse=True) + +We can download the prepared export with: + +.. code-block:: python + + sa.download_export(project, export, "", extract_zip_contents=True) + +:ref:`download_export ` will wait until the export is +finished preparing and download it to the specified folder. + diff --git a/docs/source/userguide/utilities.rst b/docs/source/userguide/utilities.rst new file mode 100644 index 000000000..3dfbf667f --- /dev/null +++ b/docs/source/userguide/utilities.rst @@ -0,0 +1,197 @@ +========== +Utilities +========== + + +Converting annotation format +============ + + +After exporting project annotations (in SuperAnnotate format), it is possible +to convert them to other annotation formats: + +.. code-block:: python + + sa.export_annotation("", "", "", "", + "", "") + +.. note:: + + Right now we support only SuperAnnotate annotation format to COCO annotation format conversion, but you can convert from "COCO", "Pascal VOC", "DataLoop", "LabelBox", "SageMaker", "Supervisely", "VGG", "VoTT" or "YOLO" annotation formats to SuperAnnotate annotation format. + +.. _git_repo: https://github.com/superannotateai/superannotate-python-sdk + +You can find more information annotation format conversion :ref:`here `. We provide some examples in our `GitHub repository `_. In the root folder of our github repository, you can run following commands to do conversions. + +.. code-block:: python + + from superannotate import export_annotation + from superannotate import import_annotation + + # From SA format to COCO panoptic format + export_annotation( + "tests/converter_test/COCO/input/fromSuperAnnotate/cats_dogs_panoptic_segm", + "tests/converter_test/COCO/output/panoptic", + "COCO", "panoptic_test", "Pixel","panoptic_segmentation" + ) + + # From COCO keypoints detection format to SA annotation format + import_annotation( + "tests/converter_test/COCO/input/toSuperAnnotate/keypoint_detection", + "tests/converter_test/COCO/output/keypoints", + "COCO", "person_keypoints_test", "Vector", "keypoint_detection" + ) + + # Pascal VOC annotation format to SA annotation format + import_annotation( + "tests/converter_test/VOC/input/fromPascalVOCToSuperAnnotate/VOC2012", + "tests/converter_test/VOC/output/instances", + "VOC", "instances_test", "Pixel", "instance_segmentation" + ) + + # YOLO annotation format to SA annotation format + import_annotation( + 'tests/converter_test/YOLO/input/toSuperAnnotate', + 'tests/converter_test/YOLO/output', + 'YOLO', '', 'Vector', 'object_detection' + ) + + # LabelBox annotation format to SA annotation format + import_annotation( + "tests/converter_test/LabelBox/input/toSuperAnnotate/", + "tests/converter_test/LabelBox/output/objects/", + "LabelBox", "labelbox_example", "Vector", "object_detection" + ) + + # Supervisely annotation format to SA annotation format + import_annotation( + "tests/converter_test/Supervisely/input/toSuperAnnotate", + "tests/converter_test/Supervisely/output", + "Supervisely", "", "Vector", "vector_annotation" + ) + + # DataLoop annotation format to SA annotation format + import_annotation( + "tests/converter_test/DataLoop/input/toSuperAnnotate", + "tests/converter_test/DataLoop/output", + "DataLoop", "", "Vector", "vector_annotation" + ) + + # VGG annotation format to SA annotation format + import_annotation( + "tests/converter_test/VGG/input/toSuperAnnotate", + "tests/converter_test/VGG/output", + "VGG", "vgg_test", "Vector", "instance_segmentation" + ) + + # VoTT annotation format to SA annotation format + import_annotation( + "tests/converter_test/VoTT/input/toSuperAnnotate", + "tests/converter_test/VoTT/output", + "VoTT", "", "Vector", "vector_annotation" + ) + + # GoogleCloud annotation format to SA annotation format + import_annotation( + "tests/converter_test/GoogleCloud/input/toSuperAnnotate", + "tests/converter_test/GoogleCloud/output", + "GoogleCloud", "image_object_detection", "Vector", "object_detection" + ) + + # GoogleCloud annotation format to SA annotation format + import_annotation( + "tests/converter_test/SageMaker/input/toSuperAnnotate", + "tests/converter_test/SageMaker/output", + "SageMaker", "test-obj-detect", "Vector", "object_detection" + ) + + +pandas DataFrame out of project annotations and annotation instance filtering +============ + + +To create a `pandas DataFrame `_ from project +SuperAnnotate format annotations: + +.. code-block:: python + + df = sa.aggregate_annotations_as_df("") + +The created DataFrame will have columns specified at +:ref:`aggregate_annotations_as_df `. + +Example of created DataFrame: + +.. image:: images/pandas_df.png + +Each row represents annotation information. One full annotation with multiple +attribute groups can be grouped under :code:`instanceId` field. + + +Working with DICOM files +============ + +JPEG images with names :file:`_.jpg` will be created +in :file:``. Those JPEG images can be uploaded to +SuperAnnotate platform using the regular: + +.. code-block:: python + + sa.upload_images_from_folder_to_project(project, "") + +Some DICOM files can have image frames that are compressed. To load them, `GDCM : +Grassroots DICOM library `_ needs to be installed: + +.. code-block:: bash + + # using conda + conda install -c conda-forge gdcm + + # or on Ubuntu with versions above 19.04 + sudo apt install python3-gdcm + +Computing consensus scores for instances between several projects +============ + + +Consensus is a tool to compare the quallity of the annotations of the same image that is present in several projects. +To compute the consensus scores: + +.. code-block:: python + + res_df = sa.consensus([project_names], "", [image_list], "") + +Here pandas DataFrame with following columns is returned: creatorEmail, imageName, instanceId, className, area, attribute, projectName, score + +.. image:: images/consensus_dataframe.png + +Besides the pandas DataFrame there is an option to get the following plots by setting the show_plots flag to True: + +* Box plot of consensus scores for each annotators +* Box plot of consensus scores for each project +* Scatter plots of consensus score vs instance area for each project + +.. code-block:: python + + sa.consensus([project_names], "", [image_list], "", show_plots=True) + +To the left of each box plot the original score points of that annotator is depicted, the box plots are colored by annotator. + +.. image:: images/consensus_annotators_box.png + +Analogically the box plots of consensus scores for each project are colored according to project name. + +.. image:: images/consensus_projects_box.png + +Scatter plot of consensus score vs instance area is separated by projects. Hovering on a point reveals its annotator and image name. +The points are colored according to class name. Each annotator is represented with separate symbol. + +.. image:: images/consensus_scatter.png + + +Computing benchmark scores for instances between ground truth project and given project list +============ + + +Benchmark is a tool to compare the quallity of the annotations of the same image that is present in several projects with +the ground truth annotation of the same image that is in a separate project. diff --git a/src/superannotate/lib/app/interface/sdk_interface.py b/src/superannotate/lib/app/interface/sdk_interface.py index d40b0757d..d4f294292 100644 --- a/src/superannotate/lib/app/interface/sdk_interface.py +++ b/src/superannotate/lib/app/interface/sdk_interface.py @@ -2326,7 +2326,6 @@ def get_item_metadata( include_custom_metadata=True ) - Response Example: :: { @@ -2909,7 +2908,7 @@ def upload_custom_values( :param items: list of name-data pairs. The key of each dict indicates an existing item name and the value represents the custom metadata dict. - The values for the corresponding keys will be added to an item or will be overridden. + The values for the corresponding keys will be added to an item or will be overridden. :type items: list of dicts :return: dictionary with succeeded and failed item names. diff --git a/src/superannotate/lib/app/server/core.py b/src/superannotate/lib/app/server/core.py index 29459d375..8d79b5623 100644 --- a/src/superannotate/lib/app/server/core.py +++ b/src/superannotate/lib/app/server/core.py @@ -105,8 +105,6 @@ def index(): app.add_url_rule("/", view_func=index) - See :ref:`url-route-registrations`. - The endpoint name for the route defaults to the name of the view function if the ``endpoint`` parameter isn't passed. An error will be raised if a function has already been registered for the