<commit_before>"""
Unix daemonization support

"""
import atexit
import datetime
import grp
import logging
import os
from os import path
import pwd
import re
import subprocess
import sys
import traceback

LOGGER = logging.getLogger(__name__)

class Daemon(object):

    def __init__(self, controller):
        """Daemonize the controller, optionally passing in the user and group
        to run as, a pid file, if core dumps should be prevented and a path to
        write out exception logs to.

        :param helper.Controller controller: The controller to daaemonize & run

        """
        self.controller = controller
        self.pidfile_path = self._get_pidfile_path()

    def __enter__(self):
        """Context manager method to return the handle to this object.

        :rtype: Daemon

        """
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        """When leaving the context, examine why the context is leaving, if it's
         an exception or what.

        """
        if exc_type and not isinstance(exc_val, SystemExit):
            LOGGER.error('Daemon context manager closed on exception: %r',
                         exc_type)

    def start(self):
        """Daemonize if the process is not already running."""
        if self._is_already_running():
            sys.exit(1)

        exception_log = self._get_exception_log_path()
        try:
            self._daemonize()
            self.controller.start()
        except Exception as error:
            with open(exception_log, 'a') as handle:
                timestamp = datetime.datetime.now().isoformat()
                handle.write('{:->80}\n'.format(' [START]'))
                handle.write('%s Exception [%s]\n' % (sys.argv[0], timestamp))
                handle.write('{:->80}\n'.format(' [INFO]'))
                handle.write('Interpreter: %s\n' % sys.executable)
                handle.write('CLI arguments: %s\n' % ' '.join(sys.argv))
                handle.write('Exception: %s\n' % error)
                handle.write('Traceback:\n')
                output = traceback.format_exception(*sys.exc_info())
                _dev_null = [(handle.write(line),
                             sys.stdout.write(line)) for line in output]
                handle.write('{:->80}\n'.format(' [END]'))
                handle.flush()
            sys.exit(1)

    def _is_already_running(self):
        """Check to see if the process is running, first looking for a pidfile,
        then shelling out in either case, removing a pidfile if it exists but
        the process is not running.

        """
        # Look for the pidfile, if exists determine if the process is alive
        if os.path.exists(self.pidfile_path):
            pid = open(self.pidfile_path).read().strip()
            try:
                os.kill(int(pid), 0)
                sys.stderr.write('Process already running as pid # %s\n' % pid)
                return True
            except OSError as error:
                LOGGER.debug('Found pidfile, no process # %s', error)
                os.unlink(self.pidfile_path)

        # Check the os for a process that is not this one that looks the same
        pattern = ' '.join(sys.argv)
        pattern = '[%s]%s' % (pattern[0], pattern[1:])
        try:
            output = subprocess.check_output('ps a | grep "%s"' % pattern,
                                             shell=True)
        except subprocess.CalledProcessError:
            return False
        pids = [int(pid) for pid in (re.findall(r'^([0-9]+)\s',
                                                output.decode('latin-1')))]
        pids.remove(os.getpid())
        if not pids:
            return False
        if len(pids) == 1:
            pids = pids[0]
        sys.stderr.write('Process already running as pid # %s\n' % pids)
        return True

    def _daemonize(self):
        """Fork into a background process and setup the process, copied in part
        from http://www.jejik.com/files/examples/daemon3x.py

        """
        LOGGER.info('Forking %s into the background', sys.argv[0])
        try:
            pid = os.fork()
            if pid > 0:
                    sys.exit(0)
        except OSError as error:
                raise OSError('Could not fork off parent: %s', error)

        # Decouple from parent environment
        os.chdir('/')
        os.setsid()
        os.umask(0)

        # Fork again
        try:
            pid = os.fork()
            if pid > 0:
                sys.exit(0)
        except OSError as error:
            raise OSError('Could not fork child: %s', error)

        # redirect standard file descriptors
        sys.stdout.flush()
        sys.stderr.flush()
        si = open(os.devnull, 'r')
        so = open(os.devnull, 'a+')
        se = open(os.devnull, 'a+')
        os.dup2(si.fileno(), sys.stdin.fileno())
        os.dup2(so.fileno(), sys.stdout.fileno())
        os.dup2(se.fileno(), sys.stderr.fileno())

        # Automatically call self._remove_pidfile when the app exits
        atexit.register(self._remove_pidfile)
        self._write_pidfile()

    def _get_exception_log_path(self, exception_log=None):
        """Return the normalized path for the connection log, raising an
        exception if it can not written to.

        :param str exception_log: The path the user passed in, if any
        :return: str
        :raises: ValueError
        :raises: OSError

        """
        if exception_log:
            if not os.access(exception_log, os.W_OK):
                raise ValueError('Cannot write to specified exception log path'
                                 ' %s' % exception_log)
            return exception_log

        for exception_log in ['/var/log/%s.errors' % sys.argv[0],
                              '/var/tmp/%s.errors' % sys.argv[0],
                              '/tmp/%s.errors' % sys.argv[0]]:
            if os.access(path.dirname(exception_log), os.W_OK):
                return exception_log

        raise OSError('Could not find an appropriate place for a exception log')

    def _get_gid(self):
        """Return the group id for the specified group.

        :rtype: int

        """
        if not self.controller.config.daemon.group:
            return None
        return grp.getgrnam(self.controller.config.daemon.group).gr_gid

    def _get_pidfile_path(self, pidfile=None):
        """Return the normalized path for the pidfile, raising an
        exception if it can not written to.

        :param str pidfile: The user specified pid file, if any
        :return: str
        :raises: ValueError
        :raises: OSError

        """
        if pidfile:
            pidfile = path.abspath(pidfile)
            if not os.access(path.dirname(pidfile), os.W_OK):
                raise ValueError('Cannot write to specified pid file path'
                                 ' %s' % pidfile)
            return pidfile

        app = sys.argv[0].split('/')[-1]

        for pidfile in ['%s/pids/%s.pid' % (os.getcwd(), app),
                         '/var/run/%s.pid' % app,
                         '/var/run/%s/%s.pid' % (app, app),
                         '/var/tmp/%s.pid' % app,
                         '/tmp/%s.pid' % app,
                         '%s.pid' % app]:
            if os.access(path.dirname(pidfile), os.W_OK):
                return pidfile

        raise OSError('Could not find an appropriate place for a pid file')

    def _get_uid(self):
        """Return the user id for the specified username

        :rtype: int

        """
        if not self.controller.config.daemon.user:
            return None
        return pwd.getpwnam(self.controller.config.daemon.user).pw_uid

    def _remove_pidfile(self):
        """Remove the pid file from the filesystem"""
        with open('/tmp/dbug.log', 'a') as handle:
            handle.write('Removing %s\n' % self.pidfile_path)
        try:
            os.unlink(self.pidfile_path)
        except OSError:
            pass

    def _write_pidfile(self):
        """Write the pid file out with the process number in the pid file"""
        with open(self.pidfile_path, "w") as handle:
            handle.write(str(os.getpid()))
<commit_msg>Change the user and group if set<commit_after>"""
Unix daemonization support

"""
import atexit
import datetime
import grp
import logging
import os
from os import path
import pwd
import re
import subprocess
import sys
import traceback

LOGGER = logging.getLogger(__name__)

class Daemon(object):

    def __init__(self, controller):
        """Daemonize the controller, optionally passing in the user and group
        to run as, a pid file, if core dumps should be prevented and a path to
        write out exception logs to.

        :param helper.Controller controller: The controller to daaemonize & run

        """
        self.controller = controller
        self.pidfile_path = self._get_pidfile_path()

    def __enter__(self):
        """Context manager method to return the handle to this object.

        :rtype: Daemon

        """
        return self

    def __exit__(self, exc_type, exc_val, exc_tb):
        """When leaving the context, examine why the context is leaving, if it's
         an exception or what.

        """
        if exc_type and not isinstance(exc_val, SystemExit):
            LOGGER.error('Daemon context manager closed on exception: %r',
                         exc_type)

    def start(self):
        """Daemonize if the process is not already running."""
        if self._is_already_running():
            sys.exit(1)

        exception_log = self._get_exception_log_path()
        try:
            self._daemonize()
            self.controller.start()
        except Exception as error:
            with open(exception_log, 'a') as handle:
                timestamp = datetime.datetime.now().isoformat()
                handle.write('{:->80}\n'.format(' [START]'))
                handle.write('%s Exception [%s]\n' % (sys.argv[0], timestamp))
                handle.write('{:->80}\n'.format(' [INFO]'))
                handle.write('Interpreter: %s\n' % sys.executable)
                handle.write('CLI arguments: %s\n' % ' '.join(sys.argv))
                handle.write('Exception: %s\n' % error)
                handle.write('Traceback:\n')
                output = traceback.format_exception(*sys.exc_info())
                _dev_null = [(handle.write(line),
                             sys.stdout.write(line)) for line in output]
                handle.write('{:->80}\n'.format(' [END]'))
                handle.flush()
            sys.exit(1)

    def _is_already_running(self):
        """Check to see if the process is running, first looking for a pidfile,
        then shelling out in either case, removing a pidfile if it exists but
        the process is not running.

        """
        # Look for the pidfile, if exists determine if the process is alive
        if os.path.exists(self.pidfile_path):
            pid = open(self.pidfile_path).read().strip()
            try:
                os.kill(int(pid), 0)
                sys.stderr.write('Process already running as pid # %s\n' % pid)
                return True
            except OSError as error:
                LOGGER.debug('Found pidfile, no process # %s', error)
                os.unlink(self.pidfile_path)

        # Check the os for a process that is not this one that looks the same
        pattern = ' '.join(sys.argv)
        pattern = '[%s]%s' % (pattern[0], pattern[1:])
        try:
            output = subprocess.check_output('ps a | grep "%s"' % pattern,
                                             shell=True)
        except subprocess.CalledProcessError:
            return False
        pids = [int(pid) for pid in (re.findall(r'^([0-9]+)\s',
                                                output.decode('latin-1')))]
        pids.remove(os.getpid())
        if not pids:
            return False
        if len(pids) == 1:
            pids = pids[0]
        sys.stderr.write('Process already running as pid # %s\n' % pids)
        return True

    def _daemonize(self):
        """Fork into a background process and setup the process, copied in part
        from http://www.jejik.com/files/examples/daemon3x.py

        """
        LOGGER.info('Forking %s into the background', sys.argv[0])
        try:
            pid = os.fork()
            if pid > 0:
                    sys.exit(0)
        except OSError as error:
                raise OSError('Could not fork off parent: %s', error)

        # Set the user id
        uid = self._get_uid()
        if uid is not None:
            os.setuid(uid)

        # Set the group id
        gid = self._get_gid()
        if gid is not None:
            os.setgid(gid)

        # Decouple from parent environment
        os.chdir('/')
        os.setsid()
        os.umask(0)

        # Fork again
        try:
            pid = os.fork()
            if pid > 0:
                sys.exit(0)
        except OSError as error:
            raise OSError('Could not fork child: %s', error)

        # redirect standard file descriptors
        sys.stdout.flush()
        sys.stderr.flush()
        si = open(os.devnull, 'r')
        so = open(os.devnull, 'a+')
        se = open(os.devnull, 'a+')
        os.dup2(si.fileno(), sys.stdin.fileno())
        os.dup2(so.fileno(), sys.stdout.fileno())
        os.dup2(se.fileno(), sys.stderr.fileno())

        # Automatically call self._remove_pidfile when the app exits
        atexit.register(self._remove_pidfile)
        self._write_pidfile()

    def _get_exception_log_path(self, exception_log=None):
        """Return the normalized path for the connection log, raising an
        exception if it can not written to.

        :param str exception_log: The path the user passed in, if any
        :return: str
        :raises: ValueError
        :raises: OSError

        """
        if exception_log:
            if not os.access(exception_log, os.W_OK):
                raise ValueError('Cannot write to specified exception log path'
                                 ' %s' % exception_log)
            return exception_log

        for exception_log in ['/var/log/%s.errors' % sys.argv[0],
                              '/var/tmp/%s.errors' % sys.argv[0],
                              '/tmp/%s.errors' % sys.argv[0]]:
            if os.access(path.dirname(exception_log), os.W_OK):
                return exception_log

        raise OSError('Could not find an appropriate place for a exception log')

    def _get_gid(self):
        """Return the group id for the specified group.

        :rtype: int

        """
        if not self.controller.config.daemon.group:
            return None
        return grp.getgrnam(self.controller.config.daemon.group).gr_gid

    def _get_pidfile_path(self, pidfile=None):
        """Return the normalized path for the pidfile, raising an
        exception if it can not written to.

        :param str pidfile: The user specified pid file, if any
        :return: str
        :raises: ValueError
        :raises: OSError

        """
        if pidfile:
            pidfile = path.abspath(pidfile)
            if not os.access(path.dirname(pidfile), os.W_OK):
                raise ValueError('Cannot write to specified pid file path'
                                 ' %s' % pidfile)
            return pidfile

        app = sys.argv[0].split('/')[-1]

        for pidfile in ['%s/pids/%s.pid' % (os.getcwd(), app),
                         '/var/run/%s.pid' % app,
                         '/var/run/%s/%s.pid' % (app, app),
                         '/var/tmp/%s.pid' % app,
                         '/tmp/%s.pid' % app,
                         '%s.pid' % app]:
            if os.access(path.dirname(pidfile), os.W_OK):
                return pidfile

        raise OSError('Could not find an appropriate place for a pid file')

    def _get_uid(self):
        """Return the user id for the specified username

        :rtype: int

        """
        if not self.controller.config.daemon.user:
            return None
        return pwd.getpwnam(self.controller.config.daemon.user).pw_uid

    def _remove_pidfile(self):
        """Remove the pid file from the filesystem"""
        with open('/tmp/dbug.log', 'a') as handle:
            handle.write('Removing %s\n' % self.pidfile_path)
        try:
            os.unlink(self.pidfile_path)
        except OSError:
            pass

    def _write_pidfile(self):
        """Write the pid file out with the process number in the pid file"""
        with open(self.pidfile_path, "w") as handle:
            handle.write(str(os.getpid()))
<|endoftext|>


<commit_before>import os
import subprocess
import sys
import time
import shutil
import zipfile

UE_VERSIONS = ['4.15', '4.16', '4.17', '4.18', '4.19']
PYTHON_VERSIONS = ["C:/Program Files/Python36", "C:/Program Files/Python35", "C:/Python27"]

RELEASE_DIR = sys.argv[1].rstrip('/')

def zipdir(path, zh, base):
    for root, dirs, files in os.walk(path):
        for file in files:
            filename = os.path.join(root, file)
            zh.write(filename, os.path.relpath(filename, base))

def msbuild(project, python_version, variant):
    base_environ = os.environ
    base_environ.update({'PYTHONHOME': python_version})
    base_environ.update({'UEP_ENABLE_UNITY_BUILD': '1'})
    if variant == 'threaded_':
        base_environ.update({'UEP_ENABLE_THREADS': '1'})
    #vs = '"C:/Program Files (x86)/MSBuild/14.0/Bin/MSBuild.exe"'
    vs = '"C:/Program Files (x86)/Microsoft Visual Studio/2017/Community/MSBuild/15.0/Bin/MSBuild.exe"'
    process = subprocess.Popen('{0} {1} /m /t:Rebuild /p:Configuration="Development Editor" /p:Platform=Win64'.format(vs, project), env=base_environ)
    while process.poll() is None:
        time.sleep(0.5)
    if process.returncode != 0:
        sys.exit(process.returncode)

def commandlet(version, project):
    ue_editor = os.path.join('D:/', 'UE_{0}'.format(version), 'Engine/Binaries/Win64/UE4Editor-Cmd.exe')
    process = subprocess.Popen('{0} D:/{1}/{2}.uproject -run=PyCommandlet D:/{3}/Plugins/UnrealEnginePython/tools/release_check.py'.format(ue_editor, project, project, project))
    while process.poll() is None:
        time.sleep(0.5)
    # ignore return code, has too much different meanings for commandlets

def git(project):
    process = subprocess.Popen('git checkout master', cwd='D:/{0}/Plugins/UnrealEnginePython'.format(project))
    while process.poll() is None:
        time.sleep(0.5)
    if process.returncode != 0:
        sys.exit(process.returncode)
    process = subprocess.Popen('git pull', cwd='D:/{0}/Plugins/UnrealEnginePython'.format(project))
    while process.poll() is None:
        time.sleep(0.5)
    if process.returncode != 0:
        sys.exit(process.returncode)

main_start = time.time()
for ue_version in UE_VERSIONS:
    project = 'PyTest{0}'.format(ue_version.replace('.', ''))
    sln = os.path.join('D:/', project, '{0}.sln'.format(project))
    git(project)
    for python_version in PYTHON_VERSIONS:
        for variant in ('', 'threaded_'):
            python_sanitized = os.path.basename(python_version).lower()
            start = time.time()
            print('\n\n***** building {0} for {1} ({2}) *****\n\n'.format(sln, python_version, variant))
            sys.stdout.flush()
            msbuild(sln, python_version, variant)
            commandlet(ue_version, project)
            end = time.time()
            for item in ('UE4Editor.modules', 'UE4Editor-UnrealEnginePython.dll', 'UE4Editor-PythonConsole.dll', 'UE4Editor-PythonEditor.dll'):
                shutil.copyfile('D:/{0}/Plugins/UnrealEnginePython/Binaries/Win64/{1}'.format(project, item), '{0}/UnrealEnginePython/Binaries/Win64/{1}'.format(RELEASE_DIR, item))
                if python_sanitized == 'python36':
                    shutil.copyfile('D:/{0}/Plugins/UnrealEnginePython/Binaries/Win64/{1}'.format(project, item), '{0}/Embedded/UnrealEnginePython/Binaries/Win64/{1}'.format(RELEASE_DIR, item))
            filename = 'UnrealEnginePython_{0}_{1}_{2}_{3}win64.zip'.format(os.path.basename(RELEASE_DIR), ue_version.replace('.','_'), python_sanitized, variant)
            zh = zipfile.ZipFile(os.path.join(RELEASE_DIR, filename), 'w', zipfile.ZIP_DEFLATED)
            zipdir(os.path.join(RELEASE_DIR, 'UnrealEnginePython'), zh, RELEASE_DIR)
            zh.close()
            if python_sanitized == 'python36':
                filename = 'UnrealEnginePython_{0}_{1}_{2}_{3}embedded_win64.zip'.format(os.path.basename(RELEASE_DIR), ue_version.replace('.','_'), python_sanitized, variant)
                zh = zipfile.ZipFile(os.path.join(RELEASE_DIR, filename), 'w', zipfile.ZIP_DEFLATED)
                zipdir(os.path.join(RELEASE_DIR, 'Embedded/UnrealEnginePython'), zh, os.path.join(RELEASE_DIR, 'Embedded'))
                zh.close()
            print('\n\n***** built {0} for {1} in {2} seconds [{3}]*****\n\n'.format(project, python_version, end-start, filename))

main_end = time.time()
print('release ready after {0} seconds'.format(main_end-main_start))
<commit_msg>added automation module to the build tool<commit_after>import os
import subprocess
import sys
import time
import shutil
import zipfile

UE_VERSIONS = ['4.15', '4.16', '4.17', '4.18', '4.19']
PYTHON_VERSIONS = ["C:/Program Files/Python36", "C:/Program Files/Python35", "C:/Python27"]

RELEASE_DIR = sys.argv[1].rstrip('/')

def zipdir(path, zh, base):
    for root, dirs, files in os.walk(path):
        for file in files:
            filename = os.path.join(root, file)
            zh.write(filename, os.path.relpath(filename, base))

def msbuild(project, python_version, variant):
    base_environ = os.environ
    base_environ.update({'PYTHONHOME': python_version})
    base_environ.update({'UEP_ENABLE_UNITY_BUILD': '1'})
    if variant == 'threaded_':
        base_environ.update({'UEP_ENABLE_THREADS': '1'})
    #vs = '"C:/Program Files (x86)/MSBuild/14.0/Bin/MSBuild.exe"'
    vs = '"C:/Program Files (x86)/Microsoft Visual Studio/2017/Community/MSBuild/15.0/Bin/MSBuild.exe"'
    process = subprocess.Popen('{0} {1} /m /t:Rebuild /p:Configuration="Development Editor" /p:Platform=Win64'.format(vs, project), env=base_environ)
    while process.poll() is None:
        time.sleep(0.5)
    if process.returncode != 0:
        sys.exit(process.returncode)

def commandlet(version, project):
    ue_editor = os.path.join('D:/', 'UE_{0}'.format(version), 'Engine/Binaries/Win64/UE4Editor-Cmd.exe')
    process = subprocess.Popen('{0} D:/{1}/{2}.uproject -run=PyCommandlet D:/{3}/Plugins/UnrealEnginePython/tools/release_check.py'.format(ue_editor, project, project, project))
    while process.poll() is None:
        time.sleep(0.5)
    # ignore return code, has too much different meanings for commandlets

def git(project):
    process = subprocess.Popen('git checkout master', cwd='D:/{0}/Plugins/UnrealEnginePython'.format(project))
    while process.poll() is None:
        time.sleep(0.5)
    if process.returncode != 0:
        sys.exit(process.returncode)
    process = subprocess.Popen('git pull', cwd='D:/{0}/Plugins/UnrealEnginePython'.format(project))
    while process.poll() is None:
        time.sleep(0.5)
    if process.returncode != 0:
        sys.exit(process.returncode)

main_start = time.time()
for ue_version in UE_VERSIONS:
    project = 'PyTest{0}'.format(ue_version.replace('.', ''))
    sln = os.path.join('D:/', project, '{0}.sln'.format(project))
    git(project)
    for python_version in PYTHON_VERSIONS:
        for variant in ('', 'threaded_'):
            python_sanitized = os.path.basename(python_version).lower()
            start = time.time()
            print('\n\n***** building {0} for {1} ({2}) *****\n\n'.format(sln, python_version, variant))
            sys.stdout.flush()
            msbuild(sln, python_version, variant)
            commandlet(ue_version, project)
            end = time.time()
            for item in ('UE4Editor.modules', 'UE4Editor-UnrealEnginePython.dll', 'UE4Editor-PythonConsole.dll', 'UE4Editor-PythonEditor.dll', 'UE4Editor-PythonAutomation.dll'):
                shutil.copyfile('D:/{0}/Plugins/UnrealEnginePython/Binaries/Win64/{1}'.format(project, item), '{0}/UnrealEnginePython/Binaries/Win64/{1}'.format(RELEASE_DIR, item))
                if python_sanitized == 'python36':
                    shutil.copyfile('D:/{0}/Plugins/UnrealEnginePython/Binaries/Win64/{1}'.format(project, item), '{0}/Embedded/UnrealEnginePython/Binaries/Win64/{1}'.format(RELEASE_DIR, item))
            filename = 'UnrealEnginePython_{0}_{1}_{2}_{3}win64.zip'.format(os.path.basename(RELEASE_DIR), ue_version.replace('.','_'), python_sanitized, variant)
            zh = zipfile.ZipFile(os.path.join(RELEASE_DIR, filename), 'w', zipfile.ZIP_DEFLATED)
            zipdir(os.path.join(RELEASE_DIR, 'UnrealEnginePython'), zh, RELEASE_DIR)
            zh.close()
            if python_sanitized == 'python36':
                filename = 'UnrealEnginePython_{0}_{1}_{2}_{3}embedded_win64.zip'.format(os.path.basename(RELEASE_DIR), ue_version.replace('.','_'), python_sanitized, variant)
                zh = zipfile.ZipFile(os.path.join(RELEASE_DIR, filename), 'w', zipfile.ZIP_DEFLATED)
                zipdir(os.path.join(RELEASE_DIR, 'Embedded/UnrealEnginePython'), zh, os.path.join(RELEASE_DIR, 'Embedded'))
                zh.close()
            print('\n\n***** built {0} for {1} in {2} seconds [{3}]*****\n\n'.format(project, python_version, end-start, filename))

main_end = time.time()
print('release ready after {0} seconds'.format(main_end-main_start))
<|endoftext|>


<commit_before>from bss_client.exception import BSSError
from bss_client.request import Request

class BSSClient(object):

    def __init__(self, endpoint, key, secret, requests_params=None):
        self.endpoint = endpoint
        self.key = key
        self.secret = secret
        if requests_params is None:
            self.requests_params = {}
        else:
            self.requests_params = requests_params

    def create_request(self):
        return Request(self.endpoint, self.key, self.secret,
                       **self.requests_params)

    def _handle_json_response(self, rsp):
        if 200 <= rsp.status_code < 300:
            return rsp.json()
        raise BSSError(rsp.status_code, rsp.text)

    def list_services(self, category=None):
        req = self.create_request()
        if category:
            req.add_param('category', category)
        path = '/account/services'
        rsp = req.request('GET', path)
        return self._handle_json_response(rsp)

    def list_catalog(self, expand='productBundleRevisions'):
        req = self.create_request()
        req.add_param('expand', expand)
        rsp = req.request('GET', '/account/catalog')
        return self._handle_json_response(rsp)

    def list_subscriptions(self):
        req = self.create_request()
        rsp = req.request('GET', '/subscriptions')
        return self._handle_json_response(rsp)

    def get_user(self, user_id):
        req = self.create_request()
        rsp = req.request('GET', '/users/{0}'.format(user_id))
        return self._handle_json_response(rsp)

    def get_account(self):
        req = self.create_request()
        rsp = req.request('GET', '/account')
        return self._handle_json_response(rsp)

    def get_catalog(self, catalog_uuid):
        req = self.create_request()
        rsp = req.request('GET', '/subscriptions/{0}'.format(catalog_uuid))
        return self._handle_json_response(rsp)

    def create_subscription(self, configurationdata, context, productbundleid,
                            resourcetype, serviceinstanceuuid, tenantparam):
        req = self.create_request()
        req.add_param('provision', 'true')
        req.add_param('configurationdata', configurationdata)
        req.add_param('context', context)
        req.add_param('productbundleid', productbundleid)
        req.add_param('resourcetype', resourcetype)
        req.add_param('serviceinstanceuuid', serviceinstanceuuid)
        req.add_param('tenantparam', tenantparam)
        rsp = req.request('POST', '/subscriptions')
        return self._handle_json_response(rsp)
<commit_msg>tenantparam can be found automatically from account data<commit_after>from bss_client.exception import BSSError
from bss_client.request import Request

class BSSClient(object):

    def __init__(self, endpoint, key, secret, requests_params=None):
        self.endpoint = endpoint
        self.key = key
        self.secret = secret
        if requests_params is None:
            self.requests_params = {}
        else:
            self.requests_params = requests_params

    def create_request(self):
        return Request(self.endpoint, self.key, self.secret,
                       **self.requests_params)

    def _handle_json_response(self, rsp):
        if 200 <= rsp.status_code < 300:
            return rsp.json()
        raise BSSError(rsp.status_code, rsp.text)

    def list_services(self, category=None):
        req = self.create_request()
        if category:
            req.add_param('category', category)
        path = '/account/services'
        rsp = req.request('GET', path)
        return self._handle_json_response(rsp)

    def list_catalog(self, expand='productBundleRevisions'):
        req = self.create_request()
        req.add_param('expand', expand)
        rsp = req.request('GET', '/account/catalog')
        return self._handle_json_response(rsp)

    def list_subscriptions(self):
        req = self.create_request()
        rsp = req.request('GET', '/subscriptions')
        return self._handle_json_response(rsp)

    def get_user(self, user_id):
        req = self.create_request()
        rsp = req.request('GET', '/users/{0}'.format(user_id))
        return self._handle_json_response(rsp)

    def get_account(self):
        req = self.create_request()
        rsp = req.request('GET', '/account')
        return self._handle_json_response(rsp)

    def get_catalog(self, catalog_uuid):
        req = self.create_request()
        rsp = req.request('GET', '/subscriptions/{0}'.format(catalog_uuid))
        return self._handle_json_response(rsp)

    def create_subscription(self, configurationdata, context, productbundleid,
                            resourcetype, serviceinstanceuuid, tenantparam=None):
        req = self.create_request()
        req.add_param('provision', 'true')
        req.add_param('configurationdata', configurationdata)
        req.add_param('context', context)
        req.add_param('productbundleid', productbundleid)
        req.add_param('resourcetype', resourcetype)
        req.add_param('serviceinstanceuuid', serviceinstanceuuid)
        if not tenantparam:
            tenantparam = self.get_account()['account']['uuid']
        req.add_param('tenantparam', tenantparam)
        rsp = req.request('POST', '/subscriptions')
        return self._handle_json_response(rsp)
<|endoftext|>


<commit_before>from collections import OrderedDict
from functools import lru_cache

from orderedset import OrderedSet

class Columns(OrderedDict):

    KEY_TRANSLATION_TABLE = {
        ord(c): None for c in ' _-'
    }

    """
    `Columns` behaves like an ordered dictionary, except it normalises
    case, whitespace, hypens and underscores in keys.

    In other words,
    Columns({'FIRST_NAME': 'example'}) == Columns({'first name': 'example'})
    >>> True
    """

    def __init__(self, row_dict):
        for key, value in row_dict.items():
            self[key] = value

    @classmethod
    def from_keys(cls, keys):
        """
        This behaves like `dict.from_keys`, except:
        - it normalises the keys to ignore case, whitespace, hypens and
          underscores
        - it stores the original, unnormalised key as the value of the
          item so it can be retrieved later
        """
        return cls(OrderedDict([(key, key) for key in keys]))

    def keys(self):
        return OrderedSet(super().keys())

    def __getitem__(self, key):
        return super().__getitem__(self.make_key(key))

    def __setitem__(self, key, value):
        super().__setitem__(self.make_key(key), value)

    def __contains__(self, key):
        return super().__contains__(self.make_key(key))

    def get(self, key, default=None):
        return self[key] if key in self else default

    def copy(self):
        return self.__class__(super().copy())

    def as_dict_with_keys(self, keys):
        return {
            key: self.get(key) for key in keys
        }

    @staticmethod
    @lru_cache(maxsize=32, typed=False)
    def make_key(original_key):
        if original_key is None:
            return None
        return original_key.translate(Columns.KEY_TRANSLATION_TABLE).lower()

class Row(Columns):

    message_too_long = False
    message_empty = False

    def __init__(
        self,
        row_dict,
        *,
        index,
        error_fn,
        recipient_column_headers,
        placeholders,
        template,
        allow_international_letters,
    ):

        self.index = index
        self.recipient_column_headers = recipient_column_headers
        self.placeholders = placeholders
        self.allow_international_letters = allow_international_letters

        if template:
            template.values = row_dict
            self.template_type = template.template_type
            # we do not validate email size for CSVs to avoid performance issues
            if self.template_type == "email":
                self.message_too_long = False
            else:
                self.message_too_long = template.is_message_too_long()
            self.message_empty = template.is_message_empty()

        super().__init__(OrderedDict(
            (key, Cell(key, value, error_fn, self.placeholders))
            for key, value in row_dict.items()
        ))

    def __getitem__(self, key):
        return super().__getitem__(key) if key in self else Cell()

    def get(self, key, default=None):
        if key not in self and default is not None:
            return default
        return self[key]

    @property
    def has_error(self):
        return self.has_error_spanning_multiple_cells or any(
            cell.error for cell in self.values()
        )

    @property
    def has_bad_recipient(self):
        if self.template_type == 'letter':
            return self.has_bad_postal_address
        return self.get(self.recipient_column_headers[0]).recipient_error

    @property
    def has_bad_postal_address(self):
        return self.template_type == 'letter' and not self.as_postal_address.valid

    @property
    def has_error_spanning_multiple_cells(self):
        return self.message_too_long or self.message_empty or self.has_bad_postal_address

    @property
    def has_missing_data(self):
        return any(
            cell.error == Cell.missing_field_error
            for cell in self.values()
        )

    @property
    def recipient(self):
        columns = [
            self.get(column).data for column in self.recipient_column_headers
        ]
        return columns[0] if len(columns) == 1 else columns

    @property
    def as_postal_address(self):
        from notifications_utils.postal_address import PostalAddress
        return PostalAddress.from_personalisation(
            self.recipient_and_personalisation,
            allow_international_letters=self.allow_international_letters,
        )

    @property
    def personalisation(self):
        return Columns({
            key: cell.data for key, cell in self.items()
            if key in self.placeholders
        })

    @property
    def recipient_and_personalisation(self):
        return Columns({
            key: cell.data for key, cell in self.items()
        })

class Cell():

    missing_field_error = 'Missing'

    def __init__(
        self,
        key=None,
        value=None,
        error_fn=None,
        placeholders=None
    ):
        self.data = value
        self.error = error_fn(key, value) if error_fn else None
        self.ignore = Columns.make_key(key) not in (placeholders or [])

    def __eq__(self, other):
        if not other.__class__ == self.__class__:
            return False
        return all((
            self.data == other.data,
            self.error == other.error,
            self.ignore == other.ignore,
        ))

    @property
    def recipient_error(self):
        return self.error not in {None, self.missing_field_error}
<commit_msg>Fix B018 Found useless expression<commit_after>from collections import OrderedDict
from functools import lru_cache

from orderedset import OrderedSet

class Columns(OrderedDict):

    """
    `Columns` behaves like an ordered dictionary, except it normalises
    case, whitespace, hypens and underscores in keys.

    In other words,
    Columns({'FIRST_NAME': 'example'}) == Columns({'first name': 'example'})
    >>> True
    """

    KEY_TRANSLATION_TABLE = {
        ord(c): None for c in ' _-'
    }

    def __init__(self, row_dict):
        for key, value in row_dict.items():
            self[key] = value

    @classmethod
    def from_keys(cls, keys):
        """
        This behaves like `dict.from_keys`, except:
        - it normalises the keys to ignore case, whitespace, hypens and
          underscores
        - it stores the original, unnormalised key as the value of the
          item so it can be retrieved later
        """
        return cls(OrderedDict([(key, key) for key in keys]))

    def keys(self):
        return OrderedSet(super().keys())

    def __getitem__(self, key):
        return super().__getitem__(self.make_key(key))

    def __setitem__(self, key, value):
        super().__setitem__(self.make_key(key), value)

    def __contains__(self, key):
        return super().__contains__(self.make_key(key))

    def get(self, key, default=None):
        return self[key] if key in self else default

    def copy(self):
        return self.__class__(super().copy())

    def as_dict_with_keys(self, keys):
        return {
            key: self.get(key) for key in keys
        }

    @staticmethod
    @lru_cache(maxsize=32, typed=False)
    def make_key(original_key):
        if original_key is None:
            return None
        return original_key.translate(Columns.KEY_TRANSLATION_TABLE).lower()

class Row(Columns):

    message_too_long = False
    message_empty = False

    def __init__(
        self,
        row_dict,
        *,
        index,
        error_fn,
        recipient_column_headers,
        placeholders,
        template,
        allow_international_letters,
    ):

        self.index = index
        self.recipient_column_headers = recipient_column_headers
        self.placeholders = placeholders
        self.allow_international_letters = allow_international_letters

        if template:
            template.values = row_dict
            self.template_type = template.template_type
            # we do not validate email size for CSVs to avoid performance issues
            if self.template_type == "email":
                self.message_too_long = False
            else:
                self.message_too_long = template.is_message_too_long()
            self.message_empty = template.is_message_empty()

        super().__init__(OrderedDict(
            (key, Cell(key, value, error_fn, self.placeholders))
            for key, value in row_dict.items()
        ))

    def __getitem__(self, key):
        return super().__getitem__(key) if key in self else Cell()

    def get(self, key, default=None):
        if key not in self and default is not None:
            return default
        return self[key]

    @property
    def has_error(self):
        return self.has_error_spanning_multiple_cells or any(
            cell.error for cell in self.values()
        )

    @property
    def has_bad_recipient(self):
        if self.template_type == 'letter':
            return self.has_bad_postal_address
        return self.get(self.recipient_column_headers[0]).recipient_error

    @property
    def has_bad_postal_address(self):
        return self.template_type == 'letter' and not self.as_postal_address.valid

    @property
    def has_error_spanning_multiple_cells(self):
        return self.message_too_long or self.message_empty or self.has_bad_postal_address

    @property
    def has_missing_data(self):
        return any(
            cell.error == Cell.missing_field_error
            for cell in self.values()
        )

    @property
    def recipient(self):
        columns = [
            self.get(column).data for column in self.recipient_column_headers
        ]
        return columns[0] if len(columns) == 1 else columns

    @property
    def as_postal_address(self):
        from notifications_utils.postal_address import PostalAddress
        return PostalAddress.from_personalisation(
            self.recipient_and_personalisation,
            allow_international_letters=self.allow_international_letters,
        )

    @property
    def personalisation(self):
        return Columns({
            key: cell.data for key, cell in self.items()
            if key in self.placeholders
        })

    @property
    def recipient_and_personalisation(self):
        return Columns({
            key: cell.data for key, cell in self.items()
        })

class Cell():

    missing_field_error = 'Missing'

    def __init__(
        self,
        key=None,
        value=None,
        error_fn=None,
        placeholders=None
    ):
        self.data = value
        self.error = error_fn(key, value) if error_fn else None
        self.ignore = Columns.make_key(key) not in (placeholders or [])

    def __eq__(self, other):
        if not other.__class__ == self.__class__:
            return False
        return all((
            self.data == other.data,
            self.error == other.error,
            self.ignore == other.ignore,
        ))

    @property
    def recipient_error(self):
        return self.error not in {None, self.missing_field_error}
<|endoftext|>


<commit_before># -*- coding: utf-8 -*-
# Licensed under a 3-clause BSD style license - see LICENSE.rst
#
# Astropy shared Sphinx settings.  These settings are shared between
# astropy itself and affiliated packages.
#
# Note that not all possible configuration values are present in this file.
#
# All configuration values have a default; values that are commented out
# serve to show the default.

import os
import sys
import warnings

from os import path

import sphinx
from distutils.version import LooseVersion

# -- General configuration ----------------------------------------------------

# The version check in Sphinx itself can only compare the major and
# minor parts of the version number, not the micro.  To do a more
# specific version check, call check_sphinx_version("x.y.z.") from
# your project's conf.py
needs_sphinx = '1.3'

on_rtd = os.environ.get('READTHEDOCS', None) == 'True'

def check_sphinx_version(expected_version):
    sphinx_version = LooseVersion(sphinx.__version__)
    expected_version = LooseVersion(expected_version)
    if sphinx_version < expected_version:
        raise RuntimeError(
            "At least Sphinx version {0} is required to build this "
            "documentation.  Found {1}.".format(
                expected_version, sphinx_version))

# Configuration for intersphinx: refer to the Python standard library.
intersphinx_mapping = {
    'python': ('https://docs.python.org/3/',
               (None, 'http://data.astropy.org/intersphinx/python3.inv')),
    'pythonloc': ('http://docs.python.org/',
                  path.abspath(path.join(path.dirname(__file__),
                                         'local/python3_local_links.inv'))),
    'numpy': ('https://docs.scipy.org/doc/numpy/',
              (None, 'http://data.astropy.org/intersphinx/numpy.inv')),
    'scipy': ('https://docs.scipy.org/doc/scipy/reference/',
              (None, 'http://data.astropy.org/intersphinx/scipy.inv')),
    'matplotlib': ('http://matplotlib.org/',
                   (None, 'http://data.astropy.org/intersphinx/matplotlib.inv')),
    'astropy': ('http://docs.astropy.org/en/stable/', None),
    'h5py': ('http://docs.h5py.org/en/latest/', None)}

# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
exclude_patterns = ['_build']

# Add any paths that contain templates here, relative to this directory.
# templates_path = ['_templates']

# The suffix of source filenames.
source_suffix = '.rst'

# The encoding of source files.
#source_encoding = 'utf-8-sig'

# The master toctree document.
master_doc = 'index'

# The reST default role (used for this markup: `text`) to use for all
# documents. Set to the "smart" one.
default_role = 'obj'

# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
#language = None

# This is added to the end of RST files - a good place to put substitutions to
# be used globally.
rst_epilog = """
.. _Astropy: http://astropy.org
"""

# A list of warning types to suppress arbitrary warning messages. We mean to
# override directives in astropy_helpers.sphinx.ext.autodoc_enhancements,
# thus need to ignore those warning. This can be removed once the patch gets
# released in upstream Sphinx (https://github.com/sphinx-doc/sphinx/pull/1843).
# Suppress the warnings requires Sphinx v1.4.2
suppress_warnings = ['app.add_directive', ]

# -- Project information ------------------------------------------------------

# There are two options for replacing |today|: either, you set today to some
# non-false value, then it is used:
#today = ''
# Else, today_fmt is used as the format for a strftime call.
#today_fmt = '%B %d, %Y'

# If true, '()' will be appended to :func: etc. cross-reference text.
#add_function_parentheses = True

# If true, the current module name will be prepended to all description
# unit titles (such as .. function::).
#add_module_names = True

# If true, sectionauthor and moduleauthor directives will be shown in the
# output. They are ignored by default.
#show_authors = False

# The name of the Pygments (syntax highlighting) style to use.
#pygments_style = 'sphinx'

# A list of ignored prefixes for module index sorting.
#modindex_common_prefix = []

# -- Settings for extensions and extension options ----------------------------

# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
extensions = [
    'sphinx.ext.autodoc',
    'sphinx.ext.intersphinx',
    'sphinx.ext.todo',
    'sphinx.ext.coverage',
    'sphinx.ext.inheritance_diagram',
    'sphinx.ext.viewcode',
    'astropy_helpers.extern.numpydoc',
    'astropy_helpers.extern.automodapi.automodapi',
    'astropy_helpers.extern.automodapi.smart_resolver',
    'astropy_helpers.sphinx.ext.tocdepthfix',
    'astropy_helpers.sphinx.ext.doctest',
    'astropy_helpers.sphinx.ext.changelog_links']

if not on_rtd and LooseVersion(sphinx.__version__) < LooseVersion('1.4'):
    extensions.append('sphinx.ext.pngmath')
else:
    extensions.append('sphinx.ext.mathjax')

try:
    import matplotlib.sphinxext.plot_directive
    extensions += [matplotlib.sphinxext.plot_directive.__name__]
# AttributeError is checked here in case matplotlib is installed but
# Sphinx isn't.  Note that this module is imported by the config file
# generator, even if we're not building the docs.
except (ImportError, AttributeError):
    warnings.warn(
        "matplotlib's plot_directive could not be imported. " +
        "Inline plots will not be included in the output")

# Don't show summaries of the members in each class along with the
# class' docstring
numpydoc_show_class_members = False

autosummary_generate = True

automodapi_toctreedirnm = 'api'

# Class documentation should contain *both* the class docstring and
# the __init__ docstring
autoclass_content = "both"

# Render inheritance diagrams in SVG
graphviz_output_format = "svg"

graphviz_dot_args = [
    '-Nfontsize=10',
    '-Nfontname=Helvetica Neue, Helvetica, Arial, sans-serif',
    '-Efontsize=10',
    '-Efontname=Helvetica Neue, Helvetica, Arial, sans-serif',
    '-Gfontsize=10',
    '-Gfontname=Helvetica Neue, Helvetica, Arial, sans-serif'
]

# -- Options for HTML output -------------------------------------------------

# Add any paths that contain custom themes here, relative to this directory.
import sphinx_astropy_theme
html_theme_path = sphinx_astropy_theme.get_html_theme_path()

# The theme to use for HTML and HTML Help pages.  See the documentation for
# a list of builtin themes.
html_theme = 'bootstrap-astropy'

# Custom sidebar templates, maps document names to template names.
html_sidebars = {
    '**': ['localtoc.html'],
    'search': [],
    'genindex': [],
    'py-modindex': [],
}

# The name of an image file (within the static path) to use as favicon of the
# docs.  This file should be a Windows icon file (.ico) being 16x16 or 32x32
# pixels large.

# included in the bootstrap-astropy theme
html_favicon = path.join(html_theme_path[0], html_theme, 'static',
                         'astropy_logo.ico')

# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
# using the given strftime format.
html_last_updated_fmt = '%d %b %Y'

# Theme options are theme-specific and customize the look and feel of a theme
# further.  For a list of options available for each theme, see the
# documentation.
#html_theme_options = {}

# The name for this set of Sphinx documents.  If None, it defaults to
# "<project> v<release> documentation".
#html_title = None

# A shorter title for the navigation bar.  Default is the same as html_title.
#html_short_title = None

# If true, SmartyPants will be used to convert quotes and dashes to
# typographically correct entities.
#html_use_smartypants = True

# Additional templates that should be rendered to pages, maps page names to
# template names.
#html_additional_pages = {}

# If false, no module index is generated.
#html_domain_indices = True

# If false, no index is generated.
#html_use_index = True

# If true, the index is split into individual pages for each letter.
#html_split_index = False

# If true, links to the reST sources are added to the pages.
#html_show_sourcelink = True

# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
#html_show_sphinx = True

# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
#html_show_copyright = True

# If true, an OpenSearch description file will be output, and all pages will
# contain a <link> tag referring to it.  The value of this option must be the
# base URL from which the finished HTML is served.
#html_use_opensearch = ''

# This is the file name suffix for HTML files (e.g. ".xhtml").
#html_file_suffix = None

# -- Options for LaTeX output ------------------------------------------------

# The paper size ('letter' or 'a4').
#latex_paper_size = 'letter'

# The font size ('10pt', '11pt' or '12pt').
#latex_font_size = '10pt'

# For "manual" documents, if this is true, then toplevel headings are parts,
# not chapters.
latex_toplevel_sectioning = 'part'

# If true, show page references after internal links.
#latex_show_pagerefs = False

# If true, show URL addresses after external links.
#latex_show_urls = False

latex_elements = {}

# Additional stuff for the LaTeX preamble.
latex_elements['preamble'] = r"""
% Use a more modern-looking monospace font
\usepackage{inconsolata}

% The enumitem package provides unlimited nesting of lists and enums.
% Sphinx may use this in the future, in which case this can be removed.
% See https://bitbucket.org/birkenfeld/sphinx/issue/777/latex-output-too-deeply-nested
\usepackage{enumitem}
\setlistdepth{15}

% In the parameters section, place a newline after the Parameters
% header.  (This is stolen directly from Numpy's conf.py, since it
% affects Numpy-style docstrings).
\usepackage{expdlist}
\let\latexdescription=\description
\def\description{\latexdescription{}{} \breaklabel}

% Support the superscript Unicode numbers used by the "unicode" units
% formatter
\DeclareUnicodeCharacter{2070}{\ensuremath{^0}}
\DeclareUnicodeCharacter{00B9}{\ensuremath{^1}}
\DeclareUnicodeCharacter{00B2}{\ensuremath{^2}}
\DeclareUnicodeCharacter{00B3}{\ensuremath{^3}}
\DeclareUnicodeCharacter{2074}{\ensuremath{^4}}
\DeclareUnicodeCharacter{2075}{\ensuremath{^5}}
\DeclareUnicodeCharacter{2076}{\ensuremath{^6}}
\DeclareUnicodeCharacter{2077}{\ensuremath{^7}}
\DeclareUnicodeCharacter{2078}{\ensuremath{^8}}
\DeclareUnicodeCharacter{2079}{\ensuremath{^9}}
\DeclareUnicodeCharacter{207B}{\ensuremath{^-}}
\DeclareUnicodeCharacter{00B0}{\ensuremath{^{\circ}}}
\DeclareUnicodeCharacter{2032}{\ensuremath{^{\prime}}}
\DeclareUnicodeCharacter{2033}{\ensuremath{^{\prime\prime}}}

% Make the "warning" and "notes" sections use a sans-serif font to
% make them stand out more.
\renewenvironment{notice}[2]{
  \def\py@noticetype{#1}
  \csname py@noticestart@#1\endcsname
  \textsf{\textbf{#2}}
}{\csname py@noticeend@\py@noticetype\endcsname}
"""

# Documents to append as an appendix to all manuals.
#latex_appendices = []

# If false, no module index is generated.
#latex_domain_indices = True

# The name of an image file (relative to this directory) to place at the top of
# the title page.
#latex_logo = None

# -- Options for the linkcheck builder ----------------------------------------

# A timeout value, in seconds, for the linkcheck builder
linkcheck_timeout = 60
<commit_msg>Only set the favicon if sphinx-astropy-theme is installed<commit_after># -*- coding: utf-8 -*-
# Licensed under a 3-clause BSD style license - see LICENSE.rst
#
# Astropy shared Sphinx settings.  These settings are shared between
# astropy itself and affiliated packages.
#
# Note that not all possible configuration values are present in this file.
#
# All configuration values have a default; values that are commented out
# serve to show the default.

import os
import sys
import warnings

from os import path

import sphinx
from distutils.version import LooseVersion

# -- General configuration ----------------------------------------------------

# The version check in Sphinx itself can only compare the major and
# minor parts of the version number, not the micro.  To do a more
# specific version check, call check_sphinx_version("x.y.z.") from
# your project's conf.py
needs_sphinx = '1.3'

on_rtd = os.environ.get('READTHEDOCS', None) == 'True'

def check_sphinx_version(expected_version):
    sphinx_version = LooseVersion(sphinx.__version__)
    expected_version = LooseVersion(expected_version)
    if sphinx_version < expected_version:
        raise RuntimeError(
            "At least Sphinx version {0} is required to build this "
            "documentation.  Found {1}.".format(
                expected_version, sphinx_version))

# Configuration for intersphinx: refer to the Python standard library.
intersphinx_mapping = {
    'python': ('https://docs.python.org/3/',
               (None, 'http://data.astropy.org/intersphinx/python3.inv')),
    'pythonloc': ('http://docs.python.org/',
                  path.abspath(path.join(path.dirname(__file__),
                                         'local/python3_local_links.inv'))),
    'numpy': ('https://docs.scipy.org/doc/numpy/',
              (None, 'http://data.astropy.org/intersphinx/numpy.inv')),
    'scipy': ('https://docs.scipy.org/doc/scipy/reference/',
              (None, 'http://data.astropy.org/intersphinx/scipy.inv')),
    'matplotlib': ('http://matplotlib.org/',
                   (None, 'http://data.astropy.org/intersphinx/matplotlib.inv')),
    'astropy': ('http://docs.astropy.org/en/stable/', None),
    'h5py': ('http://docs.h5py.org/en/latest/', None)}

# List of patterns, relative to source directory, that match files and
# directories to ignore when looking for source files.
exclude_patterns = ['_build']

# Add any paths that contain templates here, relative to this directory.
# templates_path = ['_templates']

# The suffix of source filenames.
source_suffix = '.rst'

# The encoding of source files.
#source_encoding = 'utf-8-sig'

# The master toctree document.
master_doc = 'index'

# The reST default role (used for this markup: `text`) to use for all
# documents. Set to the "smart" one.
default_role = 'obj'

# The language for content autogenerated by Sphinx. Refer to documentation
# for a list of supported languages.
#language = None

# This is added to the end of RST files - a good place to put substitutions to
# be used globally.
rst_epilog = """
.. _Astropy: http://astropy.org
"""

# A list of warning types to suppress arbitrary warning messages. We mean to
# override directives in astropy_helpers.sphinx.ext.autodoc_enhancements,
# thus need to ignore those warning. This can be removed once the patch gets
# released in upstream Sphinx (https://github.com/sphinx-doc/sphinx/pull/1843).
# Suppress the warnings requires Sphinx v1.4.2
suppress_warnings = ['app.add_directive', ]

# -- Project information ------------------------------------------------------

# There are two options for replacing |today|: either, you set today to some
# non-false value, then it is used:
#today = ''
# Else, today_fmt is used as the format for a strftime call.
#today_fmt = '%B %d, %Y'

# If true, '()' will be appended to :func: etc. cross-reference text.
#add_function_parentheses = True

# If true, the current module name will be prepended to all description
# unit titles (such as .. function::).
#add_module_names = True

# If true, sectionauthor and moduleauthor directives will be shown in the
# output. They are ignored by default.
#show_authors = False

# The name of the Pygments (syntax highlighting) style to use.
#pygments_style = 'sphinx'

# A list of ignored prefixes for module index sorting.
#modindex_common_prefix = []

# -- Settings for extensions and extension options ----------------------------

# Add any Sphinx extension module names here, as strings. They can be
# extensions coming with Sphinx (named 'sphinx.ext.*') or your custom
# ones.
extensions = [
    'sphinx.ext.autodoc',
    'sphinx.ext.intersphinx',
    'sphinx.ext.todo',
    'sphinx.ext.coverage',
    'sphinx.ext.inheritance_diagram',
    'sphinx.ext.viewcode',
    'astropy_helpers.extern.numpydoc',
    'astropy_helpers.extern.automodapi.automodapi',
    'astropy_helpers.extern.automodapi.smart_resolver',
    'astropy_helpers.sphinx.ext.tocdepthfix',
    'astropy_helpers.sphinx.ext.doctest',
    'astropy_helpers.sphinx.ext.changelog_links']

if not on_rtd and LooseVersion(sphinx.__version__) < LooseVersion('1.4'):
    extensions.append('sphinx.ext.pngmath')
else:
    extensions.append('sphinx.ext.mathjax')

try:
    import matplotlib.sphinxext.plot_directive
    extensions += [matplotlib.sphinxext.plot_directive.__name__]
# AttributeError is checked here in case matplotlib is installed but
# Sphinx isn't.  Note that this module is imported by the config file
# generator, even if we're not building the docs.
except (ImportError, AttributeError):
    warnings.warn(
        "matplotlib's plot_directive could not be imported. " +
        "Inline plots will not be included in the output")

# Don't show summaries of the members in each class along with the
# class' docstring
numpydoc_show_class_members = False

autosummary_generate = True

automodapi_toctreedirnm = 'api'

# Class documentation should contain *both* the class docstring and
# the __init__ docstring
autoclass_content = "both"

# Render inheritance diagrams in SVG
graphviz_output_format = "svg"

graphviz_dot_args = [
    '-Nfontsize=10',
    '-Nfontname=Helvetica Neue, Helvetica, Arial, sans-serif',
    '-Efontsize=10',
    '-Efontname=Helvetica Neue, Helvetica, Arial, sans-serif',
    '-Gfontsize=10',
    '-Gfontname=Helvetica Neue, Helvetica, Arial, sans-serif'
]

# -- Options for HTML output -------------------------------------------------

# Add any paths that contain custom themes here, relative to this directory.

# The theme to use for HTML and HTML Help pages.  See the documentation for
# a list of builtin themes.
html_theme = 'bootstrap-astropy'

# Custom sidebar templates, maps document names to template names.
html_sidebars = {
    '**': ['localtoc.html'],
    'search': [],
    'genindex': [],
    'py-modindex': [],
}

# The name of an image file (within the static path) to use as favicon of the
# docs.  This file should be a Windows icon file (.ico) being 16x16 or 32x32
# pixels large.

# We include by default the favicon that is in the bootstrap-astropy theme.
# However, we use a try...except because the user may not be using the
# bootstrap astropy theme and might not have it installed. But if they do
# want to use bootstrap-astropy they will need sphinx-astropy-theme to be
# installed for 'bootstrap-astropy' to work above, in which case the code
# below will succeed.
try:
    import sphinx_astropy_theme
except ImportError:
    pass
else:
    html_theme_path = sphinx_astropy_theme.get_html_theme_path()
    html_favicon = 'astropy_logo.ico'

# If not '', a 'Last updated on:' timestamp is inserted at every page bottom,
# using the given strftime format.
html_last_updated_fmt = '%d %b %Y'

# Theme options are theme-specific and customize the look and feel of a theme
# further.  For a list of options available for each theme, see the
# documentation.
#html_theme_options = {}

# The name for this set of Sphinx documents.  If None, it defaults to
# "<project> v<release> documentation".
#html_title = None

# A shorter title for the navigation bar.  Default is the same as html_title.
#html_short_title = None

# If true, SmartyPants will be used to convert quotes and dashes to
# typographically correct entities.
#html_use_smartypants = True

# Additional templates that should be rendered to pages, maps page names to
# template names.
#html_additional_pages = {}

# If false, no module index is generated.
#html_domain_indices = True

# If false, no index is generated.
#html_use_index = True

# If true, the index is split into individual pages for each letter.
#html_split_index = False

# If true, links to the reST sources are added to the pages.
#html_show_sourcelink = True

# If true, "Created using Sphinx" is shown in the HTML footer. Default is True.
#html_show_sphinx = True

# If true, "(C) Copyright ..." is shown in the HTML footer. Default is True.
#html_show_copyright = True

# If true, an OpenSearch description file will be output, and all pages will
# contain a <link> tag referring to it.  The value of this option must be the
# base URL from which the finished HTML is served.
#html_use_opensearch = ''

# This is the file name suffix for HTML files (e.g. ".xhtml").
#html_file_suffix = None

# -- Options for LaTeX output ------------------------------------------------

# The paper size ('letter' or 'a4').
#latex_paper_size = 'letter'

# The font size ('10pt', '11pt' or '12pt').
#latex_font_size = '10pt'

# For "manual" documents, if this is true, then toplevel headings are parts,
# not chapters.
latex_toplevel_sectioning = 'part'

# If true, show page references after internal links.
#latex_show_pagerefs = False

# If true, show URL addresses after external links.
#latex_show_urls = False

latex_elements = {}

# Additional stuff for the LaTeX preamble.
latex_elements['preamble'] = r"""
% Use a more modern-looking monospace font
\usepackage{inconsolata}

% The enumitem package provides unlimited nesting of lists and enums.
% Sphinx may use this in the future, in which case this can be removed.
% See https://bitbucket.org/birkenfeld/sphinx/issue/777/latex-output-too-deeply-nested
\usepackage{enumitem}
\setlistdepth{15}

% In the parameters section, place a newline after the Parameters
% header.  (This is stolen directly from Numpy's conf.py, since it
% affects Numpy-style docstrings).
\usepackage{expdlist}
\let\latexdescription=\description
\def\description{\latexdescription{}{} \breaklabel}

% Support the superscript Unicode numbers used by the "unicode" units
% formatter
\DeclareUnicodeCharacter{2070}{\ensuremath{^0}}
\DeclareUnicodeCharacter{00B9}{\ensuremath{^1}}
\DeclareUnicodeCharacter{00B2}{\ensuremath{^2}}
\DeclareUnicodeCharacter{00B3}{\ensuremath{^3}}
\DeclareUnicodeCharacter{2074}{\ensuremath{^4}}
\DeclareUnicodeCharacter{2075}{\ensuremath{^5}}
\DeclareUnicodeCharacter{2076}{\ensuremath{^6}}
\DeclareUnicodeCharacter{2077}{\ensuremath{^7}}
\DeclareUnicodeCharacter{2078}{\ensuremath{^8}}
\DeclareUnicodeCharacter{2079}{\ensuremath{^9}}
\DeclareUnicodeCharacter{207B}{\ensuremath{^-}}
\DeclareUnicodeCharacter{00B0}{\ensuremath{^{\circ}}}
\DeclareUnicodeCharacter{2032}{\ensuremath{^{\prime}}}
\DeclareUnicodeCharacter{2033}{\ensuremath{^{\prime\prime}}}

% Make the "warning" and "notes" sections use a sans-serif font to
% make them stand out more.
\renewenvironment{notice}[2]{
  \def\py@noticetype{#1}
  \csname py@noticestart@#1\endcsname
  \textsf{\textbf{#2}}
}{\csname py@noticeend@\py@noticetype\endcsname}
"""

# Documents to append as an appendix to all manuals.
#latex_appendices = []

# If false, no module index is generated.
#latex_domain_indices = True

# The name of an image file (relative to this directory) to place at the top of
# the title page.
#latex_logo = None

# -- Options for the linkcheck builder ----------------------------------------

# A timeout value, in seconds, for the linkcheck builder
linkcheck_timeout = 60
<|endoftext|>


<commit_before>"""
spaceshooter.py
Author: Avery Wallis
Credit: Original Spacewar Code, Daniel, Payton, Ethan

Assignment:
Write and submit a program that implements the spacewar game:
https://github.com/HHS-IntroProgramming/Spacewar
"""
from ggame import App, ImageAsset, RectangleAsset, SoundAsset, Frame
from ggame import LineStyle, Sprite, Sound, Color, TextAsset
import math
import time

SCREEN_WIDTH = 640
SCREEN_HEIGHT = 480

class SpaceShip(Sprite):
    """
    Animated space ship
    """
    asset = ImageAsset("images/four_spaceship_by_albertov_with_thrust.png",
        Frame(227,0,292-227,125), 4, 'vertical')

    def __init__(self, position):
        super().__init__(SpaceShip.asset, position)
        self.visible=True
        self.vx = 0
        self.vy = 0
        self.vr = 0
        self.rotation = 0
        self.thrust = 0
        self.reset = 0
        self.thrustframe = 1
        SpaceGame.listenKeyEvent("keydown", "w", self.thrustOn)
        SpaceGame.listenKeyEvent("keyup", "w", self.thrustOff)
        SpaceGame.listenKeyEvent("keydown", "a" , self.CCOn)
        SpaceGame.listenKeyEvent("keyup", "a", self.CCOff)
        SpaceGame.listenKeyEvent("keydown", "d" , self.CCthing)
        SpaceGame.listenKeyEvent("keyup", "d", self.CCOff)
        SpaceGame.listenKeyEvent("keydown", "r", self.restartOn)
        SpaceGame.listenKeyEvent("keyup", "r", self.restartOff)
        self.fxcenter = self.fycenter = 0.5

    def step(self):
        self.x += self.vx
        self.y += self.vy
        self.rotation += self.vr
        if self.thrust == 1:
            self.setImage(self.thrustframe)
            self.thrustframe += 1
            self.vx += .05*math.cos((self.rotation+math.pi/2))
            self.vy += .05*math.sin((self.rotation-math.pi/2))
            if self.thrustframe == 4:
                self.thrustframe = 1
            else:
                self.setImage(0)
        if self.vr == .1:
            self.rotation += .0001
        if self.vr == -.1:
            self.rotation -= .0001
        if self.vr==0:
            self.rotation=self.rotation

        col= self.collidingWithSprites(Sunthing)
        if col:
            self.explode()

            self.reset = 1

        if self.reset == 1:
            self.visible = True
            self.x = 300
            self.y = 300
            self.rotation = 0
            self.vx = 0
            self.vy = 0

    def thrustOn(self, event):
        self.thrust = 1
    def thrustOff(self, event):
        self.thrust = 0
    def thrustDecel(self, event):
        self.thrust=-1

    def CCOn(self, event):
        self.vr=.1
    def CCOff(self, event):
        self.vr=0
    def CCthing(self, event):
        self.vr=-.1

    def restartOn(self,event):
        self.reset = 1

    def restartOff(self, event):
        self.reset = 0

    def explode(self):
        self.visible = False
        self.vx = 0
        self.vy = 0
        ExplosionSmall(self.position)

class SpaceShip2(Sprite):
    """
    Animated space ship
    """
    asset = ImageAsset("images/four_spaceship_by_albertov_with_thrust.png",
        Frame(0,0,86,125), 4, 'vertical')

    def __init__(self, position):
        super().__init__(SpaceShip2.asset, position)
        self.visible=True
        self.vx = 0
        self.vy = 0
        self.vr = 0
        self.rotation = 0
        self.thrust = 0
        self.reset = 0
        self.thrustframe = 1
        SpaceGame.listenKeyEvent("keydown", "up arrow", self.thrustOn)
        SpaceGame.listenKeyEvent("keyup", "up arrow", self.thrustOff)
        SpaceGame.listenKeyEvent("keydown", "left arrow" , self.CCOn)
        SpaceGame.listenKeyEvent("keyup", "left arrow", self.CCOff)
        SpaceGame.listenKeyEvent("keydown", "right arrow" , self.CCthing)
        SpaceGame.listenKeyEvent("keyup", "right arrow", self.CCOff)
        SpaceGame.listenKeyEvent("keydown", "r", self.restartOn)
        SpaceGame.listenKeyEvent("keyup", "r", self.restartOff)
        self.fxcenter = self.fycenter = 0.5

    def step(self):
        self.x += self.vx
        self.y += self.vy
        self.rotation += self.vr
        if self.thrust == 1:
            self.setImage(self.thrustframe)
            self.thrustframe += 1
            self.vx += .05*math.cos((self.rotation+math.pi/2))
            self.vy += .05*math.sin((self.rotation-math.pi/2))
            if self.thrustframe == 4:
                self.thrustframe = 1
            else:
                self.setImage(0)
        if self.vr == .1:
            self.rotation += .0001
        if self.vr == -.1:
            self.rotation -= .0001
        if self.vr==0:
            self.rotation=self.rotation

        col= self.collidingWithSprites(Sunthing)
        if col:
            self.explode()

        if self.reset == 1:
            self.visible = True
            self.x = 700
            self.y = 500
            self.rotation = 0
            self.vx = 0
            self.vy = 0

    def thrustOn(self, event):
        self.thrust = 1
    def thrustOff(self, event):
        self.thrust = 0
    def thrustDecel(self, event):
        self.thrust=-1

    def CCOn(self, event):
        self.vr=.1
    def CCOff(self, event):
        self.vr=0
    def CCthing(self, event):
        self.vr=-.1

    def restartOn(self,event):
        self.reset = 1

    def restartOff(self, event):
        self.reset = 0

    def explode(self):
        self.visible = False
        self.vx = 0
        self.vy = 0
        ExplosionSmall(self.position)

class ExplosionSmall(Sprite):

    asset = ImageAsset("images/explosion1.png", Frame(0,0,128,128), 10)
    boomasset = SoundAsset("sounds/explosion1.mp3")

    def __init__(self, position):
        super().__init__(ExplosionSmall.asset, position)
        self.image = 0
        self.center = (0.5, 0.5)
        self.boom = Sound(ExplosionSmall.boomasset)
        self.boom.play()

    def step(self):
        self.setImage(self.image//2)  # slow it down
        self.image = self.image + 1
        if self.image == 20:
            self.destroy()

class Sunthing(Sprite):
    asset=ImageAsset("images/sun.png")
    width=80
    length=76
    def __init__(self, position):
        super().__init__(Sunthing.asset, position)
        self.mass = 30*1000
        self.fxcenter = 0.5
        self.fycenter = 0.5
        self.circularCollisionModel()

class SpaceGame(App):
    """
    Tutorial4 space game example.
    """
    def __init__(self, width, height):
        super().__init__(width, height)
        black = Color(0, 1)
        noline = LineStyle(0, black)
        bg_asset = ImageAsset("images/starfield.jpg")
        txt_asset= TextAsset(text="Press R to Reset", width=200, align='center',style='20px Arial', fill=Color(0xff2222,1))
        star0 = Sprite(bg_asset, (0,0))
        star1= Sprite(bg_asset, (512,0))
        star2= Sprite(bg_asset, (0,512))
        star3= Sprite(bg_asset, (512, 512))
        txt = Sprite(txt_asset, (0,0))
        Sunthing((500,360))

        SpaceShip((300,300))

        SpaceShip2((700,500))

    def step(self):

        for ship in self.getSpritesbyClass(SpaceShip):
            ship.step()

        for ship in self.getSpritesbyClass(SpaceShip2):
            ship.step()

        explosions = self.getSpritesbyClass(ExplosionSmall)
        for explosion in explosions:
            explosion.step()

myapp = SpaceGame(SCREEN_WIDTH*1.5, SCREEN_HEIGHT*1.5)
myapp.run()
<commit_msg>Updated from Brython Server: 3/30/2016 1:41:46 PM<commit_after>"""
spaceshooter.py
Author: Avery Wallis
Credit: Original Spacewar Code, Daniel, Payton, Ethan

Assignment:
Write and submit a program that implements the spacewar game:
https://github.com/HHS-IntroProgramming/Spacewar
"""
from ggame import App, ImageAsset, RectangleAsset, SoundAsset, Frame
from ggame import LineStyle, Sprite, Sound, Color, TextAsset
import math
import time

SCREEN_WIDTH = 640
SCREEN_HEIGHT = 480

class SpaceShip(Sprite):
    """
    Animated space ship
    """
    asset = ImageAsset("images/four_spaceship_by_albertov_with_thrust.png",
        Frame(227,0,292-227,125), 4, 'vertical')

    def __init__(self, position):
        super().__init__(SpaceShip.asset, position)
        self.visible=True
        self.vx = 0
        self.vy = 0
        self.vr = 0
        self.rotation = 0
        self.thrust = 0
        self.reset = 0
        self.thrustframe = 1
        SpaceGame.listenKeyEvent("keydown", "w", self.thrustOn)
        SpaceGame.listenKeyEvent("keyup", "w", self.thrustOff)
        SpaceGame.listenKeyEvent("keydown", "a" , self.CCOn)
        SpaceGame.listenKeyEvent("keyup", "a", self.CCOff)
        SpaceGame.listenKeyEvent("keydown", "d" , self.CCthing)
        SpaceGame.listenKeyEvent("keyup", "d", self.CCOff)
        SpaceGame.listenKeyEvent("keydown", "r", self.restartOn)
        SpaceGame.listenKeyEvent("keyup", "r", self.restartOff)
        self.fxcenter = self.fycenter = 0.5

    def step(self):
        self.x += self.vx
        self.y += self.vy
        self.rotation += self.vr
        if self.thrust == 1:
            self.setImage(self.thrustframe)
            self.thrustframe += 1
            self.vx += .05*math.cos((self.rotation+math.pi/2))
            self.vy += .05*math.sin((self.rotation-math.pi/2))
            if self.thrustframe == 4:
                self.thrustframe = 1
            else:
                self.setImage(0)
        if self.vr == .1:
            self.rotation += .0001
        if self.vr == -.1:
            self.rotation -= .0001
        if self.vr==0:
            self.rotation=self.rotation

        col= self.collidingWithSprites(Sunthing)
        if col:
            self.explode()
            self.reset = 1

        if self.reset == 1:
            self.visible = True
            self.x = 300
            self.y = 300
            self.rotation = 0
            self.vx = 0
            self.vy = 0

        col= self.collidingWithSprites(Sunthing)
        if col:
            self.explode()
            self.reset = 1

    def thrustOn(self, event):
        self.thrust = 1
    def thrustOff(self, event):
        self.thrust = 0
    def thrustDecel(self, event):
        self.thrust=-1

    def CCOn(self, event):
        self.vr=.1
    def CCOff(self, event):
        self.vr=0
    def CCthing(self, event):
        self.vr=-.1

    def restartOn(self,event):
        self.reset = 1

    def restartOff(self, event):
        self.reset = 0

    def explode(self):
        self.visible = False
        self.vx = 0
        self.vy = 0
        ExplosionSmall(self.position)

class SpaceShip2(Sprite):
    """
    Animated space ship
    """
    asset = ImageAsset("images/four_spaceship_by_albertov_with_thrust.png",
        Frame(0,0,86,125), 4, 'vertical')

    def __init__(self, position):
        super().__init__(SpaceShip2.asset, position)
        self.visible=True
        self.vx = 0
        self.vy = 0
        self.vr = 0
        self.rotation = 0
        self.thrust = 0
        self.reset = 0
        self.thrustframe = 1
        SpaceGame.listenKeyEvent("keydown", "up arrow", self.thrustOn)
        SpaceGame.listenKeyEvent("keyup", "up arrow", self.thrustOff)
        SpaceGame.listenKeyEvent("keydown", "left arrow" , self.CCOn)
        SpaceGame.listenKeyEvent("keyup", "left arrow", self.CCOff)
        SpaceGame.listenKeyEvent("keydown", "right arrow" , self.CCthing)
        SpaceGame.listenKeyEvent("keyup", "right arrow", self.CCOff)
        SpaceGame.listenKeyEvent("keydown", "r", self.restartOn)
        SpaceGame.listenKeyEvent("keyup", "r", self.restartOff)
        self.fxcenter = self.fycenter = 0.5

    def step(self):
        self.x += self.vx
        self.y += self.vy
        self.rotation += self.vr
        if self.thrust == 1:
            self.setImage(self.thrustframe)
            self.thrustframe += 1
            self.vx += .05*math.cos((self.rotation+math.pi/2))
            self.vy += .05*math.sin((self.rotation-math.pi/2))
            if self.thrustframe == 4:
                self.thrustframe = 1
            else:
                self.setImage(0)
        if self.vr == .1:
            self.rotation += .0001
        if self.vr == -.1:
            self.rotation -= .0001
        if self.vr==0:
            self.rotation=self.rotation

        col= self.collidingWithSprites(Sunthing)
        if col:
            self.explode()

        if self.reset == 1:
            self.visible = True
            self.x = 700
            self.y = 500
            self.rotation = 0
            self.vx = 0
            self.vy = 0

    def thrustOn(self, event):
        self.thrust = 1
    def thrustOff(self, event):
        self.thrust = 0
    def thrustDecel(self, event):
        self.thrust=-1

    def CCOn(self, event):
        self.vr=.1
    def CCOff(self, event):
        self.vr=0
    def CCthing(self, event):
        self.vr=-.1

    def restartOn(self,event):
        self.reset = 1

    def restartOff(self, event):
        self.reset = 0

    def explode(self):
        self.visible = False
        self.vx = 0
        self.vy = 0
        ExplosionSmall(self.position)

class ExplosionSmall(Sprite):

    asset = ImageAsset("images/explosion1.png", Frame(0,0,128,128), 10)
    boomasset = SoundAsset("sounds/explosion1.mp3")

    def __init__(self, position):
        super().__init__(ExplosionSmall.asset, position)
        self.image = 0
        self.center = (0.5, 0.5)
        self.boom = Sound(ExplosionSmall.boomasset)
        self.boom.play()

    def step(self):
        self.setImage(self.image//2)  # slow it down
        self.image = self.image + 1
        if self.image == 20:
            self.destroy()

class Sunthing(Sprite):
    asset=ImageAsset("images/sun.png")
    width=80
    length=76
    def __init__(self, position):
        super().__init__(Sunthing.asset, position)
        self.mass = 30*1000
        self.fxcenter = 0.5
        self.fycenter = 0.5
        self.circularCollisionModel()

class SpaceGame(App):
    """
    Tutorial4 space game example.
    """
    def __init__(self, width, height):
        super().__init__(width, height)
        black = Color(0, 1)
        noline = LineStyle(0, black)
        bg_asset = ImageAsset("images/starfield.jpg")
        txt_asset= TextAsset(text="Press R to Reset", width=200, align='center',style='20px Arial', fill=Color(0xff2222,1))
        star0 = Sprite(bg_asset, (0,0))
        star1= Sprite(bg_asset, (512,0))
        star2= Sprite(bg_asset, (0,512))
        star3= Sprite(bg_asset, (512, 512))
        txt = Sprite(txt_asset, (0,0))
        Sunthing((500,360))

        SpaceShip((300,300))

        SpaceShip2((700,500))

    def step(self):

        for ship in self.getSpritesbyClass(SpaceShip):
            ship.step()

        for ship in self.getSpritesbyClass(SpaceShip2):
            ship.step()

        explosions = self.getSpritesbyClass(ExplosionSmall)
        for explosion in explosions:
            explosion.step()

myapp = SpaceGame(SCREEN_WIDTH*1.5, SCREEN_HEIGHT*1.5)
myapp.run()
<|endoftext|>