Skip to main content

By the power of Grayskull... I have the Conda recipe!

· 7 min read
Marcelo Duarte Trevisani

The main goal of the Skeletonr is to conquer Grayskull.

Introduction

All jokes aside, the new project grayskull was created with the intention of generating better Conda recipes that would allow to package properly projects available in different channels such as PyPI, CRAN, Conan, GitHub register, GitHub repositories and so on. On top of that, Grayskull is also being developed to help conda-forge to update recipes.

Current status

Currently, Grayskull (version 0.2.1) is able to generate recipes just looking for packages on PyPI, and it is available on PyPI and conda-forge. The GitHub repository for this package is: marcelotrevisani/grayskull.

Before Grayskull, we just had conda-build skeleton to generate recipes for Python packages on PyPI. In all other aspects, the difference of quality of the generated recipes, and also the time spent to generate them have a big discrepancy when compared to conda-build skeleton and grayskull. Grayskull generates recipes taking in consideration the platform, Python version available, selectors, compilers (Fortran, C and C++), packages constrains, license type, license file, and so forth. It uses metadata available from multiple sources to try to create the best recipe possible.

Installation

You can install grayskull using pip or conda. Grayskull does not rely on conda to run and can generate recipes with minimum dependencies.

With conda

Grayskull is available on the conda-forge channel.

conda install -c conda-forge grayskull

With pip

pip install grayskull

Grayskull vs conda-build skeleton

There are some differences of recipes generated by grayskull and conda skeleton. Taking as example the pytest recipe, which has selectors for platforms, Python version constrains, and has several package constrains as well.

Grayskull (0.2.1) - took 4 seconds to generate the recipe

{% set name = "pytest" %}
{% set version = "5.3.5" %}

package:
name: {{ name|lower }}
version: {{ version }}

source:
url: https://pypi.io/packages/source/{{ name[0] }}/{{ name }}/{{ name }}-{{ version }}.tar.gz
sha256: 0d5fe9189a148acc3c3eb2ac8e1ac0742cb7618c084f3d228baaec0c254b318d

build:
number: 0
skip: true # [py2k]
entry_points:
- pytest=pytest:main
- py.test=pytest:main
script: {{ PYTHON }} -m pip install . -vv

requirements:
host:
- pip
- python
- setuptools >=40.0
- setuptools_scm
run:
- atomicwrites >=1.0 # [win]
- attrs >=17.4.0
- colorama # [win]
- importlib-metadata >=0.12 # [py<38]
- more-itertools >=4.0.0
- packaging
- pathlib2 >=2.2.0 # [py<36]
- pluggy <1.0,>=0.12
- py >=1.5.0
- python
- wcwidth

test:
imports:
- pytest
commands:
- pip check
- pytest --help
- py.test --help
requires:
- pip

about:
home: https://pypi.org/project/pytest/
summary: 'pytest: simple powerful testing with Python'
dev_url: https://github.com/pytest-dev/pytest
license: MIT
license_file: LICENSE

extra:
recipe-maintainers:
- marcelotrevisani

Skeleton (3.18.11) - took 31 seconds to generate the recipe

{% set name = "pytest" %}
{% set version = "5.3.5" %}

package:
name: "{{ name|lower }}"
version: "{{ version }}"

source:
url: "https://pypi.io/packages/source/{{ name[0] }}/{{ name }}/{{ name }}-{{ version }}.tar.gz"
sha256: 0d5fe9189a148acc3c3eb2ac8e1ac0742cb7618c084f3d228baaec0c254b318d

build:
number: 0
script: "{{ PYTHON }} -m pip install . -vv"

requirements:
host:
- atomicwrites >=1.0
- attrs >=17.4.0
- colorama;sys_platform =="win32"
- importlib-metadata >=0.12
- more-itertools >=4.0.0
- packaging
- pathlib2 >=2.2.0
- pip
- pluggy >=0.12,<1.0
- py >=1.5.0
- python
- wcwidth
run:
- atomicwrites >=1.0
- attrs >=17.4.0
- colorama;sys_platform =="win32"
- importlib-metadata >=0.12
- more-itertools >=4.0.0
- packaging
- pathlib2 >=2.2.0
- pluggy >=0.12,<1.0
- py >=1.5.0
- python
- wcwidth

about:
home: The package home page
license: MIT
license_family: MIT
license_file:
summary: "pytest: simple powerful testing with Python"
doc_url:
dev_url:

extra:
recipe-maintainers:
- your-github-id-here

Original recipe on conda-forge for pytest 5.3.5

{% set version = "5.3.5" %}

package:
name: pytest
version: {{ version }}

source:
url: https://pypi.io/packages/source/p/pytest/pytest-{{ version }}.tar.gz
sha256: 0d5fe9189a148acc3c3eb2ac8e1ac0742cb7618c084f3d228baaec0c254b318d

build:
skip: True # [py27]
number: 1
script: "{{ PYTHON }} setup.py install --single-version-externally-managed --record record.txt"
entry_points:
- py.test = py.test:main
- pytest = py.test:main

requirements:
host:
- pip
- python
- setuptools >=40.0
- setuptools_scm
run:
- atomicwrites >=1.0 # [win]
- attrs >=17.4.0
- colorama # [win]
- importlib_metadata >=0.12 # [py<38]
- more-itertools >=4.0
- packaging
- pathlib2 >=2.2.0 # [py<36]
- pluggy >=0.12,<1.0
- py >=1.5.0
- python
- setuptools >=40.0
- wcwidth
run_constrained:
# pytest-faulthandler 2 is a dummy package.
# if an older version of fault-handler is installed, it will conflict with pytest >=5.
- pytest-faulthandler >=2

test:
commands:
- pytest -h
imports:
- pytest

about:
home: https://docs.pytest.org/en/latest/
license: MIT
license_file: LICENSE
summary: 'Simple and powerful testing with Python.'
description: |
The pytest framework makes it easy to write small tests, yet scales to
support complex functional testing for applications and libraries.
doc_url: https://docs.pytest.org/en/latest/
dev_url: https://github.com/pytest-dev/pytest/

extra:
recipe-maintainers:
- flub
- goanpeca
- nicoddemus
- ocefpaf
- mingwandroid

Major differences

AttributeGrayskull (0.2.1)Skeleton (3.18.11)
Command✅ grayskull pypi pytest✅ conda skeleton pypi pytest
Time✅ 4 seconds❌ 31 seconds
License✅ Added the license file and license type correctly❗️ Added just the license type
Host Requirements✅ Added correctly all the host requirements❌ it didn't add the correctly the host dependencies. It added unnecessary dependencies and it is missing quite a few of them necessary to build the package
Run Requirements✅ Missing just setuptools from the host requirements (but this dependency is not defined on pytest package)❌ incorrect dependencies added to the project
Selectors✅ Skipping correctly Python 2 and added selectors for windows and python versions❌ it didn't add any information regarding selectors. Actually conda-build added wrong information which will result in a broken recipe. For example 'sys_platform == win32' was added which is a wrong format for conda recipes
Entry points✅ Added all entry points correctly❌ No entry points
Does it build?✅ YES❌ NO

In the case of noarch: python, Grayskull is smart enough to detect when the recipe supports it, which is not done by Skeleton. It is important to highlight that Skeleton does not detect compilers as well. Nevertheless, Grayskull always try to detect it.

Usage Grayskull (0.2.1)

Project options:

$ grayskull --help
usage: grayskull [-h] [--version] {pypi} ...

Grayskull - Conda recipe generator

positional arguments:
{pypi} Options to generate PyPI recipes
pypi Generate recipes based on PyPI

optional arguments:
-h, --help show this help message and exit
--version, -v Print Grayskull version and exit
$ grayskull pypi --help
usage: grayskull pypi [-h] [--maintainers MAINTAINERS [MAINTAINERS...]]
[--output OUTPUT]
pypi_packages [pypi_packages ...]

positional arguments:
pypi_packages Specify the PyPI package name.

optional arguments:
-h, --help show this help message and exit
--maintainers MAINTAINERS [MAINTAINERS ...], -m MAINTAINERS [MAINTAINERS...] List of maintainers which will be added to the recipe.
--output OUTPUT, -o OUTPUT Path to where the recipe will be created

To generate the recipe you can just call grayskull and pass the channel (as for now we are just supporting PyPI, it should be pypi) and the package name. You should also specify an output folder using the option --output or -o and it will create the package folder, and the recipe in there. It is important to note that the user can specify a list of maintainers which will be added to the recipe using the option --maintainers.

Example for pytest:

Grayskull CLI

If you need to specify the package version you can do it just puting the equal sign after the package name and the version just right after that. Example:

grayskull pypi requests=2.21.0

or

grayskull pypi requests==2.21.0

Grayskull pinned package -requests

If you want to generate multiple recipes just pass a list of packages, such as:

grayskul pypi pytest requests=2.21.0 colorama

Future plans

  • For the next major version (1.0.0) it is planned to add the functionality to be able to load the recipe and update just parts of it;
  • Generate Conda recipes using CRAN (R) channel (2.0.0);
  • Generate Conda recipes using Conan (C++) channel (3.0.0);

Issues

Any problem, question, suggestions please feel free to open an issue on the repository.

Contributions are very welcome! :)


This work was possible thanks to the NumFOCUS Small Development Grant program.