We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Polish docs for 0.9 release
Open issues that we would like docs included (@minrk Please add to this list)
- [x] Merge and polish PR #1967 (Configuring user environments)

Additional 'to do' items
- [x] Update changelog
- [x] Update contributors list

Not strictly documentation
- [x] Update metadata in setup.py for pypi.org's additional display of docs now


--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `setup.py`
Content:
```
1 #!/usr/bin/env python3
2 # coding: utf-8
3 
4 # Copyright (c) Juptyer Development Team.
5 # Distributed under the terms of the Modified BSD License.
6 
7 #-----------------------------------------------------------------------------
8 # Minimal Python version sanity check (from IPython)
9 #-----------------------------------------------------------------------------
10 
11 from __future__ import print_function
12 
13 import os
14 import shutil
15 import sys
16 
17 v = sys.version_info
18 if v[:2] < (3, 5):
19     error = "ERROR: JupyterHub requires Python version 3.5 or above."
20     print(error, file=sys.stderr)
21     sys.exit(1)
22 
23 shell = False
24 if os.name in ('nt', 'dos'):
25     shell = True
26     warning = "WARNING: Windows is not officially supported"
27     print(warning, file=sys.stderr)
28 
29 # At least we're on the python version we need, move on.
30 
31 import os
32 from glob import glob
33 from subprocess import check_call
34 
35 from setuptools import setup
36 from setuptools.command.bdist_egg import bdist_egg
37 
38 pjoin = os.path.join
39 
40 here = os.path.abspath(os.path.dirname(__file__))
41 share_jupyterhub = pjoin(here, 'share', 'jupyterhub')
42 static = pjoin(share_jupyterhub, 'static')
43 
44 is_repo = os.path.exists(pjoin(here, '.git'))
45 
46 #---------------------------------------------------------------------------
47 # Build basic package data, etc.
48 #---------------------------------------------------------------------------
49 
50 def get_data_files():
51     """Get data files in share/jupyter"""
52 
53     data_files = []
54     ntrim = len(here + os.path.sep)
55 
56     for (d, dirs, filenames) in os.walk(share_jupyterhub):
57         data_files.append((
58             d[ntrim:],
59             [ pjoin(d, f) for f in filenames ]
60         ))
61     return data_files
62 
63 def get_package_data():
64     """Get package data
65 
66     (mostly alembic config)
67     """
68     package_data = {}
69     package_data['jupyterhub'] = [
70         'alembic.ini',
71         'alembic/*',
72         'alembic/versions/*',
73     ]
74     return package_data
75 
76 ns = {}
77 with open(pjoin(here, 'jupyterhub', '_version.py')) as f:
78     exec(f.read(), {}, ns)
79 
80 
81 packages = []
82 for d, _, _ in os.walk('jupyterhub'):
83     if os.path.exists(pjoin(d, '__init__.py')):
84         packages.append(d.replace(os.path.sep, '.'))
85 
86 setup_args = dict(
87     name                = 'jupyterhub',
88     scripts             = glob(pjoin('scripts', '*')),
89     packages            = packages,
90                         # dummy, so that install_data doesn't get skipped
91                         # this will be overridden when bower is run anyway
92     data_files          = get_data_files() or ['dummy'],
93     package_data        = get_package_data(),
94     version             = ns['__version__'],
95     description         = "JupyterHub: A multi-user server for Jupyter notebooks",
96     long_description    = "See https://jupyterhub.readthedocs.io for more info.",
97     author              = "Jupyter Development Team",
98     author_email        = "jupyter@googlegroups.com",
99     url                 = "http://jupyter.org",
100     license             = "BSD",
101     platforms           = "Linux, Mac OS X",
102     keywords            = ['Interactive', 'Interpreter', 'Shell', 'Web'],
103     python_requires     = ">=3.5",
104     classifiers         = [
105         'Intended Audience :: Developers',
106         'Intended Audience :: System Administrators',
107         'Intended Audience :: Science/Research',
108         'License :: OSI Approved :: BSD License',
109         'Programming Language :: Python',
110         'Programming Language :: Python :: 3',
111     ],
112 )
113 
114 #---------------------------------------------------------------------------
115 # custom distutils commands
116 #---------------------------------------------------------------------------
117 
118 # imports here, so they are after setuptools import if there was one
119 from distutils.cmd import Command
120 from distutils.command.build_py import build_py
121 from distutils.command.sdist import sdist
122 
123 
124 def mtime(path):
125     """shorthand for mtime"""
126     return os.stat(path).st_mtime
127 
128 
129 class BaseCommand(Command):
130     """Dumb empty command because Command needs subclasses to override too much"""
131     user_options = []
132 
133     def initialize_options(self):
134         pass
135 
136     def finalize_options(self):
137         pass
138 
139     def get_inputs(self):
140         return []
141 
142     def get_outputs(self):
143         return []
144 
145 
146 class NPM(BaseCommand):
147     description = "fetch static client-side components with bower"
148 
149     user_options = []
150     node_modules = pjoin(here, 'node_modules')
151     bower_dir = pjoin(static, 'components')
152 
153     def should_run(self):
154         if not shutil.which('npm'):
155             print("npm unavailable", file=sys.stderr)
156             return False
157         if not os.path.exists(self.bower_dir):
158             return True
159         if not os.path.exists(self.node_modules):
160             return True
161         if mtime(self.bower_dir) < mtime(self.node_modules):
162             return True
163         return mtime(self.node_modules) < mtime(pjoin(here, 'package.json'))
164 
165     def run(self):
166         if not self.should_run():
167             print("npm dependencies up to date")
168             return
169 
170         print("installing js dependencies with npm")
171         check_call(['npm', 'install', '--progress=false', '--unsafe-perm'], cwd=here, shell=shell)
172         os.utime(self.node_modules)
173 
174         os.utime(self.bower_dir)
175         # update data-files in case this created new files
176         self.distribution.data_files = get_data_files()
177 
178 
179 class CSS(BaseCommand):
180     description = "compile CSS from LESS"
181 
182     def should_run(self):
183         """Does less need to run?"""
184         # from IPython.html.tasks.py
185 
186         css_targets = [pjoin(static, 'css', 'style.min.css')]
187         css_maps = [t + '.map' for t in css_targets]
188         targets = css_targets + css_maps
189         if not all(os.path.exists(t) for t in targets):
190             # some generated files don't exist
191             return True
192         earliest_target = sorted(mtime(t) for t in targets)[0]
193 
194         # check if any .less files are newer than the generated targets
195         for (dirpath, dirnames, filenames) in os.walk(static):
196             for f in filenames:
197                 if f.endswith('.less'):
198                     path = pjoin(static, dirpath, f)
199                     timestamp = mtime(path)
200                     if timestamp > earliest_target:
201                         return True
202 
203         return False
204 
205     def run(self):
206         if not self.should_run():
207             print("CSS up-to-date")
208             return
209 
210         self.run_command('js')
211         print("Building css with less")
212 
213         style_less = pjoin(static, 'less', 'style.less')
214         style_css = pjoin(static, 'css', 'style.min.css')
215         sourcemap = style_css + '.map'
216 
217         args = [
218             'npm', 'run', 'lessc', '--', '--clean-css',
219             '--source-map-basepath={}'.format(static),
220             '--source-map={}'.format(sourcemap),
221             '--source-map-rootpath=../',
222             style_less, style_css,
223         ]
224         try:
225             check_call(args, cwd=here, shell=shell)
226         except OSError as e:
227             print("Failed to run lessc: %s" % e, file=sys.stderr)
228             print("You can install js dependencies with `npm install`", file=sys.stderr)
229             raise
230         # update data-files in case this created new files
231         self.distribution.data_files = get_data_files()
232 
233 
234 def js_css_first(cls, strict=True):
235     class Command(cls):
236         def run(self):
237             try:
238                 self.run_command('js')
239                 self.run_command('css')
240             except Exception:
241                 if strict:
242                     raise
243                 else:
244                     pass
245             return super().run()
246     return Command
247 
248 
249 class bdist_egg_disabled(bdist_egg):
250     """Disabled version of bdist_egg
251 
252     Prevents setup.py install from performing setuptools' default easy_install,
253     which it should never ever do.
254     """
255     def run(self):
256         sys.exit("Aborting implicit building of eggs. Use `pip install .` to install from source.")
257 
258 
259 setup_args['cmdclass'] = {
260     'js': NPM,
261     'css': CSS,
262     'build_py': js_css_first(build_py, strict=is_repo),
263     'sdist': js_css_first(sdist, strict=True),
264     'bdist_egg': bdist_egg if 'bdist_egg' in sys.argv else bdist_egg_disabled,
265 }
266 
267 
268 # setuptools requirements
269 
270 setup_args['zip_safe'] = False
271 from setuptools.command.develop import develop
272 class develop_js_css(develop):
273     def run(self):
274         if not self.uninstall:
275             self.distribution.run_command('js')
276             self.distribution.run_command('css')
277         develop.run(self)
278 setup_args['cmdclass']['develop'] = develop_js_css
279 setup_args['install_requires'] = install_requires = []
280 
281 with open('requirements.txt') as f:
282     for line in f.readlines():
283         req = line.strip()
284         if not req or req.startswith('#') or '://' in req:
285             continue
286         install_requires.append(req)
287 
288 #---------------------------------------------------------------------------
289 # setup
290 #---------------------------------------------------------------------------
291 
292 def main():
293     setup(**setup_args)
294 
295 if __name__ == '__main__':
296     main()
297
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/setup.py b/setup.py
--- a/setup.py
+++ b/setup.py
@@ -83,6 +83,10 @@
     if os.path.exists(pjoin(d, '__init__.py')):
         packages.append(d.replace(os.path.sep, '.'))
 
+with open('README.md', encoding="utf8") as f:
+    readme = f.read()
+
+
 setup_args = dict(
     name                = 'jupyterhub',
     scripts             = glob(pjoin('scripts', '*')),
@@ -93,10 +97,11 @@
     package_data        = get_package_data(),
     version             = ns['__version__'],
     description         = "JupyterHub: A multi-user server for Jupyter notebooks",
-    long_description    = "See https://jupyterhub.readthedocs.io for more info.",
+    long_description    = readme,
+    long_description_content_type = 'text/markdown',
     author              = "Jupyter Development Team",
     author_email        = "jupyter@googlegroups.com",
-    url                 = "http://jupyter.org",
+    url                 = "https://jupyter.org",
     license             = "BSD",
     platforms           = "Linux, Mac OS X",
     keywords            = ['Interactive', 'Interpreter', 'Shell', 'Web'],
@@ -109,6 +114,12 @@
         'Programming Language :: Python',
         'Programming Language :: Python :: 3',
     ],
+    project_urls        = {
+        'Documentation': 'https://jupyterhub.readthedocs.io',
+        'Funding': 'https://jupyter.org/about',
+        'Source': 'https://github.com/jupyterhub/jupyterhub/',
+        'Tracker': 'https://github.com/jupyterhub/jupyterhub/issues',
+    },
 )
 
 #---------------------------------------------------------------------------

{"golden_diff": "diff --git a/setup.py b/setup.py\n--- a/setup.py\n+++ b/setup.py\n@@ -83,6 +83,10 @@\n     if os.path.exists(pjoin(d, '__init__.py')):\n         packages.append(d.replace(os.path.sep, '.'))\n \n+with open('README.md', encoding=\"utf8\") as f:\n+    readme = f.read()\n+\n+\n setup_args = dict(\n     name                = 'jupyterhub',\n     scripts             = glob(pjoin('scripts', '*')),\n@@ -93,10 +97,11 @@\n     package_data        = get_package_data(),\n     version             = ns['__version__'],\n     description         = \"JupyterHub: A multi-user server for Jupyter notebooks\",\n-    long_description    = \"See https://jupyterhub.readthedocs.io for more info.\",\n+    long_description    = readme,\n+    long_description_content_type = 'text/markdown',\n     author              = \"Jupyter Development Team\",\n     author_email        = \"jupyter@googlegroups.com\",\n-    url                 = \"http://jupyter.org\",\n+    url                 = \"https://jupyter.org\",\n     license             = \"BSD\",\n     platforms           = \"Linux, Mac OS X\",\n     keywords            = ['Interactive', 'Interpreter', 'Shell', 'Web'],\n@@ -109,6 +114,12 @@\n         'Programming Language :: Python',\n         'Programming Language :: Python :: 3',\n     ],\n+    project_urls        = {\n+        'Documentation': 'https://jupyterhub.readthedocs.io',\n+        'Funding': 'https://jupyter.org/about',\n+        'Source': 'https://github.com/jupyterhub/jupyterhub/',\n+        'Tracker': 'https://github.com/jupyterhub/jupyterhub/issues',\n+    },\n )\n \n #---------------------------------------------------------------------------\n", "issue": "Polish docs for 0.9 release\nOpen issues that we would like docs included (@minrk Please add to this list)\r\n- [x] Merge and polish PR #1967 (Configuring user environments)\r\n\r\nAdditional 'to do' items\r\n- [x] Update changelog\r\n- [x] Update contributors list\r\n\r\nNot strictly documentation\r\n- [x] Update metadata in setup.py for pypi.org's additional display of docs now\r\n\n", "before_files": [{"content": "#!/usr/bin/env python3\n# coding: utf-8\n\n# Copyright (c) Juptyer Development Team.\n# Distributed under the terms of the Modified BSD License.\n\n#-----------------------------------------------------------------------------\n# Minimal Python version sanity check (from IPython)\n#-----------------------------------------------------------------------------\n\nfrom __future__ import print_function\n\nimport os\nimport shutil\nimport sys\n\nv = sys.version_info\nif v[:2] < (3, 5):\n    error = \"ERROR: JupyterHub requires Python version 3.5 or above.\"\n    print(error, file=sys.stderr)\n    sys.exit(1)\n\nshell = False\nif os.name in ('nt', 'dos'):\n    shell = True\n    warning = \"WARNING: Windows is not officially supported\"\n    print(warning, file=sys.stderr)\n\n# At least we're on the python version we need, move on.\n\nimport os\nfrom glob import glob\nfrom subprocess import check_call\n\nfrom setuptools import setup\nfrom setuptools.command.bdist_egg import bdist_egg\n\npjoin = os.path.join\n\nhere = os.path.abspath(os.path.dirname(__file__))\nshare_jupyterhub = pjoin(here, 'share', 'jupyterhub')\nstatic = pjoin(share_jupyterhub, 'static')\n\nis_repo = os.path.exists(pjoin(here, '.git'))\n\n#---------------------------------------------------------------------------\n# Build basic package data, etc.\n#---------------------------------------------------------------------------\n\ndef get_data_files():\n    \"\"\"Get data files in share/jupyter\"\"\"\n\n    data_files = []\n    ntrim = len(here + os.path.sep)\n\n    for (d, dirs, filenames) in os.walk(share_jupyterhub):\n        data_files.append((\n            d[ntrim:],\n            [ pjoin(d, f) for f in filenames ]\n        ))\n    return data_files\n\ndef get_package_data():\n    \"\"\"Get package data\n\n    (mostly alembic config)\n    \"\"\"\n    package_data = {}\n    package_data['jupyterhub'] = [\n        'alembic.ini',\n        'alembic/*',\n        'alembic/versions/*',\n    ]\n    return package_data\n\nns = {}\nwith open(pjoin(here, 'jupyterhub', '_version.py')) as f:\n    exec(f.read(), {}, ns)\n\n\npackages = []\nfor d, _, _ in os.walk('jupyterhub'):\n    if os.path.exists(pjoin(d, '__init__.py')):\n        packages.append(d.replace(os.path.sep, '.'))\n\nsetup_args = dict(\n    name                = 'jupyterhub',\n    scripts             = glob(pjoin('scripts', '*')),\n    packages            = packages,\n                        # dummy, so that install_data doesn't get skipped\n                        # this will be overridden when bower is run anyway\n    data_files          = get_data_files() or ['dummy'],\n    package_data        = get_package_data(),\n    version             = ns['__version__'],\n    description         = \"JupyterHub: A multi-user server for Jupyter notebooks\",\n    long_description    = \"See https://jupyterhub.readthedocs.io for more info.\",\n    author              = \"Jupyter Development Team\",\n    author_email        = \"jupyter@googlegroups.com\",\n    url                 = \"http://jupyter.org\",\n    license             = \"BSD\",\n    platforms           = \"Linux, Mac OS X\",\n    keywords            = ['Interactive', 'Interpreter', 'Shell', 'Web'],\n    python_requires     = \">=3.5\",\n    classifiers         = [\n        'Intended Audience :: Developers',\n        'Intended Audience :: System Administrators',\n        'Intended Audience :: Science/Research',\n        'License :: OSI Approved :: BSD License',\n        'Programming Language :: Python',\n        'Programming Language :: Python :: 3',\n    ],\n)\n\n#---------------------------------------------------------------------------\n# custom distutils commands\n#---------------------------------------------------------------------------\n\n# imports here, so they are after setuptools import if there was one\nfrom distutils.cmd import Command\nfrom distutils.command.build_py import build_py\nfrom distutils.command.sdist import sdist\n\n\ndef mtime(path):\n    \"\"\"shorthand for mtime\"\"\"\n    return os.stat(path).st_mtime\n\n\nclass BaseCommand(Command):\n    \"\"\"Dumb empty command because Command needs subclasses to override too much\"\"\"\n    user_options = []\n\n    def initialize_options(self):\n        pass\n\n    def finalize_options(self):\n        pass\n\n    def get_inputs(self):\n        return []\n\n    def get_outputs(self):\n        return []\n\n\nclass NPM(BaseCommand):\n    description = \"fetch static client-side components with bower\"\n\n    user_options = []\n    node_modules = pjoin(here, 'node_modules')\n    bower_dir = pjoin(static, 'components')\n\n    def should_run(self):\n        if not shutil.which('npm'):\n            print(\"npm unavailable\", file=sys.stderr)\n            return False\n        if not os.path.exists(self.bower_dir):\n            return True\n        if not os.path.exists(self.node_modules):\n            return True\n        if mtime(self.bower_dir) < mtime(self.node_modules):\n            return True\n        return mtime(self.node_modules) < mtime(pjoin(here, 'package.json'))\n\n    def run(self):\n        if not self.should_run():\n            print(\"npm dependencies up to date\")\n            return\n\n        print(\"installing js dependencies with npm\")\n        check_call(['npm', 'install', '--progress=false', '--unsafe-perm'], cwd=here, shell=shell)\n        os.utime(self.node_modules)\n\n        os.utime(self.bower_dir)\n        # update data-files in case this created new files\n        self.distribution.data_files = get_data_files()\n\n\nclass CSS(BaseCommand):\n    description = \"compile CSS from LESS\"\n\n    def should_run(self):\n        \"\"\"Does less need to run?\"\"\"\n        # from IPython.html.tasks.py\n\n        css_targets = [pjoin(static, 'css', 'style.min.css')]\n        css_maps = [t + '.map' for t in css_targets]\n        targets = css_targets + css_maps\n        if not all(os.path.exists(t) for t in targets):\n            # some generated files don't exist\n            return True\n        earliest_target = sorted(mtime(t) for t in targets)[0]\n\n        # check if any .less files are newer than the generated targets\n        for (dirpath, dirnames, filenames) in os.walk(static):\n            for f in filenames:\n                if f.endswith('.less'):\n                    path = pjoin(static, dirpath, f)\n                    timestamp = mtime(path)\n                    if timestamp > earliest_target:\n                        return True\n\n        return False\n\n    def run(self):\n        if not self.should_run():\n            print(\"CSS up-to-date\")\n            return\n\n        self.run_command('js')\n        print(\"Building css with less\")\n\n        style_less = pjoin(static, 'less', 'style.less')\n        style_css = pjoin(static, 'css', 'style.min.css')\n        sourcemap = style_css + '.map'\n\n        args = [\n            'npm', 'run', 'lessc', '--', '--clean-css',\n            '--source-map-basepath={}'.format(static),\n            '--source-map={}'.format(sourcemap),\n            '--source-map-rootpath=../',\n            style_less, style_css,\n        ]\n        try:\n            check_call(args, cwd=here, shell=shell)\n        except OSError as e:\n            print(\"Failed to run lessc: %s\" % e, file=sys.stderr)\n            print(\"You can install js dependencies with `npm install`\", file=sys.stderr)\n            raise\n        # update data-files in case this created new files\n        self.distribution.data_files = get_data_files()\n\n\ndef js_css_first(cls, strict=True):\n    class Command(cls):\n        def run(self):\n            try:\n                self.run_command('js')\n                self.run_command('css')\n            except Exception:\n                if strict:\n                    raise\n                else:\n                    pass\n            return super().run()\n    return Command\n\n\nclass bdist_egg_disabled(bdist_egg):\n    \"\"\"Disabled version of bdist_egg\n\n    Prevents setup.py install from performing setuptools' default easy_install,\n    which it should never ever do.\n    \"\"\"\n    def run(self):\n        sys.exit(\"Aborting implicit building of eggs. Use `pip install .` to install from source.\")\n\n\nsetup_args['cmdclass'] = {\n    'js': NPM,\n    'css': CSS,\n    'build_py': js_css_first(build_py, strict=is_repo),\n    'sdist': js_css_first(sdist, strict=True),\n    'bdist_egg': bdist_egg if 'bdist_egg' in sys.argv else bdist_egg_disabled,\n}\n\n\n# setuptools requirements\n\nsetup_args['zip_safe'] = False\nfrom setuptools.command.develop import develop\nclass develop_js_css(develop):\n    def run(self):\n        if not self.uninstall:\n            self.distribution.run_command('js')\n            self.distribution.run_command('css')\n        develop.run(self)\nsetup_args['cmdclass']['develop'] = develop_js_css\nsetup_args['install_requires'] = install_requires = []\n\nwith open('requirements.txt') as f:\n    for line in f.readlines():\n        req = line.strip()\n        if not req or req.startswith('#') or '://' in req:\n            continue\n        install_requires.append(req)\n\n#---------------------------------------------------------------------------\n# setup\n#---------------------------------------------------------------------------\n\ndef main():\n    setup(**setup_args)\n\nif __name__ == '__main__':\n    main()\n", "path": "setup.py"}], "after_files": [{"content": "#!/usr/bin/env python3\n# coding: utf-8\n\n# Copyright (c) Juptyer Development Team.\n# Distributed under the terms of the Modified BSD License.\n\n#-----------------------------------------------------------------------------\n# Minimal Python version sanity check (from IPython)\n#-----------------------------------------------------------------------------\n\nfrom __future__ import print_function\n\nimport os\nimport shutil\nimport sys\n\nv = sys.version_info\nif v[:2] < (3, 5):\n    error = \"ERROR: JupyterHub requires Python version 3.5 or above.\"\n    print(error, file=sys.stderr)\n    sys.exit(1)\n\nshell = False\nif os.name in ('nt', 'dos'):\n    shell = True\n    warning = \"WARNING: Windows is not officially supported\"\n    print(warning, file=sys.stderr)\n\n# At least we're on the python version we need, move on.\n\nimport os\nfrom glob import glob\nfrom subprocess import check_call\n\nfrom setuptools import setup\nfrom setuptools.command.bdist_egg import bdist_egg\n\npjoin = os.path.join\n\nhere = os.path.abspath(os.path.dirname(__file__))\nshare_jupyterhub = pjoin(here, 'share', 'jupyterhub')\nstatic = pjoin(share_jupyterhub, 'static')\n\nis_repo = os.path.exists(pjoin(here, '.git'))\n\n#---------------------------------------------------------------------------\n# Build basic package data, etc.\n#---------------------------------------------------------------------------\n\ndef get_data_files():\n    \"\"\"Get data files in share/jupyter\"\"\"\n\n    data_files = []\n    ntrim = len(here + os.path.sep)\n\n    for (d, dirs, filenames) in os.walk(share_jupyterhub):\n        data_files.append((\n            d[ntrim:],\n            [ pjoin(d, f) for f in filenames ]\n        ))\n    return data_files\n\ndef get_package_data():\n    \"\"\"Get package data\n\n    (mostly alembic config)\n    \"\"\"\n    package_data = {}\n    package_data['jupyterhub'] = [\n        'alembic.ini',\n        'alembic/*',\n        'alembic/versions/*',\n    ]\n    return package_data\n\nns = {}\nwith open(pjoin(here, 'jupyterhub', '_version.py')) as f:\n    exec(f.read(), {}, ns)\n\n\npackages = []\nfor d, _, _ in os.walk('jupyterhub'):\n    if os.path.exists(pjoin(d, '__init__.py')):\n        packages.append(d.replace(os.path.sep, '.'))\n\nwith open('README.md', encoding=\"utf8\") as f:\n    readme = f.read()\n\n\nsetup_args = dict(\n    name                = 'jupyterhub',\n    scripts             = glob(pjoin('scripts', '*')),\n    packages            = packages,\n                        # dummy, so that install_data doesn't get skipped\n                        # this will be overridden when bower is run anyway\n    data_files          = get_data_files() or ['dummy'],\n    package_data        = get_package_data(),\n    version             = ns['__version__'],\n    description         = \"JupyterHub: A multi-user server for Jupyter notebooks\",\n    long_description    = readme,\n    long_description_content_type = 'text/markdown',\n    author              = \"Jupyter Development Team\",\n    author_email        = \"jupyter@googlegroups.com\",\n    url                 = \"https://jupyter.org\",\n    license             = \"BSD\",\n    platforms           = \"Linux, Mac OS X\",\n    keywords            = ['Interactive', 'Interpreter', 'Shell', 'Web'],\n    python_requires     = \">=3.5\",\n    classifiers         = [\n        'Intended Audience :: Developers',\n        'Intended Audience :: System Administrators',\n        'Intended Audience :: Science/Research',\n        'License :: OSI Approved :: BSD License',\n        'Programming Language :: Python',\n        'Programming Language :: Python :: 3',\n    ],\n    project_urls        = {\n        'Documentation': 'https://jupyterhub.readthedocs.io',\n        'Funding': 'https://jupyter.org/about',\n        'Source': 'https://github.com/jupyterhub/jupyterhub/',\n        'Tracker': 'https://github.com/jupyterhub/jupyterhub/issues',\n    },\n)\n\n#---------------------------------------------------------------------------\n# custom distutils commands\n#---------------------------------------------------------------------------\n\n# imports here, so they are after setuptools import if there was one\nfrom distutils.cmd import Command\nfrom distutils.command.build_py import build_py\nfrom distutils.command.sdist import sdist\n\n\ndef mtime(path):\n    \"\"\"shorthand for mtime\"\"\"\n    return os.stat(path).st_mtime\n\n\nclass BaseCommand(Command):\n    \"\"\"Dumb empty command because Command needs subclasses to override too much\"\"\"\n    user_options = []\n\n    def initialize_options(self):\n        pass\n\n    def finalize_options(self):\n        pass\n\n    def get_inputs(self):\n        return []\n\n    def get_outputs(self):\n        return []\n\n\nclass NPM(BaseCommand):\n    description = \"fetch static client-side components with bower\"\n\n    user_options = []\n    node_modules = pjoin(here, 'node_modules')\n    bower_dir = pjoin(static, 'components')\n\n    def should_run(self):\n        if not shutil.which('npm'):\n            print(\"npm unavailable\", file=sys.stderr)\n            return False\n        if not os.path.exists(self.bower_dir):\n            return True\n        if not os.path.exists(self.node_modules):\n            return True\n        if mtime(self.bower_dir) < mtime(self.node_modules):\n            return True\n        return mtime(self.node_modules) < mtime(pjoin(here, 'package.json'))\n\n    def run(self):\n        if not self.should_run():\n            print(\"npm dependencies up to date\")\n            return\n\n        print(\"installing js dependencies with npm\")\n        check_call(['npm', 'install', '--progress=false', '--unsafe-perm'], cwd=here, shell=shell)\n        os.utime(self.node_modules)\n\n        os.utime(self.bower_dir)\n        # update data-files in case this created new files\n        self.distribution.data_files = get_data_files()\n\n\nclass CSS(BaseCommand):\n    description = \"compile CSS from LESS\"\n\n    def should_run(self):\n        \"\"\"Does less need to run?\"\"\"\n        # from IPython.html.tasks.py\n\n        css_targets = [pjoin(static, 'css', 'style.min.css')]\n        css_maps = [t + '.map' for t in css_targets]\n        targets = css_targets + css_maps\n        if not all(os.path.exists(t) for t in targets):\n            # some generated files don't exist\n            return True\n        earliest_target = sorted(mtime(t) for t in targets)[0]\n\n        # check if any .less files are newer than the generated targets\n        for (dirpath, dirnames, filenames) in os.walk(static):\n            for f in filenames:\n                if f.endswith('.less'):\n                    path = pjoin(static, dirpath, f)\n                    timestamp = mtime(path)\n                    if timestamp > earliest_target:\n                        return True\n\n        return False\n\n    def run(self):\n        if not self.should_run():\n            print(\"CSS up-to-date\")\n            return\n\n        self.run_command('js')\n        print(\"Building css with less\")\n\n        style_less = pjoin(static, 'less', 'style.less')\n        style_css = pjoin(static, 'css', 'style.min.css')\n        sourcemap = style_css + '.map'\n\n        args = [\n            'npm', 'run', 'lessc', '--', '--clean-css',\n            '--source-map-basepath={}'.format(static),\n            '--source-map={}'.format(sourcemap),\n            '--source-map-rootpath=../',\n            style_less, style_css,\n        ]\n        try:\n            check_call(args, cwd=here, shell=shell)\n        except OSError as e:\n            print(\"Failed to run lessc: %s\" % e, file=sys.stderr)\n            print(\"You can install js dependencies with `npm install`\", file=sys.stderr)\n            raise\n        # update data-files in case this created new files\n        self.distribution.data_files = get_data_files()\n\n\ndef js_css_first(cls, strict=True):\n    class Command(cls):\n        def run(self):\n            try:\n                self.run_command('js')\n                self.run_command('css')\n            except Exception:\n                if strict:\n                    raise\n                else:\n                    pass\n            return super().run()\n    return Command\n\n\nclass bdist_egg_disabled(bdist_egg):\n    \"\"\"Disabled version of bdist_egg\n\n    Prevents setup.py install from performing setuptools' default easy_install,\n    which it should never ever do.\n    \"\"\"\n    def run(self):\n        sys.exit(\"Aborting implicit building of eggs. Use `pip install .` to install from source.\")\n\n\nsetup_args['cmdclass'] = {\n    'js': NPM,\n    'css': CSS,\n    'build_py': js_css_first(build_py, strict=is_repo),\n    'sdist': js_css_first(sdist, strict=True),\n    'bdist_egg': bdist_egg if 'bdist_egg' in sys.argv else bdist_egg_disabled,\n}\n\n\n# setuptools requirements\n\nsetup_args['zip_safe'] = False\nfrom setuptools.command.develop import develop\nclass develop_js_css(develop):\n    def run(self):\n        if not self.uninstall:\n            self.distribution.run_command('js')\n            self.distribution.run_command('css')\n        develop.run(self)\nsetup_args['cmdclass']['develop'] = develop_js_css\nsetup_args['install_requires'] = install_requires = []\n\nwith open('requirements.txt') as f:\n    for line in f.readlines():\n        req = line.strip()\n        if not req or req.startswith('#') or '://' in req:\n            continue\n        install_requires.append(req)\n\n#---------------------------------------------------------------------------\n# setup\n#---------------------------------------------------------------------------\n\ndef main():\n    setup(**setup_args)\n\nif __name__ == '__main__':\n    main()\n", "path": "setup.py"}]}