Repository: conda/conda

Issue:
Unable to install noarch: python package without entry points
I've been playing with the `noarch: python` feature in conda 4.3 and enjoying it, really great addition!

One issue that I've found is that conda seems unable to install noarch python packages that do not have entry points.  Building and installing `noarch: python` packages which have entry points works fine. 

For example, trying to build a package for the following recipe:

``` yaml
package:
  name: imagesize
  version: 0.7.1

source:
  fn: imagesize-0.7.1.tar.gz
  url: https://pypi.io/packages/source/i/imagesize/imagesize-0.7.1.tar.gz
  sha256: 0ab2c62b87987e3252f89d30b7cedbec12a01af9274af9ffa48108f2c13c6062

build:
  number: 70
  noarch: python
  script: python setup.py install --single-version-externally-managed --record record.txt

requirements:
  build:
    - python
    - setuptools
  run:
    - python

test:
  imports:
    - imagesize
```

The build fails when trying to install the package into the test environment:

```
$ conda build .
BUILD START: imagesize-0.7.1-py_70
...
TEST START: imagesize-0.7.1-py_70
Deleting work directory, /home/jhelmus/anaconda/conda-bld/imagesize_1486396885045/work/imagesize-0.7.1
updating index in: /home/jhelmus/anaconda/conda-bld/linux-64
updating index in: /home/jhelmus/anaconda/conda-bld/noarch

The following NEW packages will be INSTALLED:

    imagesize: 0.7.1-py_70 local   
    openssl:   1.0.2k-0    defaults
    python:    3.5.2-0     defaults
    readline:  6.2-2       defaults
    sqlite:    3.13.0-0    defaults
    tk:        8.5.18-0    defaults
    xz:        5.2.2-1     defaults
    zlib:      1.2.8-3     defaults

Traceback (most recent call last):
  File "/home/jhelmus/anaconda/lib/python3.5/site-packages/conda/_vendor/auxlib/entity.py", line 403, in __get__
    val = instance.__dict__[self.name]
KeyError: 'entry_points'

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/home/jhelmus/anaconda/bin/conda-build", line 6, in <module>
    sys.exit(conda_build.cli.main_build.main())
  File "/home/jhelmus/anaconda/lib/python3.5/site-packages/conda_build/cli/main_build.py", line 322, in main
    execute(sys.argv[1:])
  File "/home/jhelmus/anaconda/lib/python3.5/site-packages/conda_build/cli/main_build.py", line 313, in execute
    noverify=args.no_verify)
  File "/home/jhelmus/anaconda/lib/python3.5/site-packages/conda_build/api.py", line 97, in build
    need_source_download=need_source_download, config=config)
  File "/home/jhelmus/anaconda/lib/python3.5/site-packages/conda_build/build.py", line 1486, in build_tree
    test(pkg, config=config)
  File "/home/jhelmus/anaconda/lib/python3.5/site-packages/conda_build/build.py", line 1304, in test
    create_env(config.test_prefix, specs, config=config)
  File "/home/jhelmus/anaconda/lib/python3.5/site-packages/conda_build/build.py", line 684, in create_env
    plan.execute_actions(actions, index, verbose=config.debug)
  File "/home/jhelmus/anaconda/lib/python3.5/site-packages/conda/plan.py", line 837, in execute_actions
    execute_instructions(plan, index, verbose)
  File "/home/jhelmus/anaconda/lib/python3.5/site-packages/conda/instructions.py", line 258, in execute_instructions
    cmd(state, arg)
  File "/home/jhelmus/anaconda/lib/python3.5/site-packages/conda/instructions.py", line 119, in UNLINKLINKTRANSACTION_CMD
    txn.execute()
  File "/home/jhelmus/anaconda/lib/python3.5/site-packages/conda/core/link.py", line 256, in execute
    self.verify()
  File "/home/jhelmus/anaconda/lib/python3.5/site-packages/conda/core/link.py", line 239, in verify
    self.prepare()
  File "/home/jhelmus/anaconda/lib/python3.5/site-packages/conda/core/link.py", line 170, in prepare
    concatv(unlink_actions, link_actions))
  File "/home/jhelmus/anaconda/lib/python3.5/site-packages/conda/core/link.py", line 169, in <genexpr>
    self.all_actions = tuple(per_pkg_actions for per_pkg_actions in
  File "/home/jhelmus/anaconda/lib/python3.5/site-packages/conda/core/link.py", line 166, in <genexpr>
    for pkg_info, lt in zip(self.packages_info_to_link, link_types)
  File "/home/jhelmus/anaconda/lib/python3.5/site-packages/conda/core/link.py", line 395, in make_link_actions
    python_entry_point_actions = CreatePythonEntryPointAction.create_actions(*required_quad)
  File "/home/jhelmus/anaconda/lib/python3.5/site-packages/conda/core/path_actions.py", line 388, in create_actions
    for ep_def in noarch.entry_points)
  File "/home/jhelmus/anaconda/lib/python3.5/site-packages/conda/_vendor/auxlib/entity.py", line 413, in __get__
    raise AttributeError("A value for {0} has not been set".format(self.name))
AttributeError: A value for entry_points has not been set
```

The package can be build using the `--no-test` flag but the same error is given when attempting to install the package into a environment.  I've uploaded this package to the [jjhelmus_testing channel](https://anaconda.org/jjhelmus_testing/imagesize/files) in case it is helpful for testing.  The behavior can be replicated using the command: `conda create -n imagesize_27 -c jjhelmus_testing python=2.7 imagesize=0.7.1=py_70`.

---

conda info for the build/install environment if it helps:

```
$ conda info
Current conda install:

               platform : linux-64
          conda version : 4.3.9
       conda is private : False
      conda-env version : 4.3.9
    conda-build version : 2.1.3
         python version : 3.5.2.final.0
       requests version : 2.12.4
       root environment : /home/jhelmus/anaconda  (writable)
    default environment : /home/jhelmus/anaconda
       envs directories : /home/jhelmus/anaconda/envs
          package cache : /home/jhelmus/anaconda/pkgs
           channel URLs : https://repo.continuum.io/pkgs/free/linux-64
                          https://repo.continuum.io/pkgs/free/noarch
                          https://repo.continuum.io/pkgs/r/linux-64
                          https://repo.continuum.io/pkgs/r/noarch
                          https://repo.continuum.io/pkgs/pro/linux-64
                          https://repo.continuum.io/pkgs/pro/noarch
            config file : /home/jhelmus/.condarc
           offline mode : False
             user-agent : conda/4.3.9 requests/2.12.4 CPython/3.5.2 Linux/4.4.0-59-generic debian/stretch/sid glibc/2.23
                UID:GID : 5001:1004
```


Discussion:
Thanks for the bug report.  I'll get a fix in the next patch release.

Resolving patch:
diff --git a/conda/core/path_actions.py b/conda/core/path_actions.py
--- a/conda/core/path_actions.py
+++ b/conda/core/path_actions.py
@@ -385,14 +385,14 @@ def this_triplet(entry_point_def):
         if noarch is not None and noarch.type == NoarchType.python:
             actions = tuple(cls(transaction_context, package_info, target_prefix,
                                 *this_triplet(ep_def))
-                            for ep_def in noarch.entry_points)
+                            for ep_def in noarch.entry_points or ())
 
             if on_win:
                 actions += tuple(
                     LinkPathAction.create_python_entry_point_windows_exe_action(
                         transaction_context, package_info, target_prefix,
                         requested_link_type, ep_def
-                    ) for ep_def in noarch.entry_points
+                    ) for ep_def in noarch.entry_points or ()
                 )
 
             return actions
diff --git a/conda/models/package_info.py b/conda/models/package_info.py
--- a/conda/models/package_info.py
+++ b/conda/models/package_info.py
@@ -20,12 +20,12 @@ def box(self, instance, val):
 
 class Noarch(Entity):
     type = NoarchField(NoarchType)
-    entry_points = ListField(string_types, required=False)
+    entry_points = ListField(string_types, required=False, nullable=True)
 
 
 class PreferredEnv(Entity):
     name = StringField()
-    executable_paths = ListField(string_types, required=False)
+    executable_paths = ListField(string_types, required=False, nullable=True)
 
 
 class PackageMetadata(Entity):


Test patch:
diff --git a/tests/test_create.py b/tests/test_create.py
--- a/tests/test_create.py
+++ b/tests/test_create.py
@@ -231,7 +231,7 @@ def test_create_install_update_remove(self):
             self.assertRaises(CondaError, run_command, Commands.INSTALL, prefix, 'constructor=1.0')
             assert not package_is_installed(prefix, 'constructor')
 
-    def test_noarch_python_package(self):
+    def test_noarch_python_package_with_entry_points(self):
         with make_temp_env("-c conda-test flask") as prefix:
             py_ver = get_python_version_for_prefix(prefix)
             sp_dir = get_python_site_packages_short_path(py_ver)
@@ -250,6 +250,21 @@ def test_noarch_python_package(self):
             assert not isfile(join(prefix, pyc_file))
             assert not isfile(exe_path)
 
+    def test_noarch_python_package_without_entry_points(self):
+        # regression test for #4546
+        with make_temp_env("-c conda-test itsdangerous") as prefix:
+            py_ver = get_python_version_for_prefix(prefix)
+            sp_dir = get_python_site_packages_short_path(py_ver)
+            py_file = sp_dir + "/itsdangerous.py"
+            pyc_file = pyc_path(py_file, py_ver)
+            assert isfile(join(prefix, py_file))
+            assert isfile(join(prefix, pyc_file))
+
+            run_command(Commands.REMOVE, prefix, "itsdangerous")
+
+            assert not isfile(join(prefix, py_file))
+            assert not isfile(join(prefix, pyc_file))
+
     def test_noarch_generic_package(self):
         with make_temp_env("-c conda-test font-ttf-inconsolata") as prefix:
             assert isfile(join(prefix, 'fonts', 'Inconsolata-Regular.ttf'))