Пример #1
0
    def delete_log(self, log_type, index=0):
        if index:
            print(
                snakesay(
                    "Deleting old (archive number {index}) {type} log file for {domain} via API"
                    .format(index=index, type=log_type, domain=self.domain)))
        else:
            print(
                snakesay(
                    "Deleting current {type} log file for {domain} via API".
                    format(type=log_type, domain=self.domain)))

        if index == 1:
            url = get_api_endpoint().format(
                username=getpass.getuser(),
                flavor="files") + "path/var/log/{domain}.{type}.log.1/".format(
                    domain=self.domain, type=log_type)
        elif index > 1:
            url = get_api_endpoint().format(
                username=getpass.getuser(), flavor="files"
            ) + "path/var/log/{domain}.{type}.log.{index}.gz/".format(
                domain=self.domain, type=log_type, index=index)
        else:
            url = get_api_endpoint().format(
                username=getpass.getuser(),
                flavor="files") + "path/var/log/{domain}.{type}.log/".format(
                    domain=self.domain, type=log_type)
        response = call_api(url, "delete")
        if not response.ok:
            raise Exception(
                "DELETE log file via API failed, got {response}:{response_text}"
                .format(response=response, response_text=response.text))
Пример #2
0
    def update_schedule(self, params, *, porcelain=False):
        """Updates existing task using `params`.

        *Note*: use this method on `Task.from_id` instance.

        `params` should be one at least one of: command, enabled, interval,
        hour, minute. `interval` takes precedence over `hour` meaning that
        `hour` param will be ignored if `interval` is set to 'hourly'.

        :param params: dictionary of specs to update
        :param porcelain: when True don't use `snakesay` in stdout messages
            (defaults to False)"""

        specs = {
            "command": self.command,
            "enabled": self.enabled,
            "interval": self.interval,
            "hour": self.hour,
            "minute": self.minute,
        }

        specs.update(params)

        if ((specs["interval"] != "daily")
                or (params.get("interval") == "daily" and self.hour)
                or (params.get("hour") == self.hour)):
            specs.pop("hour")

        if params.get("minute") == self.minute:
            specs.pop("minute")

        new_specs = self.schedule.update(self.task_id, specs)

        diff = {
            key: (getattr(self, key), new_specs[key])
            for key in specs if getattr(self, key) != new_specs[key]
        }

        def make_spec_str(key, old_spec, new_spec):
            return f"<{key}> from '{old_spec}' to '{new_spec}'"

        updated = [
            make_spec_str(key, val[0], val[1]) for key, val in diff.items()
        ]

        def make_msg(join_with):
            fill = " " if join_with == ", " else join_with
            intro = f"Task {self.task_id} updated:{fill}"
            return f"{intro}{join_with.join(updated)}"

        if updated:
            if porcelain:
                logger.info(make_msg(join_with="\n"))
            else:
                logger.info(snakesay(make_msg(join_with=", ")))
            self.update_specs(new_specs)
        else:
            logger.warning(snakesay("Nothing to update!"))
Пример #3
0
    def delete(self):
        """Returns `True` when `self.path` successfully deleted on
        PythonAnywhere, `False` otherwise."""

        try:
            self.api.path_delete(self.path)
            logger.info(snakesay(f"{self.path} deleted!"))
            return True
        except Exception as e:
            logger.warning(snakesay(str(e)))
            return False
Пример #4
0
    def unshare(self):
        """Returns `True` when file unshared or has not been shared,
        `False` otherwise."""

        already_shared = self.get_sharing_url(quiet=True)
        if already_shared:
            result = self.api.sharing_delete(self.path)
            if result == 204:
                logger.info(snakesay(f"{self.path} is no longer shared!"))
                return True
            logger.warning(snakesay(f"Could not unshare {self.path}... :("))
            return False
        logger.info(snakesay(f"{self.path} is not being shared, no need to stop sharing..."))
        return True
Пример #5
0
    def share(self):
        """Returns PythonAnywhere sharing link for `self.path` or an
        empty string when share not successful."""

        try:
            code, url = self.api.sharing_post(self.path)
        except Exception as e:
            logger.warning(snakesay(str(e)))
            return ""

        msg = {200: "was already", 201: "successfully"}[code]
        sharing_url = self._make_sharing_url(url)
        logger.info(snakesay(f"{self.path} {msg} shared at {sharing_url}"))
        return sharing_url
Пример #6
0
    def get_sharing_url(self, quiet=False):
        """Returns PythonAnywhere sharing url for `self.path` if file
        is shared, empty string otherwise."""

        url = self.api.sharing_get(self.path)
        if url:
            sharing_url = self._make_sharing_url(url)
            if not quiet:
                logger.info(snakesay(f"{self.path} is shared at {sharing_url}"))
            return sharing_url

        logger.info(snakesay(f"{self.path} has not been shared"))

        return ""
Пример #7
0
def test_two_lines():
    two_lines = 'a' * 81
    message = snakesay(two_lines)
    print(message)
    assert r'/ aaaaaa' in message
    assert 'aaaaaa \\' in message
    assert r'\ aaaaaa' in message
Пример #8
0
    def delete_schedule(self):
        """Deletes existing task.

        *Note*: use this method on `Task.from_id` instance."""

        if self.schedule.delete(self.task_id):
            logger.info(snakesay(f"Task {self.task_id} deleted!"))
Пример #9
0
    def create_schedule(self):
        """Creates new scheduled task.

        *Note* use this method on `Task.to_be_created` instance."""

        params = {
            "command": self.command,
            "enabled": self.enabled,
            "interval": self.interval,
            "minute": self.minute,
        }
        if self.hour:
            params["hour"] = self.hour

        self.update_specs(self.schedule.create(params))

        mode = "will" if self.enabled else "may be enabled to"
        msg = ("Task '{command}' successfully created with id {task_id} "
               "and {mode} be run {interval} at {printable_time}").format(
                   command=self.command,
                   task_id=self.task_id,
                   mode=mode,
                   interval=self.interval,
                   printable_time=self.printable_time,
               )
        logger.info(snakesay(msg))
Пример #10
0
    def sanity_checks(self, nuke):
        print(snakesay("Running API sanity checks"))
        token = os.environ.get("API_TOKEN")
        if not token:
            raise SanityException(
                dedent(
                    """
                Could not find your API token.
                You may need to create it on the Accounts page?
                You will also need to close this console and open a new one once you've done that.
                """
                )
            )

        if nuke:
            return

        url = get_api_endpoint().format(username=getpass.getuser(), flavor="webapps") + self.domain + "/"
        response = call_api(url, "get")
        if response.status_code == 200:
            raise SanityException(
                "You already have a webapp for {domain}.\n\nUse the --nuke option if you want to replace it.".format(
                    domain=self.domain
                )
            )
Пример #11
0
    def contents(self):
        """When `self.path` points to a PythonAnywhere user
        directiory, returns a dictionary of its files and directories,
        where file/directory names are keys and values contain
        information about type and API endpoint.  Otherwise (when
        `self.path` points to a file) contents of the file are
        returned as bytes.

        >>> PAPath('/home/username').contents
        >>> {'.bashrc': {'type': 'file',
            'url': 'https://www.pythonanywhere.com/api/v0/user/username/files/path/home/username/.bashrc'},
            '.local': {'type': 'directory',
            'url': 'https://www.pythonanywhere.com/api/v0/user/username/files/path/home/username/.local'},
            ... }

        >>> PAPath('/home/username/README.txt').contents
        >>> b"some README.txt contents..."
        """

        try:
            content = self.api.path_get(self.path)
            return content if isinstance(content, dict) else content.decode("utf-8")
        except Exception as e:
            logger.warning(snakesay(str(e)))
            return None
Пример #12
0
def list_(
    tablefmt: str = typer.Option(
        "simple", "-f", "--format", help="Table format", callback=tablefmt_callback
    )
):
    """Get list of user's scheduled tasks as a table with columns:
    id, interval, at (hour:minute/minute past), status (enabled/disabled), command.

    Note:
    This script provides an overview of all tasks. Once a task id is
    known and some specific data is required it's more convenient to get
    it using `pa schedule get` command instead of parsing the table.
    """

    logger = get_logger(set_info=True)

    headers = "id", "interval", "at", "status", "command"
    attrs = "task_id", "interval", "printable_time", "enabled", "command"

    def stringify_values(task, attr):
        value = getattr(task, attr)
        if attr == "enabled":
            value = "enabled" if value else "disabled"
        return value

    table = [[stringify_values(task, attr) for attr in attrs] for task in TaskList().tasks]
    msg = tabulate(table, headers, tablefmt=tablefmt) if table else snakesay("No scheduled tasks")
    logger.info(msg)
Пример #13
0
def create(
    domain_name: str = typer.Option(
        "your-username.pythonanywhere.com",
        "-d",
        "--domain",
        help="Domain name, eg www.mydomain.com",
    ),
    python_version: str = typer.Option(
        "3.6",
        "-p",
        "--python-version",
        help="Python version, eg '3.8'",
    ),
    nuke: bool = typer.Option(
        False,
        help=
        "*Irrevocably* delete any existing web app config on this domain. Irrevocably.",
    ),
):
    domain = ensure_domain(domain_name)
    project = Project(domain, python_version)
    project.sanity_checks(nuke=nuke)
    project.virtualenv.create(nuke=nuke)
    project.create_webapp(nuke=nuke)
    project.add_static_file_mappings()
    project.webapp.reload()

    typer.echo(
        snakesay(
            f"All done! Your site is now live at https://{domain}. "
            f"Your web app config screen is here: https://www.pythonanywhere.com/user/{getpass.getuser().lower()}"
            f"/webapps/{domain.replace('.', '_')}"))
Пример #14
0
 def pip_install(self, packages):
     print(
         snakesay(
             'Pip installing {packages} (this may take a couple of minutes)'
             .format(packages=packages)))
     commands = [str(self.path / 'bin/pip'), 'install'] + packages.split()
     subprocess.check_call(commands)
Пример #15
0
def main(domain_name, certificate_file, private_key_file, suppress_reload):
    if not os.path.exists(certificate_file):
        print("Could not find certificate file {certificate_file}".format(
            certificate_file=certificate_file))
        sys.exit(1)
    with open(certificate_file, "r") as f:
        certificate = f.read()

    if not os.path.exists(private_key_file):
        print("Could not find private key file {private_key_file}".format(
            private_key_file=private_key_file))
        sys.exit(1)
    with open(private_key_file, "r") as f:
        private_key = f.read()

    webapp = Webapp(domain_name)
    webapp.set_ssl(certificate, private_key)
    if not suppress_reload:
        webapp.reload()

    ssl_details = webapp.get_ssl_info()
    print(
        snakesay("That's all set up now :-)\n"
                 "Your new certificate will expire on {expiry:%d %B %Y},\n"
                 "so shortly before then you should renew it\n"
                 "and install the new certificate.".format(
                     expiry=ssl_details["not_after"])))
Пример #16
0
    def update_settings_file(self):
        print(snakesay('Updating settings.py'))

        with self.settings_path.open() as f:
            settings = f.read()
        new_settings = settings.replace('ALLOWED_HOSTS = []',
                                        f'ALLOWED_HOSTS = [{self.domain!r}]')

        new_django = version.parse(
            self.virtualenv.get_version("django")) >= version.parse("3.1")

        if re.search(r'^MEDIA_ROOT\s*=', settings, flags=re.MULTILINE) is None:
            new_settings += "\nMEDIA_URL = '/media/'"
        if re.search(r'^STATIC_ROOT\s*=', settings,
                     flags=re.MULTILINE) is None:
            if new_django:
                new_settings += "\nSTATIC_ROOT = Path(BASE_DIR / 'static')"
            else:
                new_settings += "\nSTATIC_ROOT = os.path.join(BASE_DIR, 'static')"
        if re.search(r'^MEDIA_ROOT\s*=', settings, flags=re.MULTILINE) is None:
            if new_django:
                new_settings += "\nMEDIA_ROOT = Path(BASE_DIR / 'media')"
            else:
                new_settings += "\nMEDIA_ROOT = os.path.join(BASE_DIR, 'media')"

        with self.settings_path.open('w') as f:
            f.write(new_settings)
Пример #17
0
 def create(self, python_version, virtualenv_path, project_path, nuke):
     print(snakesay("Creating web app via API"))
     if nuke:
         webapp_url = get_api_endpoint().format(
             username=getpass.getuser(),
             flavor="webapps") + self.domain + "/"
         call_api(webapp_url, "delete")
     post_url = get_api_endpoint().format(username=getpass.getuser(),
                                          flavor="webapps")
     patch_url = post_url + self.domain + "/"
     response = call_api(post_url,
                         "post",
                         data={
                             "domain_name": self.domain,
                             "python_version":
                             PYTHON_VERSIONS[python_version]
                         })
     if not response.ok or response.json().get("status") == "ERROR":
         raise Exception(
             "POST to create webapp via API failed, got {response}:{response_text}"
             .format(response=response, response_text=response.text))
     response = call_api(patch_url,
                         "patch",
                         data={
                             "virtualenv_path": virtualenv_path,
                             "source_directory": project_path
                         })
     if not response.ok:
         raise Exception(
             "PATCH to set virtualenv path and source directory via API failed,"
             "got {response}:{response_text}".format(
                 response=response, response_text=response.text))
Пример #18
0
def main(domain, django_version, python_version, nuke):
    if domain == 'your-username.pythonanywhere.com':
        username = getpass.getuser().lower()
        pa_domain = os.environ.get('PYTHONANYWHERE_DOMAIN',
                                   'pythonanywhere.com')
        domain = '{username}.{pa_domain}'.format(username=username,
                                                 pa_domain=pa_domain)

    project = DjangoProject(domain, python_version)
    project.sanity_checks(nuke=nuke)
    project.create_virtualenv(django_version, nuke=nuke)
    project.run_startproject(nuke=nuke)
    project.find_django_files()
    project.update_settings_file()
    project.run_collectstatic()
    project.create_webapp(nuke=nuke)
    project.add_static_file_mappings()

    project.update_wsgi_file()

    project.webapp.reload()

    print(
        snakesay('All done!  Your site is now live at https://{domain}'.format(
            domain=domain)))
Пример #19
0
 def run_migrate(self):
     print(snakesay('Running migrate database'))
     subprocess.check_call([
         str(Path(self.virtualenv.path) / 'bin/python'),
         str(self.manage_py_path),
         'migrate',
     ])
Пример #20
0
def main(*, task_id, **kwargs):
    logger = get_logger(set_info=True)
    task = get_task_from_id(task_id)

    print_snake = kwargs.pop("snake")
    print_only_values = kwargs.pop("no_spec")

    specs = (
        {spec: getattr(task, spec) for spec in kwargs if kwargs[spec]}
        if any([val for val in kwargs.values()])
        else {spec: getattr(task, spec) for spec in kwargs}
    )

    # get user path instead of server path:
    if specs.get("logfile"):
        specs.update({"logfile": task.logfile.replace("/user/{}/files".format(task.user), "")})

    intro = "Task {} specs: ".format(task_id)
    if print_only_values:
        specs = "\n".join([str(val) for val in specs.values()])
        logger.info(specs)
    elif print_snake:
        specs = ["<{}>: {}".format(spec, value) for spec, value in specs.items()]
        specs.sort()
        logger.info(snakesay(intro + ", ".join(specs)))
    else:
        table = [[spec, val] for spec, val in specs.items()]
        table.sort(key=lambda x: x[0])
        logger.info(intro)
        logger.info(tabulate(table, tablefmt="simple"))
def main(*, task_id, **kwargs):
    logger = get_logger()

    if kwargs.pop("hourly"):
        kwargs["interval"] = "hourly"
    if kwargs.pop("daily"):
        kwargs["hour"] = kwargs["hour"] if kwargs["hour"] else datetime.now(
        ).hour
        kwargs["interval"] = "daily"

    def parse_opts(*opts):
        candidates = [key for key in opts if kwargs.pop(key, None)]
        return candidates[0] if candidates else None

    if not parse_opts("quiet"):
        logger.setLevel(logging.INFO)

    porcelain = parse_opts("porcelain")
    enable_opt = parse_opts("toggle_enabled", "disable", "enable")

    task = get_task_from_id(task_id)

    params = {key: val for key, val in kwargs.items() if val}
    if enable_opt:
        enabled = {
            "toggle_enabled": not task.enabled,
            "disable": False,
            "enable": True
        }[enable_opt]
        params.update({"enabled": enabled})

    try:
        task.update_schedule(params, porcelain=porcelain)
    except Exception as e:
        logger.warning(snakesay(str(e)))
Пример #22
0
 def pip_install(self, packages):
     print(
         snakesay(
             f"Pip installing {packages} (this may take a couple of minutes)"
         ))
     commands = [str(self.path / "bin/pip"), "install"] + packages.split()
     subprocess.check_call(commands)
Пример #23
0
 def create(self, python_version, virtualenv_path, project_path, nuke):
     print(snakesay('Creating web app via API'))
     if nuke:
         webapp_url = get_api_endpoint().format(username=getpass.getuser()) + self.domain + '/'
         call_api(webapp_url, 'delete')
     post_url = get_api_endpoint().format(username=getpass.getuser())
     patch_url = post_url + self.domain + '/'
     response = call_api(post_url, 'post', data={
         'domain_name': self.domain, 'python_version': PYTHON_VERSIONS[python_version]},
     )
     if not response.ok or response.json().get('status') == 'ERROR':
         raise Exception(
             'POST to create webapp via API failed, got {response}:{response_text}'.format(
                 response=response,
                 response_text=response.text,
             )
         )
     response = call_api(
         patch_url, 'patch',
         data={'virtualenv_path': virtualenv_path, 'source_directory': project_path}
     )
     if not response.ok:
         raise Exception(
             "PATCH to set virtualenv path and source directory via API failed,"
             "got {response}:{response_text}".format(
                 response=response,
                 response_text=response.text,
             )
         )
Пример #24
0
 def start_bash(self):
     print(
         snakesay(
             'Starting Bash shell with activated virtualenv in project directory.  Press Ctrl+D to exit.'
         ))
     unique_id = str(uuid.uuid4())
     launch_bash_in_virtualenv(self.virtualenv.path, unique_id,
                               self.project_path)
Пример #25
0
 def update_wsgi_file(self):
     print(
         snakesay('Updating wsgi file at {wsgi_file_path}'.format(
             wsgi_file_path=self.wsgi_file_path)))
     template = (Path(__file__).parent /
                 'wsgi_file_template.py').open().read()
     with self.wsgi_file_path.open('w') as f:
         f.write(template.format(project=self))
Пример #26
0
 def run_collectstatic(self):
     print(snakesay('Running collectstatic'))
     subprocess.check_call([
         str(Path(self.virtualenv.path) / 'bin/python'),
         str(self.manage_py_path),
         'collectstatic',
         '--noinput',
     ])
Пример #27
0
    def add_default_static_files_mappings(self, project_path):
        print(snakesay("Adding static files mappings for /static/ and /media/"))

        url = (
            get_api_endpoint().format(username=getpass.getuser(), flavor="webapps") + self.domain + "/static_files/"
        )
        call_api(url, "post", json=dict(url="/static/", path=str(Path(project_path) / "static")))
        call_api(url, "post", json=dict(url="/media/", path=str(Path(project_path) / "media")))
Пример #28
0
def test_two_lines():
    two_lines = 'a' * 81
    message = snakesay(two_lines)
    print(message)
    assert '/ ' in message
    assert '| aaaaa' in message
    assert 'aaaaa |' in message
    assert '\\ ' in message
Пример #29
0
 def create(self, nuke):
     print(
         snakesay(f"Creating virtualenv with Python{self.python_version}"))
     command = f"mkvirtualenv --python=python{self.python_version} {self.domain}"
     if nuke:
         command = f"rmvirtualenv {self.domain} && {command}"
     subprocess.check_call(
         ["bash", "-c", f"source virtualenvwrapper.sh && {command}"])
     return self
Пример #30
0
 def run_startproject(self, nuke):
     print(snakesay('Starting Django project'))
     if nuke and self.project_path.exists():
         shutil.rmtree(self.project_path)
     self.project_path.mkdir()
     subprocess.check_call([
         Path(self.virtualenv.path) / 'bin/django-admin.py', 'startproject',
         'mysite', self.project_path
     ])