forked from ci4s/label-studio
383 lines
13 KiB
Python
383 lines
13 KiB
Python
"""This file and its contents are licensed under the Apache License 2.0. Please see the included NOTICE for copyright information and LICENSE for a copy of the license.
|
|
"""
|
|
import sys
|
|
import logging
|
|
import socket
|
|
import pathlib
|
|
import os
|
|
import io
|
|
import json
|
|
import getpass
|
|
|
|
from colorama import init, Fore
|
|
if sys.platform == 'win32':
|
|
init(convert=True)
|
|
|
|
# on windows there will be problems with sqlite and json1 support, so fix it
|
|
from label_studio.core.utils.windows_sqlite_fix import windows_dll_fix
|
|
windows_dll_fix()
|
|
|
|
from django.core.management import call_command
|
|
from django.db import IntegrityError
|
|
from django.core.wsgi import get_wsgi_application
|
|
from django.db.migrations.executor import MigrationExecutor
|
|
from django.db import connections, DEFAULT_DB_ALIAS
|
|
|
|
from label_studio.core.argparser import parse_input_args
|
|
from label_studio.core.utils.params import get_env
|
|
|
|
logger = logging.getLogger(__name__)
|
|
|
|
|
|
LS_PATH = str(pathlib.Path(__file__).parent.absolute())
|
|
DEFAULT_USERNAME = 'default_user@localhost'
|
|
|
|
|
|
def _setup_env():
|
|
sys.path.insert(0, LS_PATH)
|
|
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "label_studio.core.settings.label_studio")
|
|
application = get_wsgi_application()
|
|
|
|
|
|
def _app_run(host, port):
|
|
http_socket = '{}:{}'.format(host, port)
|
|
call_command('runserver', '--noreload', http_socket)
|
|
|
|
|
|
def is_database_synchronized(database):
|
|
connection = connections[database]
|
|
connection.prepare_database()
|
|
executor = MigrationExecutor(connection)
|
|
targets = executor.loader.graph.leaf_nodes()
|
|
return not executor.migration_plan(targets)
|
|
|
|
|
|
def _apply_database_migrations():
|
|
if not is_database_synchronized(DEFAULT_DB_ALIAS):
|
|
print('Initializing database..')
|
|
call_command('migrate', '--no-color', verbosity=0)
|
|
|
|
|
|
def _get_config(config_path):
|
|
with io.open(os.path.abspath(config_path), encoding='utf-8') as c:
|
|
config = json.load(c)
|
|
return config
|
|
|
|
|
|
def _create_project(title, user, label_config=None, sampling=None, description=None):
|
|
from projects.models import Project
|
|
from organizations.models import Organization
|
|
|
|
project = Project.objects.filter(title=title).first()
|
|
if project is not None:
|
|
print('Project with title "{}" already exists'.format(title))
|
|
else:
|
|
org = Organization.objects.first()
|
|
org.add_user(user)
|
|
project = Project.objects.create(title=title, created_by=user, organization=org)
|
|
print('Project with title "{}" successfully created'.format(title))
|
|
|
|
if label_config is not None:
|
|
with open(os.path.abspath(label_config)) as c:
|
|
project.label_config = c.read()
|
|
|
|
if sampling is not None:
|
|
project.sampling = sampling
|
|
|
|
if description is not None:
|
|
project.description = description
|
|
|
|
project.save()
|
|
return project
|
|
|
|
|
|
def _get_user_info(username):
|
|
from users.models import User
|
|
from users.serializers import UserSerializer
|
|
if not username:
|
|
username = DEFAULT_USERNAME
|
|
|
|
user = User.objects.filter(email=username)
|
|
if not user.exists():
|
|
print({'status': 'error', 'message': f"user {username} doesn't exist"})
|
|
return
|
|
|
|
user = user.first()
|
|
user_data = UserSerializer(user).data
|
|
user_data['token'] = user.auth_token.key
|
|
user_data['status'] = 'ok'
|
|
print('=> User info:')
|
|
print(user_data)
|
|
return user_data
|
|
|
|
|
|
def _create_user(input_args, config):
|
|
from users.models import User
|
|
from organizations.models import Organization
|
|
|
|
username = input_args.username or config.get('username') or get_env('USERNAME')
|
|
password = input_args.password or config.get('password') or get_env('PASSWORD')
|
|
token = input_args.user_token or config.get('user_token') or get_env('USER_TOKEN')
|
|
|
|
if not username:
|
|
user = User.objects.filter(email=DEFAULT_USERNAME).first()
|
|
if user is not None:
|
|
if password and not user.check_password(password):
|
|
user.set_password(password)
|
|
user.save()
|
|
print(f'User {DEFAULT_USERNAME} password changed')
|
|
return user
|
|
print(f'Please enter default user email, or press Enter to use {DEFAULT_USERNAME}')
|
|
username = input('Email: ')
|
|
if not username:
|
|
username = DEFAULT_USERNAME
|
|
if not password:
|
|
password = getpass.getpass(f'Default user password {DEFAULT_USERNAME}: ')
|
|
|
|
try:
|
|
user = User.objects.create_user(email=username, password=password)
|
|
user.is_staff = True
|
|
user.is_superuser = True
|
|
user.save()
|
|
|
|
if token and len(token) > 5:
|
|
from rest_framework.authtoken.models import Token
|
|
Token.objects.filter(key=user.auth_token.key).update(key=token)
|
|
else:
|
|
print(f"Token {token} is not applied to user {DEFAULT_USERNAME} "
|
|
f"because it's empty or len(token) < 5")
|
|
|
|
except IntegrityError:
|
|
print('User {} already exists'.format(username))
|
|
|
|
user = User.objects.get(email=username)
|
|
org = Organization.objects.first()
|
|
if not org:
|
|
Organization.create_organization(created_by=user, title='Label Studio')
|
|
else:
|
|
org.add_user(user)
|
|
|
|
return user
|
|
|
|
|
|
def _init(input_args, config):
|
|
if not _project_exists(input_args.project_name):
|
|
from projects.models import Project
|
|
sampling_map = {'sequential': Project.SEQUENCE, 'uniform': Project.UNIFORM,
|
|
'prediction-score-min': Project.UNCERTAINTY}
|
|
user = _create_user(input_args, config)
|
|
_create_project(
|
|
title=input_args.project_name,
|
|
user=user,
|
|
label_config=input_args.label_config,
|
|
description=input_args.project_desc,
|
|
sampling=sampling_map.get(input_args.sampling, 'sequential')
|
|
)
|
|
else:
|
|
print('Project "{0}" already exists'.format(input_args.project_name))
|
|
|
|
|
|
def _reset_password(input_args):
|
|
from users.models import User
|
|
|
|
username = input_args.username
|
|
if not username:
|
|
username = input('Username: ')
|
|
|
|
user = User.objects.filter(email=username).first()
|
|
if user is None:
|
|
print('User with username {} not found'.format(username))
|
|
return
|
|
|
|
password = input_args.password
|
|
if not password:
|
|
password = getpass.getpass('New password:')
|
|
|
|
if not password:
|
|
print('Can not set empty password')
|
|
return
|
|
|
|
if user.check_password(password):
|
|
print('Entered password is the same as current')
|
|
return
|
|
|
|
user.set_password(password)
|
|
user.save()
|
|
print('Password successfully changed')
|
|
|
|
|
|
def check_port_in_use(host, port):
|
|
logger.info('Checking if host & port is available :: ' + str(host) + ':' + str(port))
|
|
host = host.replace('https://', '').replace('http://', '')
|
|
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
|
return s.connect_ex((host, port)) == 0
|
|
|
|
|
|
def _get_free_port(port, debug):
|
|
# check port is busy
|
|
if not debug:
|
|
original_port = port
|
|
# try up to 1000 new ports
|
|
while check_port_in_use('localhost', port):
|
|
old_port = port
|
|
port = int(port) + 1
|
|
if port - original_port >= 1000:
|
|
raise ConnectionError(
|
|
'\n*** WARNING! ***\n Could not find an available port\n'
|
|
+ ' to launch label studio. \n Last tested port was '
|
|
+ str(port)
|
|
+ '\n****************\n'
|
|
)
|
|
print(
|
|
'\n*** WARNING! ***\n* Port '
|
|
+ str(old_port)
|
|
+ ' is in use.\n'
|
|
+ '* Trying to start at '
|
|
+ str(port)
|
|
+ '\n****************\n'
|
|
)
|
|
return port
|
|
|
|
|
|
def _project_exists(project_name):
|
|
from projects.models import Project
|
|
|
|
return Project.objects.filter(title=project_name).exists()
|
|
|
|
|
|
def main():
|
|
input_args = parse_input_args()
|
|
|
|
# setup logging level
|
|
if input_args.log_level:
|
|
os.environ.setdefault("LOG_LEVEL", input_args.log_level)
|
|
|
|
if input_args.database:
|
|
database_path = pathlib.Path(input_args.database)
|
|
os.environ.setdefault("DATABASE_NAME", str(database_path.absolute()))
|
|
|
|
if input_args.data_dir:
|
|
data_dir_path = pathlib.Path(input_args.data_dir)
|
|
os.environ.setdefault("LABEL_STUDIO_BASE_DATA_DIR", str(data_dir_path.absolute()))
|
|
|
|
config = _get_config(input_args.config_path)
|
|
|
|
# set host name
|
|
host = input_args.host or config.get('host', '')
|
|
if not get_env('HOST'):
|
|
os.environ.setdefault('HOST', host) # it will be passed to settings.HOSTNAME as env var
|
|
|
|
_setup_env()
|
|
_apply_database_migrations()
|
|
|
|
from label_studio.core.utils.common import collect_versions
|
|
versions = collect_versions()
|
|
|
|
if input_args.command == 'reset_password':
|
|
_reset_password(input_args)
|
|
return
|
|
|
|
if input_args.command == 'shell':
|
|
call_command('shell_plus')
|
|
return
|
|
|
|
# print version
|
|
if input_args.command == 'version':
|
|
from label_studio import __version__
|
|
print('\nLabel Studio version:', __version__, '\n')
|
|
print(json.dumps(versions, indent=4))
|
|
|
|
# init
|
|
elif input_args.command == 'user' or getattr(input_args, 'user', None):
|
|
_get_user_info(input_args.username)
|
|
return
|
|
|
|
# init
|
|
elif input_args.command == 'init' or getattr(input_args, 'init', None):
|
|
_init(input_args, config)
|
|
|
|
print('')
|
|
print('Label Studio has been successfully initialized.')
|
|
if input_args.command != 'start':
|
|
print('Start the server: label-studio start ' + input_args.project_name)
|
|
return
|
|
|
|
# start with migrations from old projects, '.' project_name means 'label-studio start' without project name
|
|
elif input_args.command == 'start' and input_args.project_name != '.':
|
|
from label_studio.core.old_ls_migration import migrate_existing_project
|
|
from projects.models import Project
|
|
sampling_map = {'sequential': Project.SEQUENCE, 'uniform': Project.UNIFORM,
|
|
'prediction-score-min': Project.UNCERTAINTY}
|
|
|
|
if not _project_exists(input_args.project_name):
|
|
migrated = False
|
|
project_path = pathlib.Path(input_args.project_name)
|
|
if project_path.exists():
|
|
print('Project directory from previous verion of label-studio found')
|
|
print('Start migrating..')
|
|
config_path = project_path / 'config.json'
|
|
config = _get_config(config_path)
|
|
user = _create_user(input_args, config)
|
|
label_config_path = project_path / 'config.xml'
|
|
project = _create_project(
|
|
title=input_args.project_name,
|
|
user=user,
|
|
label_config=label_config_path,
|
|
sampling=sampling_map.get(config.get('sampling', 'sequential'), Project.UNIFORM),
|
|
description=config.get('description', ''),
|
|
)
|
|
migrate_existing_project(project_path, project, config)
|
|
migrated = True
|
|
|
|
print(
|
|
Fore.LIGHTYELLOW_EX +
|
|
'\n*** WARNING! ***\n'
|
|
+ f'Project {input_args.project_name} migrated to Label Studio Database\n'
|
|
+ "YOU DON'T NEED THIS FOLDER ANYMORE"
|
|
+ '\n****************\n' +
|
|
Fore.WHITE
|
|
)
|
|
if not migrated:
|
|
print(
|
|
'Project "{project_name}" not found. '
|
|
'Did you miss create it first with `label-studio init {project_name}` ?'.format(
|
|
project_name=input_args.project_name
|
|
)
|
|
)
|
|
return
|
|
|
|
# on `start` command, launch browser if --no-browser is not specified and start label studio server
|
|
if input_args.command == 'start' or input_args.command is None:
|
|
from label_studio.core.utils.common import start_browser
|
|
|
|
if get_env('USERNAME') and get_env('PASSWORD') or input_args.username:
|
|
_create_user(input_args, config)
|
|
|
|
# ssl not supported from now
|
|
cert_file = input_args.cert_file or config.get('cert')
|
|
key_file = input_args.key_file or config.get('key')
|
|
if cert_file or key_file:
|
|
logger.error("Label Studio doesn't support SSL web server with cert and key.\n"
|
|
'Use nginx or other servers for it.')
|
|
return
|
|
|
|
# internal port and internal host for server start
|
|
internal_host = input_args.internal_host or config.get('internal_host', '0.0.0.0')
|
|
internal_port = input_args.port or get_env('PORT') or config.get('port', 8080)
|
|
internal_port = int(internal_port)
|
|
internal_port = _get_free_port(internal_port, input_args.debug)
|
|
|
|
# save selected port to global settings
|
|
from django.conf import settings
|
|
settings.INTERNAL_PORT = str(internal_port)
|
|
|
|
# browser
|
|
url = ('http://localhost:' + str(internal_port)) if not host else host
|
|
start_browser(url, input_args.no_browser)
|
|
|
|
_app_run(host=internal_host, port=internal_port)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
sys.exit(main())
|