Merge pull request #3852 from minrk/isort

Use isort for import formatting
This commit is contained in:
Erik Sundell
2022-04-01 11:07:28 +02:00
committed by GitHub
85 changed files with 366 additions and 604 deletions

View File

@@ -18,10 +18,10 @@ repos:
- --py36-plus
# Autoformat: Python code
- repo: https://github.com/asottile/reorder_python_imports
rev: v3.0.1
- repo: https://github.com/pycqa/isort
rev: 5.10.1
hooks:
- id: reorder-python-imports
- id: isort
# Autoformat: Python code
- repo: https://github.com/psf/black

View File

@@ -56,13 +56,15 @@ todo_include_todos = False
# Set the default role so we can use `foo` instead of ``foo``
default_role = 'literal'
# -- Config -------------------------------------------------------------
from jupyterhub.app import JupyterHub
from docutils import nodes
from sphinx.directives.other import SphinxDirective
from contextlib import redirect_stdout
from io import StringIO
from docutils import nodes
from sphinx.directives.other import SphinxDirective
# -- Config -------------------------------------------------------------
from jupyterhub.app import JupyterHub
# create a temp instance of JupyterHub just to get the output of the generate-config
# and help --all commands.
jupyterhub_app = JupyterHub()

View File

@@ -7,9 +7,10 @@ to enable testing without administrative privileges.
c = get_config() # noqa
c.Application.log_level = 'DEBUG'
from oauthenticator.azuread import AzureAdOAuthenticator
import os
from oauthenticator.azuread import AzureAdOAuthenticator
c.JupyterHub.authenticator_class = AzureAdOAuthenticator
c.AzureAdOAuthenticator.client_id = os.getenv("AAD_CLIENT_ID")

View File

@@ -5,12 +5,9 @@ from urllib.parse import urlparse
from tornado.httpserver import HTTPServer
from tornado.ioloop import IOLoop
from tornado.web import Application
from tornado.web import authenticated
from tornado.web import RequestHandler
from tornado.web import Application, RequestHandler, authenticated
from jupyterhub.services.auth import HubOAuthCallbackHandler
from jupyterhub.services.auth import HubOAuthenticated
from jupyterhub.services.auth import HubOAuthCallbackHandler, HubOAuthenticated
from jupyterhub.utils import url_path_join
SCOPE_PREFIX = "custom:grades"

View File

@@ -5,13 +5,10 @@ so all URLs and requests necessary for OAuth with JupyterHub should be in one pl
"""
import json
import os
from urllib.parse import urlencode
from urllib.parse import urlparse
from urllib.parse import urlencode, urlparse
from tornado import log
from tornado import web
from tornado.httpclient import AsyncHTTPClient
from tornado.httpclient import HTTPRequest
from tornado import log, web
from tornado.httpclient import AsyncHTTPClient, HTTPRequest
from tornado.httputil import url_concat
from tornado.ioloop import IOLoop

View File

@@ -16,7 +16,6 @@ import time
import requests
log = logging.getLogger(__name__)

View File

@@ -3,9 +3,7 @@ import datetime
import json
import os
from tornado import escape
from tornado import ioloop
from tornado import web
from tornado import escape, ioloop, web
from jupyterhub.services.auth import HubAuthenticated

View File

@@ -1,8 +1,5 @@
from datetime import datetime
from typing import Any
from typing import Dict
from typing import List
from typing import Optional
from typing import Any, Dict, List, Optional
from pydantic import BaseModel

View File

@@ -1,9 +1,7 @@
import json
import os
from fastapi import HTTPException
from fastapi import Security
from fastapi import status
from fastapi import HTTPException, Security, status
from fastapi.security import OAuth2AuthorizationCodeBearer
from fastapi.security.api_key import APIKeyQuery

View File

@@ -1,14 +1,9 @@
import os
from fastapi import APIRouter
from fastapi import Depends
from fastapi import Form
from fastapi import Request
from fastapi import APIRouter, Depends, Form, Request
from .client import get_client
from .models import AuthorizationError
from .models import HubApiError
from .models import User
from .models import AuthorizationError, HubApiError, User
from .security import get_current_user
# APIRouter prefix cannot end in /

View File

@@ -7,16 +7,10 @@ import os
import secrets
from functools import wraps
from flask import Flask
from flask import make_response
from flask import redirect
from flask import request
from flask import Response
from flask import session
from flask import Flask, Response, make_response, redirect, request, session
from jupyterhub.services.auth import HubOAuth
prefix = os.environ.get('JUPYTERHUB_SERVICE_PREFIX', '/')
auth = HubOAuth(api_token=os.environ['JUPYTERHUB_API_TOKEN'], cache_max_age=60)

View File

@@ -10,12 +10,9 @@ from urllib.parse import urlparse
from tornado.httpserver import HTTPServer
from tornado.ioloop import IOLoop
from tornado.web import Application
from tornado.web import authenticated
from tornado.web import RequestHandler
from tornado.web import Application, RequestHandler, authenticated
from jupyterhub.services.auth import HubOAuthCallbackHandler
from jupyterhub.services.auth import HubOAuthenticated
from jupyterhub.services.auth import HubOAuthCallbackHandler, HubOAuthenticated
from jupyterhub.utils import url_path_join

View File

@@ -10,9 +10,7 @@ from urllib.parse import urlparse
from tornado.httpserver import HTTPServer
from tornado.ioloop import IOLoop
from tornado.web import Application
from tornado.web import authenticated
from tornado.web import RequestHandler
from tornado.web import Application, RequestHandler, authenticated
from jupyterhub.services.auth import HubAuthenticated

View File

@@ -1,2 +1 @@
from ._version import __version__
from ._version import version_info
from ._version import __version__, version_info

View File

@@ -4,7 +4,7 @@
def get_data_files():
"""Walk up until we find share/jupyterhub"""
import sys
from os.path import join, abspath, dirname, exists, split
from os.path import abspath, dirname, exists, join, split
path = abspath(dirname(__file__))
starting_points = [path]

View File

@@ -3,8 +3,7 @@ import sys
from logging.config import fileConfig
from alembic import context
from sqlalchemy import engine_from_config
from sqlalchemy import pool
from sqlalchemy import engine_from_config, pool
# this is the Alembic Config object, which provides
# access to the values within the .ini file in use.
@@ -13,6 +12,7 @@ config = context.config
# This line sets up loggers basically.
if 'jupyterhub' in sys.modules:
from traitlets.config import MultipleInstanceError
from jupyterhub.app import JupyterHub
app = None

View File

@@ -11,8 +11,8 @@ down_revision = None
branch_labels = None
depends_on = None
from alembic import op
import sqlalchemy as sa
from alembic import op
def upgrade():

View File

@@ -15,8 +15,8 @@ import logging
logger = logging.getLogger('alembic')
from alembic import op
import sqlalchemy as sa
from alembic import op
tables = ('oauth_access_tokens', 'oauth_codes')

View File

@@ -22,8 +22,9 @@ import logging
logger = logging.getLogger('alembic')
from alembic import op
import sqlalchemy as sa
from alembic import op
from jupyterhub.orm import JSONDict

View File

@@ -11,8 +11,9 @@ down_revision = '896818069c98'
branch_labels = None
depends_on = None
from alembic import op
import sqlalchemy as sa
from alembic import op
from jupyterhub.orm import JSONDict

View File

@@ -11,11 +11,11 @@ down_revision = '1cebaf56856c'
branch_labels = None
depends_on = None
from alembic import op
import sqlalchemy as sa
import logging
import sqlalchemy as sa
from alembic import op
logger = logging.getLogger('alembic')

View File

@@ -13,17 +13,12 @@ depends_on = None
import sqlalchemy as sa
from alembic import op
from sqlalchemy import Column
from sqlalchemy import ForeignKey
from sqlalchemy import Integer
from sqlalchemy import Table
from sqlalchemy import Unicode
from sqlalchemy import Column, ForeignKey, Integer, Table, Unicode
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import relationship
from sqlalchemy.orm.session import Session
from jupyterhub import orm
from jupyterhub import roles
from jupyterhub import orm, roles
def upgrade():

View File

@@ -12,12 +12,11 @@ down_revision = '4dc2d5a8c53c'
branch_labels = None
depends_on = None
from alembic import op
import sqlalchemy as sa
from alembic import op
from jupyterhub import orm
naming_convention = orm.meta.naming_convention

View File

@@ -11,8 +11,8 @@ down_revision = 'd68c98b66cd4'
branch_labels = None
depends_on = None
from alembic import op
import sqlalchemy as sa
from alembic import op
def upgrade():

View File

@@ -12,11 +12,11 @@ branch_labels = None
depends_on = None
from alembic import op
import sqlalchemy as sa
from datetime import datetime
import sqlalchemy as sa
from alembic import op
def upgrade():
op.add_column('users', sa.Column('created', sa.DateTime, nullable=True))

View File

@@ -11,8 +11,8 @@ down_revision = 'eeb276e51423'
branch_labels = None
depends_on = None
from alembic import op
import sqlalchemy as sa
from alembic import op
def upgrade():

View File

@@ -11,8 +11,8 @@ down_revision = '99a28a4418e1'
branch_labels = None
depends_on = None
from alembic import op
import sqlalchemy as sa
from alembic import op
def upgrade():

View File

@@ -12,8 +12,9 @@ down_revision = '19c0846f6344'
branch_labels = None
depends_on = None
from alembic import op
import sqlalchemy as sa
from alembic import op
from jupyterhub.orm import JSONDict

View File

@@ -1,9 +1,4 @@
from . import auth
from . import groups
from . import hub
from . import proxy
from . import services
from . import users
from . import auth, groups, hub, proxy, services, users
from .base import *
default_handlers = []

View File

@@ -4,22 +4,14 @@
import itertools
import json
from datetime import datetime
from urllib.parse import parse_qsl
from urllib.parse import quote
from urllib.parse import urlencode
from urllib.parse import urlparse
from urllib.parse import urlunparse
from urllib.parse import parse_qsl, quote, urlencode, urlparse, urlunparse
from oauthlib import oauth2
from tornado import web
from .. import orm
from .. import roles
from .. import scopes
from ..utils import get_browser_protocol
from ..utils import token_authenticated
from .base import APIHandler
from .base import BaseHandler
from .. import orm, roles, scopes
from ..utils import get_browser_protocol, token_authenticated
from .base import APIHandler, BaseHandler
class TokenAPIHandler(APIHandler):

View File

@@ -4,10 +4,7 @@
import json
from functools import lru_cache
from http.client import responses
from urllib.parse import parse_qs
from urllib.parse import urlencode
from urllib.parse import urlparse
from urllib.parse import urlunparse
from urllib.parse import parse_qs, urlencode, urlparse, urlunparse
from sqlalchemy.exc import SQLAlchemyError
from tornado import web
@@ -15,9 +12,7 @@ from tornado import web
from .. import orm
from ..handlers import BaseHandler
from ..scopes import get_scopes_for
from ..utils import get_browser_protocol
from ..utils import isoformat
from ..utils import url_path_join
from ..utils import get_browser_protocol, isoformat, url_path_join
PAGINATION_MEDIA_TYPE = "application/jupyterhub-pagination+json"

View File

@@ -6,8 +6,7 @@ import json
from tornado import web
from .. import orm
from ..scopes import needs_scope
from ..scopes import Scope
from ..scopes import Scope, needs_scope
from .base import APIHandler

View File

@@ -6,8 +6,7 @@ Currently GET-only, no actions can be taken to modify services.
# Distributed under the terms of the Modified BSD License.
import json
from ..scopes import needs_scope
from ..scopes import Scope
from ..scopes import Scope, needs_scope
from .base import APIHandler

View File

@@ -3,26 +3,19 @@
# Distributed under the terms of the Modified BSD License.
import asyncio
import json
from datetime import datetime
from datetime import timedelta
from datetime import timezone
from datetime import datetime, timedelta, timezone
from async_generator import aclosing
from dateutil.parser import parse as parse_date
from sqlalchemy import func
from sqlalchemy import or_
from sqlalchemy import func, or_
from tornado import web
from tornado.iostream import StreamClosedError
from .. import orm
from .. import scopes
from .. import orm, scopes
from ..roles import assign_default_roles
from ..scopes import needs_scope
from ..user import User
from ..utils import isoformat
from ..utils import iterate_until
from ..utils import maybe_future
from ..utils import url_path_join
from ..utils import isoformat, iterate_until, maybe_future, url_path_join
from .base import APIHandler

View File

@@ -14,15 +14,11 @@ import socket
import sys
import time
from concurrent.futures import ThreadPoolExecutor
from datetime import datetime
from datetime import timedelta
from datetime import timezone
from datetime import datetime, timedelta, timezone
from getpass import getuser
from operator import itemgetter
from textwrap import dedent
from urllib.parse import unquote
from urllib.parse import urlparse
from urllib.parse import urlunparse
from urllib.parse import unquote, urlparse, urlunparse
if sys.version_info[:2] < (3, 3):
raise ValueError("Python < 3.3 not supported: %s" % sys.version)
@@ -36,78 +32,72 @@ except AttributeError as e:
asyncio_all_tasks = asyncio.Task.all_tasks
asyncio_current_task = asyncio.Task.current_task
from dateutil.parser import parse as parse_date
from jinja2 import Environment, FileSystemLoader, PrefixLoader, ChoiceLoader
from sqlalchemy.exc import OperationalError, SQLAlchemyError
from tornado.httpclient import AsyncHTTPClient
import tornado.httpserver
from tornado.ioloop import IOLoop, PeriodicCallback
from tornado.log import app_log, access_log, gen_log
import tornado.options
from dateutil.parser import parse as parse_date
from jinja2 import ChoiceLoader, Environment, FileSystemLoader, PrefixLoader
from jupyter_telemetry.eventlog import EventLog
from sqlalchemy.exc import OperationalError, SQLAlchemyError
from tornado import gen, web
from tornado.httpclient import AsyncHTTPClient
from tornado.ioloop import IOLoop, PeriodicCallback
from tornado.log import access_log, app_log, gen_log
from traitlets import (
Unicode,
Integer,
Dict,
List,
Bool,
Any,
Tuple,
Set,
Instance,
Bool,
Bytes,
Dict,
Float,
Instance,
Integer,
List,
Set,
Tuple,
Unicode,
Union,
observe,
default,
observe,
validate,
)
from traitlets.config import Application, Configurable, catch_config_error
from jupyter_telemetry.eventlog import EventLog
here = os.path.dirname(__file__)
import jupyterhub
from . import handlers, apihandlers
from .handlers.static import CacheControlStaticFilesHandler, LogoHandler
from .services.service import Service
from . import crypto
from . import dbutil
from . import orm
from . import roles
from . import scopes
from .user import UserDict
from .oauth.provider import make_provider
from . import apihandlers, crypto, dbutil, handlers, orm, roles, scopes
from ._data import DATA_FILES_PATH
from .log import CoroutineLogFormatter, log_request
from .proxy import Proxy, ConfigurableHTTPProxy
from .traitlets import URLPrefix, Command, EntryPointType, Callable
from .utils import (
AnyTimeoutError,
catch_db_error,
maybe_future,
url_path_join,
print_stacks,
print_ps_info,
make_ssl_context,
)
from .metrics import HUB_STARTUP_DURATION_SECONDS
from .metrics import INIT_SPAWNERS_DURATION_SECONDS
from .metrics import RUNNING_SERVERS
from .metrics import TOTAL_USERS
# classes for config
from .auth import Authenticator, PAMAuthenticator
from .crypto import CryptKeeper
from .spawner import Spawner, LocalProcessSpawner
from .objects import Hub, Server
# For faking stats
from .emptyclass import EmptyClass
from .handlers.static import CacheControlStaticFilesHandler, LogoHandler
from .log import CoroutineLogFormatter, log_request
from .metrics import (
HUB_STARTUP_DURATION_SECONDS,
INIT_SPAWNERS_DURATION_SECONDS,
RUNNING_SERVERS,
TOTAL_USERS,
)
from .oauth.provider import make_provider
from .objects import Hub, Server
from .proxy import ConfigurableHTTPProxy, Proxy
from .services.service import Service
from .spawner import LocalProcessSpawner, Spawner
from .traitlets import Callable, Command, EntryPointType, URLPrefix
from .user import UserDict
from .utils import (
AnyTimeoutError,
catch_db_error,
make_ssl_context,
maybe_future,
print_ps_info,
print_stacks,
url_path_join,
)
common_aliases = {
'log-level': 'Application.log_level',

View File

@@ -9,9 +9,7 @@ import warnings
from concurrent.futures import ThreadPoolExecutor
from functools import partial
from shutil import which
from subprocess import PIPE
from subprocess import Popen
from subprocess import STDOUT
from subprocess import PIPE, STDOUT, Popen
try:
import pamela
@@ -20,13 +18,12 @@ except Exception as e:
_pamela_error = e
from tornado.concurrent import run_on_executor
from traitlets import Any, Bool, Dict, Integer, Set, Unicode, default, observe
from traitlets.config import LoggingConfigurable
from traitlets import Bool, Integer, Set, Unicode, Dict, Any, default, observe
from .handlers.login import LoginHandler
from .utils import maybe_future, url_path_join
from .traitlets import Command
from .utils import maybe_future, url_path_join
class Authenticator(LoggingConfigurable):

View File

@@ -4,18 +4,12 @@ import os
from binascii import a2b_hex
from concurrent.futures import ThreadPoolExecutor
from traitlets import Any
from traitlets import default
from traitlets import Integer
from traitlets import List
from traitlets import observe
from traitlets import validate
from traitlets.config import Config
from traitlets.config import SingletonConfigurable
from traitlets import Any, Integer, List, default, observe, validate
from traitlets.config import Config, SingletonConfigurable
try:
import cryptography
from cryptography.fernet import Fernet, MultiFernet, InvalidToken
from cryptography.fernet import Fernet, InvalidToken, MultiFernet
except ImportError:
cryptography = None

View File

@@ -1,7 +1,4 @@
from . import base
from . import login
from . import metrics
from . import pages
from . import base, login, metrics, pages
from .base import *
from .login import *

View File

@@ -9,50 +9,43 @@ import random
import re
import time
import uuid
from datetime import datetime
from datetime import timedelta
from datetime import datetime, timedelta
from http.client import responses
from urllib.parse import parse_qs
from urllib.parse import parse_qsl
from urllib.parse import urlencode
from urllib.parse import urlparse
from urllib.parse import urlunparse
from urllib.parse import parse_qs, parse_qsl, urlencode, urlparse, urlunparse
from jinja2 import TemplateNotFound
from sqlalchemy.exc import SQLAlchemyError
from tornado import gen
from tornado import web
from tornado.httputil import HTTPHeaders
from tornado.httputil import url_concat
from tornado import gen, web
from tornado.httputil import HTTPHeaders, url_concat
from tornado.ioloop import IOLoop
from tornado.log import app_log
from tornado.web import addslash
from tornado.web import RequestHandler
from tornado.web import RequestHandler, addslash
from .. import __version__
from .. import orm
from .. import roles
from .. import scopes
from ..metrics import PROXY_ADD_DURATION_SECONDS
from ..metrics import PROXY_DELETE_DURATION_SECONDS
from ..metrics import ProxyDeleteStatus
from ..metrics import RUNNING_SERVERS
from ..metrics import SERVER_POLL_DURATION_SECONDS
from ..metrics import SERVER_SPAWN_DURATION_SECONDS
from ..metrics import SERVER_STOP_DURATION_SECONDS
from ..metrics import ServerPollStatus
from ..metrics import ServerSpawnStatus
from ..metrics import ServerStopStatus
from ..metrics import TOTAL_USERS
from .. import __version__, orm, roles, scopes
from ..metrics import (
PROXY_ADD_DURATION_SECONDS,
PROXY_DELETE_DURATION_SECONDS,
RUNNING_SERVERS,
SERVER_POLL_DURATION_SECONDS,
SERVER_SPAWN_DURATION_SECONDS,
SERVER_STOP_DURATION_SECONDS,
TOTAL_USERS,
ProxyDeleteStatus,
ServerPollStatus,
ServerSpawnStatus,
ServerStopStatus,
)
from ..objects import Server
from ..scopes import needs_scope
from ..spawner import LocalProcessSpawner
from ..user import User
from ..utils import AnyTimeoutError
from ..utils import get_accepted_mimetype
from ..utils import get_browser_protocol
from ..utils import maybe_future
from ..utils import url_path_join
from ..utils import (
AnyTimeoutError,
get_accepted_mimetype,
get_browser_protocol,
maybe_future,
url_path_join,
)
# pattern for the authentication token header
auth_header_pat = re.compile(r'^(?:token|bearer)\s+([^\s]+)$', flags=re.IGNORECASE)

View File

@@ -1,7 +1,5 @@
"""Handlers for serving prometheus metrics"""
from prometheus_client import CONTENT_TYPE_LATEST
from prometheus_client import generate_latest
from prometheus_client import REGISTRY
from prometheus_client import CONTENT_TYPE_LATEST, REGISTRY, generate_latest
from ..utils import metrics_authentication
from .base import BaseHandler

View File

@@ -12,11 +12,9 @@ from tornado import web
from tornado.httputil import url_concat
from .. import __version__
from ..metrics import SERVER_POLL_DURATION_SECONDS
from ..metrics import ServerPollStatus
from ..metrics import SERVER_POLL_DURATION_SECONDS, ServerPollStatus
from ..scopes import needs_scope
from ..utils import maybe_future
from ..utils import url_path_join
from ..utils import maybe_future, url_path_join
from .base import BaseHandler

View File

@@ -6,13 +6,10 @@ import logging
import traceback
from functools import partial
from http.cookies import SimpleCookie
from urllib.parse import urlparse
from urllib.parse import urlunparse
from urllib.parse import urlparse, urlunparse
from tornado.log import access_log
from tornado.log import LogFormatter
from tornado.web import HTTPError
from tornado.web import StaticFileHandler
from tornado.log import LogFormatter, access_log
from tornado.web import HTTPError, StaticFileHandler
from .handlers.pages import HealthCheckHandler
from .metrics import prometheus_log_method

View File

@@ -21,8 +21,7 @@ them manually here.
"""
from enum import Enum
from prometheus_client import Gauge
from prometheus_client import Histogram
from prometheus_client import Gauge, Histogram
REQUEST_DURATION_SECONDS = Histogram(
'jupyterhub_request_duration_seconds',

View File

@@ -3,19 +3,14 @@
implements https://oauthlib.readthedocs.io/en/latest/oauth2/server.html
"""
from oauthlib import uri_validate
from oauthlib.oauth2 import RequestValidator
from oauthlib.oauth2 import WebApplicationServer
from oauthlib.oauth2.rfc6749.grant_types import authorization_code
from oauthlib.oauth2.rfc6749.grant_types import base
from oauthlib.oauth2 import RequestValidator, WebApplicationServer
from oauthlib.oauth2.rfc6749.grant_types import authorization_code, base
from tornado.log import app_log
from .. import orm
from ..roles import roles_to_scopes
from ..scopes import _check_scopes_exist
from ..scopes import access_scopes
from ..scopes import identify_scopes
from ..utils import compare_token
from ..utils import hash_token
from ..scopes import _check_scopes_exist, access_scopes, identify_scopes
from ..utils import compare_token, hash_token
# patch absolute-uri check
# because we want to allow relative uri oauth

View File

@@ -3,25 +3,20 @@
# Distributed under the terms of the Modified BSD License.
import socket
import warnings
from urllib.parse import urlparse
from urllib.parse import urlunparse
from urllib.parse import urlparse, urlunparse
from traitlets import default
from traitlets import HasTraits
from traitlets import Instance
from traitlets import Integer
from traitlets import observe
from traitlets import Unicode
from traitlets import validate
from traitlets import HasTraits, Instance, Integer, Unicode, default, observe, validate
from . import orm
from .traitlets import URLPrefix
from .utils import can_connect
from .utils import make_ssl_context
from .utils import random_port
from .utils import url_path_join
from .utils import wait_for_http_server
from .utils import wait_for_server
from .utils import (
can_connect,
make_ssl_context,
random_port,
url_path_join,
wait_for_http_server,
wait_for_server,
)
class Server(HasTraits):

View File

@@ -3,47 +3,44 @@
# Distributed under the terms of the Modified BSD License.
import enum
import json
from base64 import decodebytes
from base64 import encodebytes
from datetime import datetime
from datetime import timedelta
from base64 import decodebytes, encodebytes
from datetime import datetime, timedelta
import alembic.command
import alembic.config
from alembic.script import ScriptDirectory
from sqlalchemy import Boolean
from sqlalchemy import Column
from sqlalchemy import create_engine
from sqlalchemy import DateTime
from sqlalchemy import event
from sqlalchemy import exc
from sqlalchemy import ForeignKey
from sqlalchemy import inspect
from sqlalchemy import Integer
from sqlalchemy import MetaData
from sqlalchemy import or_
from sqlalchemy import select
from sqlalchemy import Table
from sqlalchemy import Unicode
from sqlalchemy import (
Boolean,
Column,
DateTime,
ForeignKey,
Integer,
MetaData,
Table,
Unicode,
create_engine,
event,
exc,
inspect,
or_,
select,
)
from sqlalchemy.ext.declarative import declarative_base
from sqlalchemy.orm import backref
from sqlalchemy.orm import interfaces
from sqlalchemy.orm import object_session
from sqlalchemy.orm import relationship
from sqlalchemy.orm import Session
from sqlalchemy.orm import sessionmaker
from sqlalchemy.orm import (
Session,
backref,
interfaces,
object_session,
relationship,
sessionmaker,
)
from sqlalchemy.pool import StaticPool
from sqlalchemy.sql.expression import bindparam
from sqlalchemy.types import LargeBinary
from sqlalchemy.types import Text
from sqlalchemy.types import TypeDecorator
from sqlalchemy.types import LargeBinary, Text, TypeDecorator
from tornado.log import app_log
from .roles import roles_to_scopes
from .utils import compare_token
from .utils import hash_token
from .utils import new_token
from .utils import random_port
from .utils import compare_token, hash_token, new_token, random_port
# top-level variable for easier mocking in tests
utcnow = datetime.utcnow

View File

@@ -26,29 +26,18 @@ from subprocess import Popen
from urllib.parse import quote
from weakref import WeakKeyDictionary
from tornado.httpclient import AsyncHTTPClient
from tornado.httpclient import HTTPError
from tornado.httpclient import HTTPRequest
from tornado.httpclient import AsyncHTTPClient, HTTPError, HTTPRequest
from tornado.ioloop import PeriodicCallback
from traitlets import Any
from traitlets import Bool
from traitlets import default
from traitlets import Dict
from traitlets import Instance
from traitlets import Integer
from traitlets import observe
from traitlets import Unicode
from traitlets import Any, Bool, Dict, Instance, Integer, Unicode, default, observe
from traitlets.config import LoggingConfigurable
from . import utils
from .metrics import CHECK_ROUTES_DURATION_SECONDS
from .metrics import PROXY_POLL_DURATION_SECONDS
from .objects import Server
from .utils import AnyTimeoutError
from .utils import exponential_backoff
from .utils import url_path_join
from jupyterhub.traitlets import Command
from . import utils
from .metrics import CHECK_ROUTES_DURATION_SECONDS, PROXY_POLL_DURATION_SECONDS
from .objects import Server
from .utils import AnyTimeoutError, exponential_backoff, url_path_join
def _one_at_a_time(method):
"""decorator to limit an async method to be called only once

View File

@@ -7,8 +7,7 @@ from functools import wraps
from sqlalchemy import func
from tornado.log import app_log
from . import orm
from . import scopes
from . import orm, scopes
def get_default_roles():

View File

@@ -23,8 +23,7 @@ import sqlalchemy as sa
from tornado import web
from tornado.log import app_log
from . import orm
from . import roles
from . import orm, roles
"""when modifying the scope definitions, make sure that `docs/source/rbac/generate-scope-table.py` is run
so that changes are reflected in the documentation and REST API description."""

View File

@@ -40,21 +40,12 @@ from urllib.parse import urlencode
import requests
from tornado.httputil import url_concat
from tornado.log import app_log
from tornado.web import HTTPError
from tornado.web import RequestHandler
from traitlets import default
from traitlets import Dict
from traitlets import Instance
from traitlets import Integer
from traitlets import observe
from traitlets import Set
from traitlets import Unicode
from traitlets import validate
from tornado.web import HTTPError, RequestHandler
from traitlets import Dict, Instance, Integer, Set, Unicode, default, observe, validate
from traitlets.config import SingletonConfigurable
from ..scopes import _intersect_expanded_scopes
from ..utils import get_browser_protocol
from ..utils import url_path_join
from ..utils import get_browser_protocol, url_path_join
def check_scopes(required_scopes, scopes):

View File

@@ -45,21 +45,22 @@ import pipes
import shutil
from subprocess import Popen
from traitlets import Any
from traitlets import Bool
from traitlets import default
from traitlets import Dict
from traitlets import HasTraits
from traitlets import Instance
from traitlets import List
from traitlets import Unicode
from traitlets import validate
from traitlets import (
Any,
Bool,
Dict,
HasTraits,
Instance,
List,
Unicode,
default,
validate,
)
from traitlets.config import LoggingConfigurable
from .. import orm
from ..objects import Server
from ..spawner import LocalProcessSpawner
from ..spawner import set_user_setuid
from ..spawner import LocalProcessSpawner, set_user_setuid
from ..traitlets import Command
from ..utils import url_path_join

View File

@@ -2,10 +2,8 @@
Contains default notebook-app subclass and mixins
"""
from .app import main
from .app import SingleUserNotebookApp
from .mixins import HubAuthenticatedHandler
from .mixins import make_singleuser_app
from .app import SingleUserNotebookApp, main
from .mixins import HubAuthenticatedHandler, make_singleuser_app
# backward-compatibility
JupyterHubLoginHandler = SingleUserNotebookApp.login_handler_class

View File

@@ -21,35 +21,29 @@ from importlib import import_module
from textwrap import dedent
from urllib.parse import urlparse
from jinja2 import ChoiceLoader
from jinja2 import FunctionLoader
from jinja2 import ChoiceLoader, FunctionLoader
from tornado import ioloop
from tornado.httpclient import AsyncHTTPClient
from tornado.httpclient import HTTPRequest
from tornado.httpclient import AsyncHTTPClient, HTTPRequest
from tornado.web import RequestHandler
from traitlets import Any
from traitlets import Bool
from traitlets import Bytes
from traitlets import CUnicode
from traitlets import default
from traitlets import import_item
from traitlets import Integer
from traitlets import observe
from traitlets import TraitError
from traitlets import Unicode
from traitlets import validate
from traitlets import (
Any,
Bool,
Bytes,
CUnicode,
Integer,
TraitError,
Unicode,
default,
import_item,
observe,
validate,
)
from traitlets.config import Configurable
from .._version import __version__
from .._version import _check_version
from .._version import __version__, _check_version
from ..log import log_request
from ..services.auth import HubOAuth
from ..services.auth import HubOAuthCallbackHandler
from ..services.auth import HubOAuthenticated
from ..utils import exponential_backoff
from ..utils import isoformat
from ..utils import make_ssl_context
from ..utils import url_path_join
from ..services.auth import HubOAuth, HubOAuthCallbackHandler, HubOAuthenticated
from ..utils import exponential_backoff, isoformat, make_ssl_context, url_path_join
def _bool_env(key):

View File

@@ -19,29 +19,31 @@ from urllib.parse import urlparse
from async_generator import aclosing
from sqlalchemy import inspect
from tornado.ioloop import PeriodicCallback
from traitlets import Any
from traitlets import Bool
from traitlets import default
from traitlets import Dict
from traitlets import Float
from traitlets import Instance
from traitlets import Integer
from traitlets import List
from traitlets import observe
from traitlets import Unicode
from traitlets import Union
from traitlets import validate
from traitlets import (
Any,
Bool,
Dict,
Float,
Instance,
Integer,
List,
Unicode,
Union,
default,
observe,
validate,
)
from traitlets.config import LoggingConfigurable
from .objects import Server
from .traitlets import ByteSpecification
from .traitlets import Callable
from .traitlets import Command
from .utils import AnyTimeoutError
from .utils import exponential_backoff
from .utils import maybe_future
from .utils import random_port
from .utils import url_path_join
from .traitlets import ByteSpecification, Callable, Command
from .utils import (
AnyTimeoutError,
exponential_backoff,
maybe_future,
random_port,
url_path_join,
)
if os.name == 'nt':
import psutil

View File

@@ -35,22 +35,17 @@ from getpass import getuser
from subprocess import TimeoutExpired
from unittest import mock
from pytest import fixture
from pytest import raises
from pytest import fixture, raises
from tornado import ioloop
from tornado.httpclient import HTTPError
from tornado.platform.asyncio import AsyncIOMainLoop
import jupyterhub.services.service
from . import mocking
from .. import crypto
from .. import orm
from .. import scopes
from ..roles import create_role
from ..roles import get_default_roles
from ..roles import mock_roles
from ..roles import update_roles
from .. import crypto, orm, scopes
from ..roles import create_role, get_default_roles, mock_roles, update_roles
from ..utils import random_port
from . import mocking
from .mocking import MockHub
from .test_services import mockservice_cmd
from .utils import add_user

View File

@@ -37,23 +37,15 @@ from urllib.parse import urlparse
from pamela import PAMError
from tornado.ioloop import IOLoop
from traitlets import Bool
from traitlets import default
from traitlets import Dict
from traitlets import Bool, Dict, default
from .. import metrics
from .. import orm
from .. import roles
from .. import metrics, orm, roles
from ..app import JupyterHub
from ..auth import PAMAuthenticator
from ..singleuser import SingleUserNotebookApp
from ..spawner import SimpleLocalProcessSpawner
from ..utils import random_port
from ..utils import utcnow
from .utils import async_requests
from .utils import public_host
from .utils import public_url
from .utils import ssl_setup
from ..utils import random_port, utcnow
from .utils import async_requests, public_host, public_url, ssl_setup
def mock_authenticate(username, password, service, encoding):

View File

@@ -19,15 +19,14 @@ import sys
from urllib.parse import urlparse
import requests
from tornado import httpserver
from tornado import ioloop
from tornado import log
from tornado import web
from tornado import httpserver, ioloop, log, web
from tornado.httputil import url_concat
from jupyterhub.services.auth import HubAuthenticated
from jupyterhub.services.auth import HubOAuthCallbackHandler
from jupyterhub.services.auth import HubOAuthenticated
from jupyterhub.services.auth import (
HubAuthenticated,
HubOAuthCallbackHandler,
HubOAuthenticated,
)
from jupyterhub.utils import make_ssl_context
@@ -123,7 +122,7 @@ def main():
if __name__ == '__main__':
from tornado.options import parse_command_line, options
from tornado.options import options, parse_command_line
parse_command_line()
options.logging = 'debug'

View File

@@ -16,10 +16,7 @@ import os
import sys
from urllib.parse import urlparse
from tornado import httpserver
from tornado import ioloop
from tornado import log
from tornado import web
from tornado import httpserver, ioloop, log, web
from tornado.options import options
from ..utils import make_ssl_context

View File

@@ -4,32 +4,23 @@ import json
import re
import sys
import uuid
from datetime import datetime
from datetime import timedelta
from datetime import datetime, timedelta
from unittest import mock
from urllib.parse import quote
from urllib.parse import urlparse
from urllib.parse import urlunparse
from urllib.parse import quote, urlparse, urlunparse
from pytest import fixture
from pytest import mark
from pytest import fixture, mark
from tornado.httputil import url_concat
import jupyterhub
from .. import orm
from ..apihandlers.base import PAGINATION_MEDIA_TYPE
from ..objects import Server
from ..utils import url_path_join as ujoin
from ..utils import utcnow
from .conftest import new_username
from .mocking import public_host
from .mocking import public_url
from .utils import add_user
from .utils import api_request
from .utils import async_requests
from .utils import auth_header
from .utils import find_user
from .mocking import public_host, public_url
from .utils import add_user, api_request, async_requests, auth_header, find_user
# --------------------
# Authentication tests

View File

@@ -6,11 +6,8 @@ import os
import re
import sys
import time
from subprocess import check_output
from subprocess import PIPE
from subprocess import Popen
from tempfile import NamedTemporaryFile
from tempfile import TemporaryDirectory
from subprocess import PIPE, Popen, check_output
from tempfile import NamedTemporaryFile, TemporaryDirectory
from unittest.mock import patch
import pytest
@@ -18,8 +15,7 @@ import traitlets
from traitlets.config import Config
from .. import orm
from ..app import COOKIE_SECRET_BYTES
from ..app import JupyterHub
from ..app import COOKIE_SECRET_BYTES, JupyterHub
from .mocking import MockHub
from .test_api import add_user

View File

@@ -10,16 +10,10 @@ from requests import HTTPError
from traitlets import Any
from traitlets.config import Config
from .mocking import MockPAMAuthenticator
from .mocking import MockStructGroup
from .mocking import MockStructPasswd
from .utils import add_user
from .utils import async_requests
from .utils import get_page
from .utils import public_url
from jupyterhub import auth
from jupyterhub import crypto
from jupyterhub import orm
from jupyterhub import auth, crypto, orm
from .mocking import MockPAMAuthenticator, MockStructGroup, MockStructPasswd
from .utils import add_user, async_requests, get_page, public_url
async def test_pam_auth():

View File

@@ -9,13 +9,11 @@ authentication can expire in a number of ways:
"""
from contextlib import contextmanager
from unittest import mock
from urllib.parse import parse_qs
from urllib.parse import urlparse
from urllib.parse import parse_qs, urlparse
import pytest
from .utils import api_request
from .utils import get_page
from .utils import api_request, get_page
async def refresh_expired(authenticator, user):

View File

@@ -1,13 +1,11 @@
import os
from binascii import b2a_base64
from binascii import b2a_hex
from binascii import b2a_base64, b2a_hex
from unittest.mock import patch
import pytest
from .. import crypto
from ..crypto import decrypt
from ..crypto import encrypt
from ..crypto import decrypt, encrypt
keys = [('%i' % i).encode('ascii') * 32 for i in range(3)]
hex_keys = [b2a_hex(key).decode('ascii') for key in keys]

View File

@@ -9,8 +9,7 @@ from pytest import raises
from traitlets.config import Config
from .. import orm
from ..app import NewToken
from ..app import UpgradeDB
from ..app import NewToken, UpgradeDB
from ..scopes import _check_scopes_exist
here = os.path.abspath(os.path.dirname(__file__))

View File

@@ -16,7 +16,6 @@ from traitlets.config import Config
from .mocking import MockHub
# To test new schemas, add them to the `valid_events`
# and `invalid_events` dictionary below.

View File

@@ -4,8 +4,7 @@ import time
from unittest import mock
import pytest
from requests.exceptions import ConnectionError
from requests.exceptions import SSLError
from requests.exceptions import ConnectionError, SSLError
from ..utils import AnyTimeoutError
from .test_api import add_user

View File

@@ -3,11 +3,9 @@ from unittest import mock
import pytest
from .utils import api_request
from .utils import get_page
from jupyterhub import metrics
from jupyterhub import orm
from jupyterhub import roles
from jupyterhub import metrics, orm, roles
from .utils import api_request, get_page
async def test_total_users(app):

View File

@@ -2,22 +2,15 @@
import asyncio
import json
from unittest import mock
from urllib.parse import urlencode
from urllib.parse import urlparse
from urllib.parse import urlencode, urlparse
import pytest
from tornado.httputil import url_concat
from ..utils import url_path_join
from .mocking import FormSpawner
from .mocking import public_url
from .test_api import add_user
from .test_api import api_request
from .test_api import fill_user
from .test_api import normalize_user
from .test_api import TIMESTAMP
from .utils import async_requests
from .utils import get_page
from .mocking import FormSpawner, public_url
from .test_api import TIMESTAMP, add_user, api_request, fill_user, normalize_user
from .utils import async_requests, get_page
@pytest.fixture

View File

@@ -3,17 +3,13 @@
# Distributed under the terms of the Modified BSD License.
import os
import socket
from datetime import datetime
from datetime import timedelta
from datetime import datetime, timedelta
from unittest import mock
import pytest
from tornado import gen
from .. import crypto
from .. import objects
from .. import orm
from .. import roles
from .. import crypto, objects, orm, roles
from ..emptyclass import EmptyClass
from ..user import User
from .mocking import MockSpawner

View File

@@ -2,31 +2,28 @@
import asyncio
import sys
from unittest import mock
from urllib.parse import parse_qs
from urllib.parse import urlencode
from urllib.parse import urlparse
from urllib.parse import parse_qs, urlencode, urlparse
import pytest
from bs4 import BeautifulSoup
from tornado.escape import url_escape
from tornado.httputil import url_concat
from .. import orm
from .. import roles
from .. import scopes
from .. import orm, roles, scopes
from ..auth import Authenticator
from ..handlers import BaseHandler
from ..utils import url_path_join
from ..utils import url_path_join as ujoin
from .mocking import FalsyCallableFormSpawner
from .mocking import FormSpawner
from .mocking import FalsyCallableFormSpawner, FormSpawner
from .test_api import next_event
from .utils import api_request
from .utils import async_requests
from .utils import AsyncSession
from .utils import get_page
from .utils import public_host
from .utils import public_url
from .utils import (
AsyncSession,
api_request,
async_requests,
get_page,
public_host,
public_url,
)
async def test_root_no_auth(app):

View File

@@ -3,8 +3,7 @@ import json
import os
from contextlib import contextmanager
from subprocess import Popen
from urllib.parse import quote
from urllib.parse import urlparse
from urllib.parse import quote, urlparse
import pytest
from traitlets.config import Config
@@ -12,8 +11,7 @@ from traitlets.config import Config
from ..utils import url_path_join as ujoin
from ..utils import wait_for_http_server
from .mocking import MockHub
from .test_api import add_user
from .test_api import api_request
from .test_api import add_user, api_request
from .utils import skip_if_ssl

View File

@@ -8,14 +8,11 @@ import pytest
from pytest import mark
from tornado.log import app_log
from .. import orm
from .. import roles
from ..scopes import get_scopes_for
from ..scopes import scope_definitions
from .. import orm, roles
from ..scopes import get_scopes_for, scope_definitions
from ..utils import utcnow
from .mocking import MockHub
from .utils import add_user
from .utils import api_request
from .utils import add_user, api_request
@mark.role

View File

@@ -7,22 +7,20 @@ from pytest import mark
from tornado import web
from tornado.httputil import HTTPServerRequest
from .. import orm
from .. import roles
from .. import scopes
from .. import orm, roles, scopes
from ..handlers import BaseHandler
from ..scopes import _check_scope_access
from ..scopes import _expand_self_scope
from ..scopes import _intersect_expanded_scopes
from ..scopes import expand_scopes
from ..scopes import get_scopes_for
from ..scopes import identify_scopes
from ..scopes import needs_scope
from ..scopes import parse_scopes
from ..scopes import Scope
from .utils import add_user
from .utils import api_request
from .utils import auth_header
from ..scopes import (
Scope,
_check_scope_access,
_expand_self_scope,
_intersect_expanded_scopes,
expand_scopes,
get_scopes_for,
identify_scopes,
needs_scope,
parse_scopes,
)
from .utils import add_user, api_request, auth_header
def get_handler_with_scopes(scopes):

View File

@@ -8,14 +8,15 @@ from async_generator import asynccontextmanager
from .. import orm
from ..roles import update_roles
from ..utils import exponential_backoff
from ..utils import maybe_future
from ..utils import random_port
from ..utils import url_path_join
from ..utils import wait_for_http_server
from ..utils import (
exponential_backoff,
maybe_future,
random_port,
url_path_join,
wait_for_http_server,
)
from .mocking import public_url
from .utils import async_requests
from .utils import skip_if_ssl
from .utils import async_requests, skip_if_ssl
mockservice_path = os.path.dirname(os.path.abspath(__file__))
mockservice_py = os.path.join(mockservice_path, 'mockservice.py')

View File

@@ -4,9 +4,7 @@ import os
import sys
from binascii import hexlify
from unittest import mock
from urllib.parse import parse_qs
from urllib.parse import quote
from urllib.parse import urlparse
from urllib.parse import parse_qs, quote, urlparse
import pytest
from bs4 import BeautifulSoup
@@ -14,14 +12,11 @@ from pytest import raises
from tornado.httputil import url_concat
from tornado.log import app_log
from .. import orm
from .. import roles
from .. import scopes
from .. import orm, roles, scopes
from ..services.auth import _ExpiringDict
from ..utils import url_path_join
from .mocking import public_url
from .utils import async_requests
from .utils import AsyncSession
from .utils import AsyncSession, async_requests
# mock for sending monotonic counter way into the future
monotonic_future = mock.patch('time.monotonic', lambda: sys.maxsize)

View File

@@ -2,20 +2,18 @@
import os
import sys
from contextlib import contextmanager
from subprocess import CalledProcessError
from subprocess import check_output
from subprocess import CalledProcessError, check_output
from unittest import mock
from urllib.parse import urlparse
import pytest
import jupyterhub
from .. import orm
from ..utils import url_path_join
from .mocking import public_url
from .mocking import StubSingleUserSpawner
from .utils import async_requests
from .utils import AsyncSession
from .mocking import StubSingleUserSpawner, public_url
from .utils import AsyncSession, async_requests
@contextmanager

View File

@@ -16,14 +16,10 @@ import pytest
from .. import orm
from .. import spawner as spawnermod
from ..objects import Hub
from ..objects import Server
from ..spawner import LocalProcessSpawner
from ..spawner import Spawner
from ..objects import Hub, Server
from ..spawner import LocalProcessSpawner, Spawner
from ..user import User
from ..utils import AnyTimeoutError
from ..utils import new_token
from ..utils import url_path_join
from ..utils import AnyTimeoutError, new_token, url_path_join
from .mocking import public_url
from .test_api import add_user
from .utils import async_requests

View File

@@ -1,10 +1,7 @@
import pytest
from traitlets import HasTraits
from traitlets import TraitError
from traitlets import HasTraits, TraitError
from jupyterhub.traitlets import ByteSpecification
from jupyterhub.traitlets import Command
from jupyterhub.traitlets import URLPrefix
from jupyterhub.traitlets import ByteSpecification, Command, URLPrefix
def test_url_prefix():

View File

@@ -3,8 +3,7 @@ import logging
import pytest
from .._version import _check_version
from .._version import reset_globals
from .._version import _check_version, reset_globals
def setup_function(function):

View File

@@ -7,11 +7,9 @@ import pytest
import requests
from certipy import Certipy
from jupyterhub import metrics
from jupyterhub import orm
from jupyterhub import metrics, orm
from jupyterhub.objects import Server
from jupyterhub.roles import assign_default_roles
from jupyterhub.roles import update_roles
from jupyterhub.roles import assign_default_roles, update_roles
from jupyterhub.utils import url_path_join as ujoin

View File

@@ -4,13 +4,7 @@ Traitlets that are used in JupyterHub
# Copyright (c) Jupyter Development Team.
# Distributed under the terms of the Modified BSD License.
import entrypoints
from traitlets import Integer
from traitlets import List
from traitlets import TraitError
from traitlets import TraitType
from traitlets import Type
from traitlets import Undefined
from traitlets import Unicode
from traitlets import Integer, List, TraitError, TraitType, Type, Undefined, Unicode
class URLPrefix(Unicode):

View File

@@ -3,34 +3,21 @@
import json
import warnings
from collections import defaultdict
from datetime import datetime
from datetime import timedelta
from urllib.parse import quote
from urllib.parse import urlparse
from datetime import datetime, timedelta
from urllib.parse import quote, urlparse
from sqlalchemy import inspect
from tornado import gen
from tornado import web
from tornado import gen, web
from tornado.httputil import urlencode
from tornado.log import app_log
from . import orm
from ._version import __version__
from ._version import _check_version
from .crypto import CryptKeeper
from .crypto import decrypt
from .crypto import encrypt
from .crypto import EncryptionUnavailable
from .crypto import InvalidToken
from .metrics import RUNNING_SERVERS
from .metrics import TOTAL_USERS
from ._version import __version__, _check_version
from .crypto import CryptKeeper, EncryptionUnavailable, InvalidToken, decrypt, encrypt
from .metrics import RUNNING_SERVERS, TOTAL_USERS
from .objects import Server
from .spawner import LocalProcessSpawner
from .utils import AnyTimeoutError
from .utils import make_ssl_context
from .utils import maybe_future
from .utils import url_path_join
from .utils import AnyTimeoutError, make_ssl_context, maybe_future, url_path_join
# detailed messages about the most common failure-to-start errors,
# which manifest timeouts during start

View File

@@ -16,18 +16,14 @@ import threading
import uuid
import warnings
from binascii import b2a_hex
from datetime import datetime
from datetime import timezone
from datetime import datetime, timezone
from hmac import compare_digest
from operator import itemgetter
from async_generator import aclosing
from sqlalchemy.exc import SQLAlchemyError
from tornado import gen
from tornado import ioloop
from tornado import web
from tornado.httpclient import AsyncHTTPClient
from tornado.httpclient import HTTPError
from tornado import gen, ioloop, web
from tornado.httpclient import AsyncHTTPClient, HTTPError
from tornado.log import app_log
# For compatibility with python versions 3.6 or earlier.
@@ -477,6 +473,7 @@ def print_stacks(file=sys.stderr):
# no need to add them to startup
import asyncio
import traceback
from .log import coroutine_frames
print("Active threads: %i" % threading.active_count(), file=file)

View File

@@ -1,3 +1,6 @@
[tool.isort]
profile = "black"
[tool.black]
skip-string-normalization = true
target_version = [

View File

@@ -9,13 +9,11 @@ import shutil
import sys
from subprocess import check_call
from setuptools import Command
from setuptools import setup
from setuptools import Command, setup
from setuptools.command.bdist_egg import bdist_egg
from setuptools.command.build_py import build_py
from setuptools.command.sdist import sdist
v = sys.version_info
if v[:2] < (3, 6):
error = "ERROR: JupyterHub requires Python version 3.6 or above."