{ "cells": [ { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#hide\n", "# default_exp conda" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "# Create conda packages\n", "\n", "> Pure python packages created from nbdev settings.ini" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#export\n", "from fastcore.script import *\n", "from fastcore.all import *\n", "from fastrelease.core import find_config\n", "\n", "import yaml,subprocess\n", "from copy import deepcopy\n", "try: from packaging.version import parse\n", "except ImportError: from pip._vendor.packaging.version import parse\n", "\n", "_PYPI_URL = 'https://pypi.org/pypi/'" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#export\n", "def pypi_json(s):\n", " \"Dictionary decoded JSON for PYPI path `s`\"\n", " return urljson(f'{_PYPI_URL}{s}/json')" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#export\n", "def latest_pypi(name):\n", " \"Latest version of `name` on pypi\"\n", " return max(parse(r) for r,o in pypi_json(name)['releases'].items()\n", " if not parse(r).is_prerelease and not o[0]['yanked'])" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#export\n", "def _pip_conda_meta(name, path):\n", " ver = str(latest_pypi('sentencepiece'))\n", " pypi = pypi_json(f'{name}/{ver}')\n", " info = pypi['info']\n", " rel = [o for o in pypi['urls'] if o['packagetype']=='sdist'][0]\n", " reqs = ['pip', 'python', 'packaging']\n", "\n", " # Work around conda build bug - 'package' and 'source' must be first\n", " d1 = {\n", " 'package': {'name': name, 'version': ver},\n", " 'source': {'url':rel['url'], 'sha256':rel['digests']['sha256']}\n", " }\n", " d2 = {\n", " 'build': {'number': '0', 'noarch': 'python',\n", " 'script': '{{ PYTHON }} -m pip install . -vv'},\n", " 'test': {'imports': [name]},\n", " 'requirements': {'host':reqs, 'run':reqs},\n", " 'about': {'license': info['license'], 'home': info['project_url'], 'summary': info['summary']}\n", " }\n", " return d1,d2" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#export\n", "def _write_yaml(path, name, d1, d2):\n", " path = Path(path)\n", " p = path/name\n", " p.mkdir(exist_ok=True, parents=True)\n", " yaml.SafeDumper.ignore_aliases = lambda *args : True\n", " with (p/'meta.yaml').open('w') as f:\n", " yaml.safe_dump(d1, f)\n", " yaml.safe_dump(d2, f)" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#export\n", "def write_pip_conda_meta(name, path='conda'):\n", " \"Writes a `meta.yaml` file for `name` to the `conda` directory of the current directory\"\n", " _write_yaml(path, name, *_pip_conda_meta(name))" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#export\n", "def _get_conda_meta():\n", " cfg,cfg_path = find_config()\n", " name,ver = cfg.get('lib_name'),cfg.get('version')\n", " url = cfg.get('doc_host') or cfg.get('git_url')\n", "\n", " reqs = ['pip', 'python', 'packaging']\n", " if cfg.get('requirements'): reqs += cfg.get('requirements').split()\n", " if cfg.get('conda_requirements'): reqs += cfg.get('conda_requirements').split()\n", "\n", " pypi = pypi_json(f'{name}/{ver}')\n", " rel = [o for o in pypi['urls'] if o['packagetype']=='sdist'][0]\n", "\n", " # Work around conda build bug - 'package' and 'source' must be first\n", " d1 = {\n", " 'package': {'name': name, 'version': ver},\n", " 'source': {'url':rel['url'], 'sha256':rel['digests']['sha256']}\n", " }\n", "\n", " d2 = {\n", " 'build': {'number': '0', 'noarch': 'python',\n", " 'script': '{{ PYTHON }} -m pip install . -vv'},\n", " 'requirements': {'host':reqs, 'run':reqs},\n", " 'test': {'imports': [cfg.get('lib_path')]},\n", " 'about': {\n", " 'license': 'Apache Software',\n", " 'license_family': 'APACHE',\n", " 'home': url, 'doc_url': url, 'dev_url': url,\n", " 'summary': cfg.get('description')\n", " },\n", " 'extra': {'recipe-maintainers': [cfg.get('user')]}\n", " }\n", " return name,d1,d2" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#export\n", "def write_conda_meta(path='conda'):\n", " \"Writes a `meta.yaml` file to the `conda` directory of the current directory\"\n", " _write_yaml(path, *_get_conda_meta())" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "This function is used in the `fastrelease_conda_package` CLI command.\n", "\n", "**NB**: you need to first of all upload your package to PyPi, before creating the conda package." ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [ "#export\n", "@call_parse\n", "def fastrelease_conda_package(path:Param(\"Path where package will be created\", str)='conda',\n", " do_build:Param(\"Run `conda build` step\", bool_arg)=True,\n", " build_args:Param(\"Additional args (as str) to send to `conda build`\", str)='',\n", " do_upload:Param(\"Run `anaconda upload` step\", bool_arg)=True,\n", " upload_user:Param(\"Optional user to upload package to\")=None):\n", " \"Create a `meta.yaml` file ready to be built into a package, and optionally build and upload it\"\n", " write_conda_meta(path)\n", " cfg,cfg_path = find_config()\n", " name = cfg.get('lib_name')\n", " out = f\"Done. Next steps:\\n```\\`cd {path}\\n\"\"\"\n", " out_upl = f\"anaconda upload build/noarch/{name}-{cfg.get('version')}-py_0.tar.bz2\"\n", " if not do_build:\n", " print(f\"{out}conda build .\\n{out_upl}\\n```\")\n", " return\n", "\n", " os.chdir(path)\n", " try: res = subprocess.check_output(f\"conda build --output-folder build {build_args} .\".split()).decode()\n", " except subprocess.CalledProcessError as e: print(f\"{e.output}\\n\\nBuild failed.\")\n", " if 'to anaconda.org' in res: return\n", " if 'anaconda upload' not in res:\n", " print(f\"{res}\\n\\Build failed.\")\n", " return\n", "\n", " upload_str = re.findall('(anaconda upload .*)', res)[0]\n", " if upload_user: upload_str = upload_str.replace('anaconda upload ', f'anaconda upload -u {upload_user} ')\n", " try: res = subprocess.check_output(upload_str.split(), stderr=STDOUT).decode()\n", " except subprocess.CalledProcessError as e: print(f\"{e.output}\\n\\nUpload failed.\")\n", " if 'Upload complete' not in res: print(f\"{res}\\n\\nUpload failed.\")" ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "To build and upload a conda package, cd to the root of your repo, and then:\n", "\n", " fastrelease_conda_package\n", "\n", "Or to do things more manually:\n", "\n", "```\n", "fastrelease_conda_package --do_build false\n", "cd conda\n", "conda build {name}\n", "anaconda upload $CONDA_PREFIX/conda-bld/noarch/{name}-{ver}-*.tar.bz2\n", "```\n", "\n", "Add `--debug` to the `conda build command` to debug any problems that occur. Note that the build step takes a few minutes. Add `-u {org_name}` to the `anaconda upload` command if you wish to upload to an organization, or pass `upload_user` to `fastrelease_conda_package`.\n", "\n", "**NB**: you need to first of all upload your package to PyPi, before creating the conda package." ] }, { "cell_type": "markdown", "metadata": {}, "source": [ "## Export-" ] }, { "cell_type": "code", "execution_count": 1, "metadata": {}, "outputs": [ { "name": "stdout", "output_type": "stream", "text": [ "Converted 00_core.ipynb.\n", "Converted 01_conda.ipynb.\n", "Converted index.ipynb.\n" ] } ], "source": [ "#hide\n", "from nbdev.export import notebook2script\n", "notebook2script()" ] }, { "cell_type": "code", "execution_count": null, "metadata": {}, "outputs": [], "source": [] } ], "metadata": { "kernelspec": { "display_name": "Python 3", "language": "python", "name": "python3" }, "language_info": { "codemirror_mode": { "name": "ipython", "version": 3 }, "file_extension": ".py", "mimetype": "text/x-python", "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", "version": "3.8.3" }, "toc": { "base_numbering": 1, "nav_menu": {}, "number_sections": false, "sideBar": true, "skip_h1_title": false, "title_cell": "Table of Contents", "title_sidebar": "Contents", "toc_cell": false, "toc_position": {}, "toc_section_display": true, "toc_window_display": false } }, "nbformat": 4, "nbformat_minor": 4 }