<commit_before>from datetime import date, timedelta
from typing import Tuple, Iterator

class Week:
    """A Week object represents a week in epidemiological week calendar."""

    __slots__ = "_year", "_week", "_method"

    def __init__(
        self, year: int, week: int, method: str = "CDC", validate: bool = True
    ) -> None:
        """
        :param year: Epidemiological year
        :type year: int
        :param week: Epidemiological week
        :type week: int
        :param method: Calculation method, which may be ``cdc`` where the week
            starts on Sunday or ``iso`` where the week starts on Monday
            (default is ``cdc``)
        :type method: str
        :param validate: Whether to validate year, week and method or not
            (default is ``True``)
        :type validate: bool
        """

        if validate:
            _check_year(year)
            _check_method(method)
            _check_week(year, week, method)

        self._year = year
        self._week = week
        self._method = method

    def __repr__(self) -> str:
        return "Week({}, {}, {})".format(self._year, self._week, self._method)

    def __str__(self) -> str:
        return self.isoformat()

    def __hash__(self) -> int:
        return hash((self.year, self.week, self.method))

    def __eq__(self, other: "Week") -> bool:
        return self._compare(other) == 0

    def __gt__(self, other: "Week") -> bool:
        return self._compare(other) > 0

    def __ge__(self, other: "Week") -> bool:
        return self._compare(other) >= 0

    def __lt__(self, other: "Week") -> bool:
        return self._compare(other) < 0

    def __le__(self, other: "Week") -> bool:
        return self._compare(other) <= 0

    def _compare(self, other: "Week") -> int:
        """Compare two Week objects after checking if they are comparable."""
        class_name = self.__class__.__name__
        other_name = type(other).__name__
        if not isinstance(other, self.__class__):
            raise TypeError(f"can't compare '{class_name}' to '{other_name}'")
        if self.method != other.method:
            raise TypeError(
                f"can't compare '{class_name}' objects with different "
                f"calculation methods"
            )
        x = self.weektuple()
        y = other.weektuple()
        return 0 if x == y else 1 if x > y else -1

    def __add__(self, other: int) -> "Week":
        if not isinstance(other, int):
            raise TypeError("second operand must be 'int'")
        new_date = self.startdate() + timedelta(weeks=other)
        return Week.fromdate(new_date, self._method)

    def __sub__(self, other: int) -> "Week":
        if not isinstance(other, int):
            raise TypeError("second operand must be 'int'")
        return self + (-other)

    def __contains__(self, other: date) -> bool:
        if not isinstance(other, date):
            raise TypeError("tested operand must be 'date' object")
        return other in self.iterdates()

    @classmethod
    def fromdate(cls, date: date, method: str = "cdc") -> "Week":
        """Construct Week object from a date.

        :param date: Date object
        :type date: datetime.date
        :param method: Calculation method, which may be ``cdc`` where the week
            starts on Sunday or ``iso`` where the week starts on Monday
            (default is ``cdc``)
        :type method: str
        """

        _check_method(method)
        year = date.year
        date_ordinal = date.toordinal()
        year_start_ordinal = _year_start(year, method)
        week = (date_ordinal - year_start_ordinal) // 7
        if week < 0:
            year -= 1
            year_start_ordinal = _year_start(year, method)
            week = (date_ordinal - year_start_ordinal) // 7
        elif week >= 52:
            year_start_ordinal = _year_start(year + 1, method)
            if date_ordinal >= year_start_ordinal:
                year += 1
                week = 0
        week += 1
        return cls(year, week, method, validate=False)

    @classmethod
    def thisweek(cls, method: str = "cdc") -> "Week":
        """Construct Week object from current date.

        :param method: Calculation method, which may be ``cdc`` where the week
            starts on Sunday or ``iso`` where the week starts on Monday
            (default is ``cdc``)
        :type method: str
        """

        return cls.fromdate(date.today(), method)

    @property
    def year(self) -> int:
        """Return year as an integer"""
        return self._year

    @property
    def week(self) -> int:
        """Return week number as an integer"""
        return self._week

    @property
    def method(self) -> str:
        """Return calculation method as a string"""
        return self._method.upper()

    def weektuple(self) -> Tuple[int, int]:
        """Return week as a tuple of (year, week)."""
        return self._year, self._week

    def isoformat(self) -> str:
        """Return a string representing the week in compact form of ISO format
        ‘YYYYWww’.
        """

        return "{:04}W{:02}".format(self._year, self._week)

    def startdate(self) -> date:
        """Return date for first day of week."""
        year_start_ordinal = _year_start(self._year, self._method)
        week_start_ordinal = year_start_ordinal + ((self._week - 1) * 7)
        startdate = date.fromordinal(week_start_ordinal)
        return startdate

    def enddate(self) -> date:
        """Return date for last day of week."""
        enddate = self.startdate() + timedelta(days=6)
        return enddate

    def iterdates(self) -> Iterator[date]:
        """Return an iterator that yield datetime.date objects for all days of
        week."""

        startdate = self.startdate()
        for day in range(0, 7):
            yield startdate + timedelta(days=day)

class Year:
    """A Year object represents a year in epidemiological week calendar."""

    __slots__ = "_year", "_method"

    def __init__(self, year: int, method: str = "cdc") -> None:
        """
        :param year: Epidemiological year
        :type year: int
        :param method: Calculation method, which may be ``cdc`` where the week
            starts on Sunday or ``iso`` where the week starts on Monday
            (default is ``cdc``)
        :type method: str
        """

        _check_year(year)
        _check_method(method)
        self._year = year
        self._method = method

    def __repr__(self) -> str:
        return "Year({}, {})".format(self._year, self._method)

    def __str__(self) -> str:
        return "{:04}".format(self._year)

    def __hash__(self) -> int:
        return hash((self.year, self.method))

    @classmethod
    def thisyear(cls, method: str = "cdc") -> "Year":
        """Construct Year object from current date.

        :param method: Calculation method, which may be ``cdc`` where the week
            starts on Sunday or ``iso`` where the week starts on Monday
            (default is ``cdc``)
        :type method: str
        """

        return cls(date.today().year, method)

    @property
    def year(self) -> int:
        """Return year as an integer"""
        return self._year

    @property
    def method(self) -> str:
        """Return calculation method as a string"""
        return self._method.upper()

    @property
    def totalweeks(self) -> int:
        """Return number of weeks in year as an integer"""
        return _year_total_weeks(self._year, self._method)

    def startdate(self) -> date:
        """Return date for first day of first week of year."""
        year_start_ordinal = _year_start(self._year, self._method)
        return date.fromordinal(year_start_ordinal)

    def enddate(self) -> date:
        """Return date for last day of last week of year."""
        year_end_ordinal = _year_start(self._year + 1, self._method) - 1
        return date.fromordinal(year_end_ordinal)

    def iterweeks(self) -> Iterator[Week]:
        """Return an iterator that yield Week objects for all weeks of year."""
        for week in range(1, self.totalweeks() + 1):
            yield Week(self.year, week, self.method, validate=False)

def _check_year(year: int) -> None:
    """Check type and value of year."""
    if not isinstance(year, int):
        raise TypeError("year must be an integer")
    if not 1 <= year <= 9999:
        raise ValueError("year must be in 1..9999")

def _check_week(year: int, week: int, method: str) -> None:
    """Check type and value of week."""
    if not isinstance(week, int):
        raise TypeError("week must be an integer")
    weeks = _year_total_weeks(year, method)
    if not 1 <= week <= weeks:
        raise ValueError("week must be in 1..{} for year".format(weeks))

def _check_method(method: str) -> None:
    """Check type and value of calculation method."""
    if not isinstance(method, str):
        raise TypeError("method must be a string")
    methods = ["cdc", "iso"]
    if method.lower() not in methods:
        raise ValueError("method must be '{}' or '{}'".format(*methods))

def _method_adjustment(method: str) -> int:
    """Return needed adjustment based on first day of week."""
    first_day = ("Mon", "Sun")
    if method.lower() == "cdc":
        return first_day.index("Sun")
    return first_day.index("Mon")

def _year_start(year: int, method: str) -> int:
    """Return ordinal for first day of first week for year."""
    adjustment = _method_adjustment(method)
    mid_weekday = 3 - adjustment  # Sun is 6 .. Mon is 0
    jan1 = date(year, 1, 1)
    jan1_ordinal = jan1.toordinal()
    jan1_weekday = jan1.weekday()
    week1_start_ordinal = jan1_ordinal - jan1_weekday - adjustment
    if jan1_weekday > mid_weekday:
        week1_start_ordinal += 7
    return week1_start_ordinal

def _year_total_weeks(year: int, method: str) -> int:
    """Return number of weeks in year."""
    year_start_ordinal = _year_start(year, method)
    next_year_start_ordinal = _year_start(year + 1, method)
    weeks = (next_year_start_ordinal - year_start_ordinal) // 7
    return weeks
<commit_msg>change the `method` argument to accept CDC or ISO<commit_after>from datetime import date, timedelta
from typing import Tuple, Iterator

class Week:
    """A Week object represents a week in epidemiological week calendar."""

    __slots__ = "_year", "_week", "_method"

    def __init__(
        self, year: int, week: int, method: str = "CDC", validate: bool = True
    ) -> None:
        """
        :param year: Epidemiological year
        :type year: int
        :param week: Epidemiological week
        :type week: int
        :param method: Calculation method, which may be ``CDC`` where the week
            starts on Sunday or ``ISO`` where the week starts on Monday
            (default is ``CDC``)
        :type method: str
        :param validate: Whether to validate year, week and method or not
            (default is ``True``)
        :type validate: bool
        """

        if validate:
            _check_year(year)
            _check_method(method)
            _check_week(year, week, method)

        self._year = year
        self._week = week
        self._method = method

    def __repr__(self) -> str:
        return "Week({}, {}, {})".format(self._year, self._week, self._method)

    def __str__(self) -> str:
        return self.isoformat()

    def __hash__(self) -> int:
        return hash((self.year, self.week, self.method))

    def __eq__(self, other: "Week") -> bool:
        return self._compare(other) == 0

    def __gt__(self, other: "Week") -> bool:
        return self._compare(other) > 0

    def __ge__(self, other: "Week") -> bool:
        return self._compare(other) >= 0

    def __lt__(self, other: "Week") -> bool:
        return self._compare(other) < 0

    def __le__(self, other: "Week") -> bool:
        return self._compare(other) <= 0

    def _compare(self, other: "Week") -> int:
        """Compare two Week objects after checking if they are comparable."""
        class_name = self.__class__.__name__
        other_name = type(other).__name__
        if not isinstance(other, self.__class__):
            raise TypeError(f"can't compare '{class_name}' to '{other_name}'")
        if self.method != other.method:
            raise TypeError(
                f"can't compare '{class_name}' objects with different "
                f"calculation methods"
            )
        x = self.weektuple()
        y = other.weektuple()
        return 0 if x == y else 1 if x > y else -1

    def __add__(self, other: int) -> "Week":
        if not isinstance(other, int):
            raise TypeError("second operand must be 'int'")
        new_date = self.startdate() + timedelta(weeks=other)
        return Week.fromdate(new_date, self._method)

    def __sub__(self, other: int) -> "Week":
        if not isinstance(other, int):
            raise TypeError("second operand must be 'int'")
        return self + (-other)

    def __contains__(self, other: date) -> bool:
        if not isinstance(other, date):
            raise TypeError("tested operand must be 'date' object")
        return other in self.iterdates()

    @classmethod
    def fromdate(cls, date: date, method: str = "CDC") -> "Week":
        """Construct Week object from a date.

        :param date: Date object
        :type date: datetime.date
        :param method: Calculation method, which may be ``CDC`` where the week
            starts on Sunday or ``ISO`` where the week starts on Monday
            (default is ``CDC``)
        :type method: str
        """

        _check_method(method)
        year = date.year
        date_ordinal = date.toordinal()
        year_start_ordinal = _year_start(year, method)
        week = (date_ordinal - year_start_ordinal) // 7
        if week < 0:
            year -= 1
            year_start_ordinal = _year_start(year, method)
            week = (date_ordinal - year_start_ordinal) // 7
        elif week >= 52:
            year_start_ordinal = _year_start(year + 1, method)
            if date_ordinal >= year_start_ordinal:
                year += 1
                week = 0
        week += 1
        return cls(year, week, method, validate=False)

    @classmethod
    def thisweek(cls, method: str = "CDC") -> "Week":
        """Construct Week object from current date.

        :param method: Calculation method, which may be ``CDC`` where the week
            starts on Sunday or ``ISO`` where the week starts on Monday
            (default is ``CDC``)
        :type method: str
        """

        return cls.fromdate(date.today(), method)

    @property
    def year(self) -> int:
        """Return year as an integer"""
        return self._year

    @property
    def week(self) -> int:
        """Return week number as an integer"""
        return self._week

    @property
    def method(self) -> str:
        """Return calculation method as a string"""
        return self._method.upper()

    def weektuple(self) -> Tuple[int, int]:
        """Return week as a tuple of (year, week)."""
        return self._year, self._week

    def isoformat(self) -> str:
        """Return a string representing the week in compact form of ISO format
        ‘YYYYWww’.
        """

        return "{:04}W{:02}".format(self._year, self._week)

    def startdate(self) -> date:
        """Return date for first day of week."""
        year_start_ordinal = _year_start(self._year, self._method)
        week_start_ordinal = year_start_ordinal + ((self._week - 1) * 7)
        startdate = date.fromordinal(week_start_ordinal)
        return startdate

    def enddate(self) -> date:
        """Return date for last day of week."""
        enddate = self.startdate() + timedelta(days=6)
        return enddate

    def iterdates(self) -> Iterator[date]:
        """Return an iterator that yield datetime.date objects for all days of
        week."""

        startdate = self.startdate()
        for day in range(0, 7):
            yield startdate + timedelta(days=day)

class Year:
    """A Year object represents a year in epidemiological week calendar."""

    __slots__ = "_year", "_method"

    def __init__(self, year: int, method: str = "CDC") -> None:
        """
        :param year: Epidemiological year
        :type year: int
        :param method: Calculation method, which may be ``CDC`` where the week
            starts on Sunday or ``ISO`` where the week starts on Monday
            (default is ``CDC``)
        :type method: str
        """

        _check_year(year)
        _check_method(method)
        self._year = year
        self._method = method

    def __repr__(self) -> str:
        return "Year({}, {})".format(self._year, self._method)

    def __str__(self) -> str:
        return "{:04}".format(self._year)

    def __hash__(self) -> int:
        return hash((self.year, self.method))

    @classmethod
    def thisyear(cls, method: str = "CDC") -> "Year":
        """Construct Year object from current date.

        :param method: Calculation method, which may be ``CDC`` where the week
            starts on Sunday or ``ISO`` where the week starts on Monday
            (default is ``CDC``)
        :type method: str
        """

        return cls(date.today().year, method)

    @property
    def year(self) -> int:
        """Return year as an integer"""
        return self._year

    @property
    def method(self) -> str:
        """Return calculation method as a string"""
        return self._method.upper()

    @property
    def totalweeks(self) -> int:
        """Return number of weeks in year as an integer"""
        return _year_total_weeks(self._year, self._method)

    def startdate(self) -> date:
        """Return date for first day of first week of year."""
        year_start_ordinal = _year_start(self._year, self._method)
        return date.fromordinal(year_start_ordinal)

    def enddate(self) -> date:
        """Return date for last day of last week of year."""
        year_end_ordinal = _year_start(self._year + 1, self._method) - 1
        return date.fromordinal(year_end_ordinal)

    def iterweeks(self) -> Iterator[Week]:
        """Return an iterator that yield Week objects for all weeks of year."""
        for week in range(1, self.totalweeks() + 1):
            yield Week(self.year, week, self.method, validate=False)

def _check_year(year: int) -> None:
    """Check type and value of year."""
    if not isinstance(year, int):
        raise TypeError("year must be an integer")
    if not 1 <= year <= 9999:
        raise ValueError("year must be in 1..9999")

def _check_week(year: int, week: int, method: str) -> None:
    """Check type and value of week."""
    if not isinstance(week, int):
        raise TypeError("week must be an integer")
    weeks = _year_total_weeks(year, method)
    if not 1 <= week <= weeks:
        raise ValueError("week must be in 1..{} for year".format(weeks))

def _check_method(method: str) -> None:
    """Check type and value of calculation method."""
    if not isinstance(method, str):
        raise TypeError("method must be a string")
    methods = ["CDC", "ISO"]
    if method.lower() not in methods:
        raise ValueError("method must be '{}' or '{}'".format(*methods))

def _method_adjustment(method: str) -> int:
    """Return needed adjustment based on first day of week."""
    first_day = ("Mon", "Sun")
    if method.upper() == "CDC":
        return first_day.index("Sun")
    return first_day.index("Mon")

def _year_start(year: int, method: str) -> int:
    """Return ordinal for first day of first week for year."""
    adjustment = _method_adjustment(method)
    mid_weekday = 3 - adjustment  # Sun is 6 .. Mon is 0
    jan1 = date(year, 1, 1)
    jan1_ordinal = jan1.toordinal()
    jan1_weekday = jan1.weekday()
    week1_start_ordinal = jan1_ordinal - jan1_weekday - adjustment
    if jan1_weekday > mid_weekday:
        week1_start_ordinal += 7
    return week1_start_ordinal

def _year_total_weeks(year: int, method: str) -> int:
    """Return number of weeks in year."""
    year_start_ordinal = _year_start(year, method)
    next_year_start_ordinal = _year_start(year + 1, method)
    weeks = (next_year_start_ordinal - year_start_ordinal) // 7
    return weeks
<|endoftext|>


<commit_before>__version__ = 'v3.8.0'

__all__ = [
    '__version__',
]
<commit_msg>bump version to v3.9.0<commit_after>__version__ = 'v3.9.0'

__all__ = [
    '__version__',
]
<|endoftext|>


<commit_before># Copyright 2016 - Nokia Corporation
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.

from cliff import command
from cliff import lister
from cliff import show

from vitrageclient.common import utils

# noinspection PyAbstractClass
class TemplateValidate(show.ShowOne):
    """Validate a template file"""

    def get_parser(self, prog_name):
        parser = super(TemplateValidate, self).get_parser(prog_name)
        parser.add_argument('--path',
                            required=True,
                            help='full path for template file or templates dir'
                            )
        parser.add_argument('--type',
                            choices=['standard', 'definition', 'equivalence'],
                            help='Template type. Valid types:'
                                 '[standard, definition, equivalence]')
        return parser

    @property
    def formatter_default(self):
        return 'json'

    def take_action(self, parsed_args):

        result = utils.get_client(self).template.validate(
            path=parsed_args.path,
            template_type=parsed_args.type)

        return self.dict2columns(result)

class TemplateList(lister.Lister):
    """List all templates"""

    def get_parser(self, prog_name):
        parser = super(TemplateList, self).get_parser(prog_name)
        return parser

    def take_action(self, parsed_args):
        templates = utils.get_client(self).template.list()
        return utils.list2cols_with_rename(
            (
                ('UUID', 'uuid'),
                ('Name', 'name'),
                ('Status', 'status'),
                ('Status details', 'status details'),
                ('Date', 'date'),
                ('Type', 'type'),
            ), templates)

class TemplateShow(show.ShowOne):
    """Show a template"""

    def get_parser(self, prog_name):
        parser = super(TemplateShow, self).get_parser(prog_name)
        parser.add_argument('uuid', help='Template UUID')
        return parser

    @property
    def formatter_default(self):
        return 'json'

    def take_action(self, parsed_args):
        uuid = parsed_args.uuid
        template = utils.get_client(self).template.show(uuid=uuid)
        return self.dict2columns(template)

class TemplateAdd(lister.Lister):
    """Add a template

    support 3 types of templates:
    standard, definition, equivalence
    """

    def get_parser(self, prog_name):
        parser = super(TemplateAdd, self).get_parser(prog_name)
        parser.add_argument('--path',
                            required=True,
                            help='full path for template file or templates dir'
                            )
        parser.add_argument('--type',
                            choices=['standard', 'definition', 'equivalence'],
                            help='Template type. Valid types:'
                                 '[standard, definition, equivalence]')
        return parser

    def take_action(self, parsed_args):
        path = parsed_args.path
        template_type = parsed_args.type
        templates = utils.get_client(self).template.add(
            path=path, template_type=template_type)
        return utils.list2cols_with_rename(
            (
                ('UUID', 'uuid'),
                ('Name', 'name'),
                ('Status', 'status'),
                ('Status details', 'status details'),
                ('Date', 'date'),
                ('Type', 'type'),
            ), templates)

class TemplateDelete(command.Command):
    """Delete a template"""

    def get_parser(self, prog_name):
        parser = super(TemplateDelete, self).get_parser(prog_name)
        parser.add_argument('uuid', help='ID of a template', nargs='+')
        return parser

    @property
    def formatter_default(self):
        return 'json'

    def take_action(self, parsed_args):
        uuid = parsed_args.uuid
        utils.get_client(self).template.delete(uuid=uuid)
<commit_msg>Template delete-validate uuid format<commit_after># Copyright 2016 - Nokia Corporation
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
#      http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import re

import argparse
from cliff import command
from cliff import lister
from cliff import show

from vitrageclient.common import utils

# noinspection PyAbstractClass
class TemplateValidate(show.ShowOne):
    """Validate a template file"""

    def get_parser(self, prog_name):
        parser = super(TemplateValidate, self).get_parser(prog_name)
        parser.add_argument('--path',
                            required=True,
                            help='full path for template file or templates dir'
                            )
        parser.add_argument('--type',
                            choices=['standard', 'definition', 'equivalence'],
                            help='Template type. Valid types:'
                                 '[standard, definition, equivalence]')
        return parser

    @property
    def formatter_default(self):
        return 'json'

    def take_action(self, parsed_args):

        result = utils.get_client(self).template.validate(
            path=parsed_args.path,
            template_type=parsed_args.type)

        return self.dict2columns(result)

class TemplateList(lister.Lister):
    """List all templates"""

    def get_parser(self, prog_name):
        parser = super(TemplateList, self).get_parser(prog_name)
        return parser

    def take_action(self, parsed_args):
        templates = utils.get_client(self).template.list()
        return utils.list2cols_with_rename(
            (
                ('UUID', 'uuid'),
                ('Name', 'name'),
                ('Status', 'status'),
                ('Status details', 'status details'),
                ('Date', 'date'),
                ('Type', 'type'),
            ), templates)

class TemplateShow(show.ShowOne):
    """Show a template"""

    def get_parser(self, prog_name):
        parser = super(TemplateShow, self).get_parser(prog_name)
        parser.add_argument('uuid', help='Template UUID')
        return parser

    @property
    def formatter_default(self):
        return 'json'

    def take_action(self, parsed_args):
        uuid = parsed_args.uuid
        template = utils.get_client(self).template.show(uuid=uuid)
        return self.dict2columns(template)

class TemplateAdd(lister.Lister):
    """Add a template

    support 3 types of templates:
    standard, definition, equivalence
    """

    def get_parser(self, prog_name):
        parser = super(TemplateAdd, self).get_parser(prog_name)
        parser.add_argument('--path',
                            required=True,
                            help='full path for template file or templates dir'
                            )
        parser.add_argument('--type',
                            choices=['standard', 'definition', 'equivalence'],
                            help='Template type. Valid types:'
                                 '[standard, definition, equivalence]')
        return parser

    def take_action(self, parsed_args):
        path = parsed_args.path
        template_type = parsed_args.type
        templates = utils.get_client(self).template.add(
            path=path, template_type=template_type)
        return utils.list2cols_with_rename(
            (
                ('UUID', 'uuid'),
                ('Name', 'name'),
                ('Status', 'status'),
                ('Status details', 'status details'),
                ('Date', 'date'),
                ('Type', 'type'),
            ), templates)

class TemplateDelete(command.Command):
    """Delete a template"""

    def get_parser(self, prog_name):
        parser = super(TemplateDelete, self).get_parser(prog_name)
        parser.add_argument('uuid',
                            help='ID of a template',
                            nargs='+',
                            type=TemplateDelete.vaild_uuid)
        return parser

    @property
    def formatter_default(self):
        return 'json'

    def take_action(self, parsed_args):
        uuid = parsed_args.uuid
        utils.get_client(self).template.delete(uuid=uuid)

    @staticmethod
    def vaild_uuid(uuids):
        rege = '^[a-z0-9]{8}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{4}-[a-z0-9]{12}$'
        if type(uuids) != list:
            uuids = [uuids]
        for uuid in uuids:
            if not re.match(rege, uuid):
                raise argparse.ArgumentTypeError("Not a uuid format")
        return uuids
<|endoftext|>


<commit_before>"""
atheris instruments modules at import-time.
The Instrument() function temporarily installs an import hook (AtherisMetaPathFinder)
in sys.meta_path that employs a custom loader
(AtherisSourceFileLoader, AtherisSourcelessFileLoader).
"""

import sys
from importlib.abc import MetaPathFinder
from importlib.machinery import SourceFileLoader, SourcelessFileLoader, PathFinder
from _frozen_importlib_external import SourceFileLoader, SourcelessFileLoader
from _frozen_importlib import BuiltinImporter, FrozenImporter

from .instrument_bytecode import patch_code

class AtherisMetaPathFinder(MetaPathFinder):
    def __init__(self, packages):
        super().__init__()
        self._target_packages = packages

    def find_spec(self, fullname, path, target=None):
        package_name = fullname.split(".")[0]

        if not self._target_packages or package_name in self._target_packages:
            spec = PathFinder.find_spec(fullname, path, target)

            if spec is None or spec.loader is None:
                return None

            if isinstance(spec.loader, SourceFileLoader):
                spec.loader = AtherisSourceFileLoader(spec.loader.name, spec.loader.path)
            elif isinstance(spec.loader, SourcelessFileLoader):
                spec.loader = AtherisSourcelessFileLoader(spec.loader.name, spec.loader.path)
            else:
                return None

            spec.loader_state = None

            print(f"Instrumenting {fullname}")

            return spec

        else:
            return None

    def invalidate_caches(self):
        return PathFinder.invalidate_caches()

class AtherisSourceFileLoader(SourceFileLoader):
    def get_code(self, fullname):
        code = super().get_code(fullname)

        if code is None:
            return None
        else:
            return patch_code(code, True)

class AtherisSourcelessFileLoader(SourcelessFileLoader):
    def get_code(self, fullname):
        code = super().get_code(fullname)

        if code is None:
            return None
        else:
            return patch_code(code, True)

class HookManager:
    def __init__(self, packages):
        self._target_packages = packages

    def __enter__(self):
        i = 0
        while i < len(sys.meta_path):
            if isinstance(sys.meta_path[i], AtherisMetaPathFinder):
                return self
            i += 1

        i = 0
        while i < len(sys.meta_path) and sys.meta_path[i] in [BuiltinImporter, FrozenImporter]:
            i += 1

        sys.meta_path.insert(i, AtherisMetaPathFinder(self._target_packages))

        return self

    def __exit__(self, *args):
        i = 0
        while i < len(sys.meta_path):
            if isinstance(sys.meta_path[i], AtherisMetaPathFinder):
                sys.meta_path.pop(i)
            else:
                i += 1

def instrument(*modules):
    """
    This function temporarily installs an import hook which instruments
    all imported modules.
    The arguments to this function are names of modules or packages.
    If it is a fully qualified module name, the name of its package will be used.
    """
    target_packages = set()

    for module_name in modules:
        if not isinstance(module_name, str):
            raise RuntimeError("atheris.Instrument() expects names of modules of type <str>")
        elif not module_name:
            raise RuntimeError(f"atheris.Instrument(): Invalid module name: {module_name}")
        elif module_name[0] == ".":
            raise RuntimeError("atheris.Instrument(): Please specify fully qualified module names (absolute not relative)")

        if "." in module_name:
            module_name = module_name.split(".")[0]

        target_packages.add(module_name)

    return HookManager(target_packages)
<commit_msg>Don't allow atheris to instrument itself<commit_after>"""
atheris instruments modules at import-time.
The Instrument() function temporarily installs an import hook (AtherisMetaPathFinder)
in sys.meta_path that employs a custom loader
(AtherisSourceFileLoader, AtherisSourcelessFileLoader).
"""

import sys
from importlib.abc import MetaPathFinder
from importlib.machinery import SourceFileLoader, SourcelessFileLoader, PathFinder
from _frozen_importlib_external import SourceFileLoader, SourcelessFileLoader
from _frozen_importlib import BuiltinImporter, FrozenImporter

from .instrument_bytecode import patch_code

class AtherisMetaPathFinder(MetaPathFinder):
    def __init__(self, packages):
        super().__init__()
        self._target_packages = packages

    def find_spec(self, fullname, path, target=None):
        package_name = fullname.split(".")[0]

        if (not self._target_packages or package_name in self._target_packages) and package_name != "atheris":
            spec = PathFinder.find_spec(fullname, path, target)

            if spec is None or spec.loader is None:
                return None

            if isinstance(spec.loader, SourceFileLoader):
                spec.loader = AtherisSourceFileLoader(spec.loader.name, spec.loader.path)
            elif isinstance(spec.loader, SourcelessFileLoader):
                spec.loader = AtherisSourcelessFileLoader(spec.loader.name, spec.loader.path)
            else:
                return None

            spec.loader_state = None

            print(f"Instrumenting {fullname}")

            return spec

        else:
            return None

    def invalidate_caches(self):
        return PathFinder.invalidate_caches()

class AtherisSourceFileLoader(SourceFileLoader):
    def get_code(self, fullname):
        code = super().get_code(fullname)

        if code is None:
            return None
        else:
            return patch_code(code, True)

class AtherisSourcelessFileLoader(SourcelessFileLoader):
    def get_code(self, fullname):
        code = super().get_code(fullname)

        if code is None:
            return None
        else:
            return patch_code(code, True)

class HookManager:
    def __init__(self, packages):
        self._target_packages = packages

    def __enter__(self):
        i = 0
        while i < len(sys.meta_path):
            if isinstance(sys.meta_path[i], AtherisMetaPathFinder):
                return self
            i += 1

        i = 0
        while i < len(sys.meta_path) and sys.meta_path[i] in [BuiltinImporter, FrozenImporter]:
            i += 1

        sys.meta_path.insert(i, AtherisMetaPathFinder(self._target_packages))

        return self

    def __exit__(self, *args):
        i = 0
        while i < len(sys.meta_path):
            if isinstance(sys.meta_path[i], AtherisMetaPathFinder):
                sys.meta_path.pop(i)
            else:
                i += 1

def instrument(*modules):
    """
    This function temporarily installs an import hook which instruments
    all imported modules.
    The arguments to this function are names of modules or packages.
    If it is a fully qualified module name, the name of its package will be used.
    """
    target_packages = set()

    for module_name in modules:
        if not isinstance(module_name, str):
            raise RuntimeError("atheris.Instrument() expects names of modules of type <str>")
        elif not module_name:
            raise RuntimeError(f"atheris.Instrument(): Invalid module name: {module_name}")
        elif module_name[0] == ".":
            raise RuntimeError("atheris.Instrument(): Please specify fully qualified module names (absolute not relative)")

        if "." in module_name:
            module_name = module_name.split(".")[0]

        target_packages.add(module_name)

    return HookManager(target_packages)
<|endoftext|>


<commit_before>import time

from astropy import units as u
from astropy.coordinates import SkyCoord
from astropy.time import Time

from ..utils.logger import has_logger
from ..utils.indi import PanIndiDevice
from ..utils import error

@has_logger
class Mount(PanIndiDevice):

    def __init__(self,
                 config=dict(),
                 location=None,
                 ):
        """
        Abstract Base class for controlling a mount. This provides the basic functionality
        for the mounts. Sub-classes should override the `initialize` method for mount-specific
        issues as well as any helper methods specific mounts might need. See "NotImplemented Methods"
        section of this module.

        Sets the following properies:

            - self.non_sidereal_available = False
            - self.PEC_available = False
            - self.is_initialized = False

        Args:
            config (dict):              Custom configuration passed to base mount. This is usually
                                        read from the main system config.

            commands (dict):            Commands for the telescope. These are read from a yaml file
                                        that maps the mount-specific commands to common commands.

            location (EarthLocation):   An astropy.coordinates.EarthLocation that contains location information.
        """
        config['driver'] = 'indi_ieq_telescope'
        config['init_commands'] = {}

        super().__init__(config)

        # Set the initial location
        self._location = location

        # Set some initial commands
        self.config['init_commands'].update({
            'TELESCOPE_SLEW_RATE': {'SLEW_MAX': 'On'},
            'GUIDE_RATE': {'GUIDE_RATE': '0.90'},
            'DEVICE_PORT': {'PORT': config['port']},
        })

        # We set some initial mount properties. May come from config
        # self.non_sidereal_available = self.mount_config.setdefault('non_sidereal_available', False)
        # self.PEC_available = self.mount_config.setdefault('PEC_available', False)

        # Initial states
        self.is_initialized = False
        self._is_parked = False
        self._is_slewing = False
        self._is_parking = False
        self._is_tracking = False
        self._is_home = False

        self._status_lookup = dict()

        # Set initial coordinates
        self._target_coordinates = None
        self._current_coordinates = None
        self._park_coordinates = None

        self.initialize()

##################################################################################################
# Properties
##################################################################################################

    @property
    def location(self):
        """ astropy.coordinates.SkyCoord: The location details for the mount.

        When a new location is set,`_setup_location_for_mount` is called, which will update the mount
        with the current location. It is anticipated the mount won't change locations while observing
        so this should only be done upon mount initialization.

        """
        return self._location

    @location.setter
    def location(self, location):
        self._location = location
        # If the location changes we need to update the mount
        self._setup_location_for_mount()

    @property
    def is_parked(self):
        """ bool: Mount parked status. """
        self._is_parked = False
        if self.get_property('TELESCOPE_PARK', '_STATE', result=True) == 'Ok':
            self._is_parked = True

        return self._is_parked

    @property
    def is_home(self):
        """ bool: Mount home status. """
        self._is_home = False
        self.logger.warning("is_home ISN'T WORKING RIGHT. DON'T TRUST RESULT")
        if self.get_property('HOME', '_STATE', result=True) == 'Ok':
            self._is_home = True

        return self._is_home

    @property
    def is_parking(self):
        """ bool: Mount parked status. """
        self._is_parking = False
        if self.get_property('TELESCOPE_PARK', '_STATE', result=True) == 'Busy':
            self._is_parking = True

        return self._is_parking

    @property
    def is_slewing(self):
        """ bool: Mount tracking status.  """
        self._is_slewing = False
        if self.get_property('EQUATORIAL_EOD_COORD', '_STATE', result=True) == 'Busy':
            self._is_slewing = True

        return self._is_slewing

    @property
    def is_tracking(self):
        """ bool: Mount slewing status. """
        self._is_tracking = False
        if self.get_property('TELESCOPE_TRACK_RATE', '_STATE', result=True) == 'Busy':
            self._is_tracking = True

        return self._is_tracking

##################################################################################################
# Methods
##################################################################################################

    def initialize(self):
        """ Initialize the mount """
        self.logger.info('Initializing {} mount'.format(__name__))
        if not self.is_connected:
            self.connect()

        if self.is_connected and not self.is_initialized:
            self.is_initialized = False

            try:
                self._setup_location_for_mount()
            except Exception as e:
                raise error.MountNotFound('Problem initializing mount: {}'.format(e))
            else:
                self.get_all_properties()
                self.is_initialized = True

        self.logger.info('Mount initialized: {}'.format(self.is_initialized))

        return self.is_initialized

    def status(self):
        """ Gets the system status

        """
        pass

    def get_target_coordinates(self):
        """ Gets the RA and Dec for the mount's current target. This does NOT necessarily
        reflect the current position of the mount, see `get_current_coordinates`.

        Returns:
            astropy.coordinates.SkyCoord:
        """

        if self._target_coordinates is None:
            self.logger.info("Target coordinates not set")
        else:
            self.logger.info('Mount target_coordinates: {}'.format(self._target_coordinates))

        return self._target_coordinates

    def set_target_coordinates(self, coords):
        """ Sets the RA and Dec for the mount's current target.

        Args:
            coords (astropy.coordinates.SkyCoord): coordinates specifying target location

        Returns:
            bool:  Boolean indicating success
        """
        assert isinstance(coords, SkyCoord), self.logger.warning("Must be SkyCoord for target")
        # Save the skycoord coordinates
        self._target_coordinates = coords

        return True

    def get_current_coordinates(self):
        """ Reads out the current coordinates from the mount.

        Note:
            See `_mount_coord_to_skycoord` and `_skycoord_to_mount_coord` for translation of
            mount specific coordinates to astropy.coordinates.SkyCoord

        Returns:
            astropy.coordinates.SkyCoord
        """
        self.logger.debug('Getting current mount coordinates')

        dec = self.get_property('EQUATORIAL_EOD_COORD', 'DEC', result=True)
        ra = self.get_property('EQUATORIAL_EOD_COORD', 'RA', result=True)

        # Turn the mount coordinates into a SkyCoord
        self._current_coordinates = SkyCoord(ra=float(ra) * u.hourangle, dec=float(dec) * u.degree)

        return self._current_coordinates

    def set_park_coordinates(self, ha=-170 * u.degree, dec=-10 * u.degree):
        """ Calculates the RA-Dec for the the park position.

        This method returns a location that points the optics of the unit down toward the ground.

        The RA is calculated from subtracting the desired hourangle from the local sidereal time. This requires
        a proper location be set.

        Note:
            Mounts usually don't like to track or slew below the horizon so this will most likely require a
            configuration item be set on the mount itself.

        Args:
            ha (Optional[astropy.units.degree]): Hourangle of desired parking position. Defaults to -165 degrees
            dec (Optional[astropy.units.degree]): Declination of desired parking position. Defaults to -165 degrees

        Returns:
            park_skycoord (astropy.coordinates.SkyCoord): A SkyCoord object representing current parking position.
        """
        self.logger.debug('Setting park position')

        park_time = Time.now()
        park_time.location = self.location

        lst = park_time.sidereal_time('apparent')
        self.logger.debug("LST: {}".format(lst))
        self.logger.debug("HA: {}".format(ha))

        ra = lst - ha
        self.logger.debug("RA: {}".format(ra))
        self.logger.debug("Dec: {}".format(dec))

        self._park_coordinates = SkyCoord(ra, dec)

        self.set_property('TELESCOPE_PARK_POSITION', {
            'PARK_DEC': '{:2.05f}'.format(self._park_coordinates.dec.value),
            'PARK_RA': '{:2.05f}'.format(self._park_coordinates.ra.to(u.hourangle).value)
        })

        self.logger.info("Park Coordinates RA-Dec: {}".format(self._park_coordinates))

##################################################################################################
# Movement methods
##################################################################################################

    def slew_to_coordinates(self, coords, ra_rate=15.0, dec_rate=0.0):
        """ Slews to given coordinates.

        Note:
            Slew rates are not implemented yet.

        Args:
            coords (astropy.SkyCoord): Coordinates to slew to
            ra_rate (Optional[float]): Slew speed - RA tracking rate in arcsecond per second. Defaults to 15.0
            dec_rate (Optional[float]): Slew speed - Dec tracking rate in arcsec per second. Defaults to 0.0

        Returns:
            bool: indicating success
        """
        assert isinstance(coords, tuple), self.logger.warning(
            'slew_to_coordinates expects RA-Dec coords')

        response = 0

        if not self.is_parked:
            # Set the coordinates
            if self.set_target_coordinates(coords):
                response = self.slew_to_target()
            else:
                self.logger.warning("Could not set target_coordinates")

        return response

    def slew_to_target(self):
        """ Slews to the current _target_coordinates

        Returns:
            bool: indicating success
        """
        response = 0

        if not self.is_parked:
            assert self._target_coordinates is not None, self.logger.warning(
                "Target Coordinates not set")

            ra = self._target_coordinates.ra
            dec = self._target_coordinates.dec

            self.logger.debug("Setting RA/Dec: {} {}".format(ra.value, dec.value))

            self.set_property('TIME_UTC', {
                'UTC': Time.now().isot.split('.')[0],
                'OFFSET': '{}'.format(self.config.get('utc_offset'))
            })
            self.set_property('ON_COORD_SET', {'SLEW': 'Off', 'SYNC': 'Off', 'TRACK': 'On'})
            self.set_property(
                'EQUATORIAL_EOD_COORD', {
                    'RA': '{:2.05f}'.format(ra.to(u.hourangle).value),
                    'DEC': '{:2.02f}'.format(dec.value)
                })

        else:
            self.logger.info('Mount is parked')

        return response

    def slew_to_home(self):
        """ Slews the mount to the home position.

        Note:
            Home position and Park position are not the same thing

        Returns:
            bool: indicating success
        """
        self.set_property('HOME', {'GoToHome': 'On'})

    def slew_to_zero(self):
        """ Calls `slew_to_home` in base class. Can be overridden.  """
        self.slew_to_home()

    def park(self):
        """ Slews to the park position and parks the mount.

        Note:
            When mount is parked no movement commands will be accepted.

        Returns:
            bool: indicating success
        """
        self.set_park_coordinates()
        self.set_target_coordinates(self._park_coordinates)
        if self.set_property('TELESCOPE_PARK', {'PARK': 'On'}) == 0:
            self.logger.debug('Slewing to park')
        else:
            self.logger.warning('Problem with slew_to_park')

    def unpark(self):
        """ Unparks the mount. Does not do any movement commands but makes them available again.

        Returns:
            bool: indicating success
        """
        if self.set_property('TELESCOPE_PARK', {'PARK': 'Off', 'UNPARK': 'On'}) == 0:
            self.logger.info('Mount unparked')
        else:
            self.logger.warning('Problem with unpark')

    def home_and_park(self):
        """ Slew to the home position and then slew to park """

        self.slew_to_home()
        while self.is_slewing:
            time.sleep(5)
            self.logger.info("Slewing to home, sleeping for 5 seconds")

        # INDI driver is parking once it gets to home setting.
        self.unpark()
        self.park()

        while self.is_slewing:
            time.sleep(5)
            self.logger.info("Slewing to park, sleeping for 5 seconds")

        if self.is_parked:
            self.logger.info("Mount parked")
        else:
            self.logger.error("MOUNT DID NOT PARK CORRECTLY")

    def move_direction(self, direction='north', seconds=1.0):
        """ Move mount in specified `direction` for given amount of `seconds`

        Args:
            direction(str):     Direction to move mount. One of four cardinal directions, defaults to 'north'.
            seconds(float):     Number of seconds to sleep, defaults to 1.0
        """
        seconds = float(seconds)
        assert direction in ['north', 'south', 'east', 'west']

        if direction in ['north', 'south']:
            move_command = 'TELESCOPE_MOTION_NS'
        else:
            move_command = 'TELESCOPE_MOTION_WE'

        self.logger.debug("Move command: {}".format(move_command))

        try:
            self.set_property(move_command, {'MOTION_{}'.format(direction.upper()): 'On'})
            time.sleep(seconds)
            self.set_property(move_command, {'MOTION_{}'.format(direction.upper()): 'Off'})
        except Exception as e:
            self.logger.warning("Problem moving command!! Make sure mount has stopped moving: {}".format(e))
        finally:
            # Note: We do this twice. That's fine.
            self.logger.debug("Stopping movement")
            self.set_property("TELESCOPE_ABORT_MOTION", {'ABORT': 'On'})

##################################################################################################
# Private Methods
##################################################################################################

    def _setup_location_for_mount(self):
        """
        Sets the mount up to the current location. Mount must be initialized first.

        This uses mount.location (an astropy.coords.EarthLocation) to set most of the params and the rest is
        read from a config file.  Users should not call this directly.

        Includes:
        * Latitude set_long
        * Longitude set_lat
        * Universal Time Offset set_gmt_offset
        * Current Date set_local_date
        * Current Time set_local_time
        """

        # East longitude for mount
        lon = (360 + self.location.longitude.to(u.degree).value) % 360

        self.config['init_commands'].update({
            'TIME_UTC': {
                'UTC': Time.now().isot.split('.')[0],
                'OFFSET': '{}'.format(self.config.get('utc_offset'))
            },
            'GEOGRAPHIC_COORD': {
                'LAT': "{:2.02f}".format(self.location.latitude.to(u.degree).value),
                'LONG': "{:2.02f}".format(lon),
                'ELEV': "{:.0f}".format(self.location.height.value),
            },
        })

    def _sync_coords(self, coords):
        """ Sync mount to given coordinates

        Note:
            Here be dragons.

        Args:
            corods(SkyCoord):   Coordinates to sync to
        """

        self.logger.debug("Sync coordinates to {}".format(coords))
        self.set_property('ON_COORD_SET', {'SLEW': 'Off', 'SYNC': 'On', 'TRACK': 'Off'})

        ra = coords.ra
        dec = coords.dec

        self.set_property(
            'EQUATORIAL_EOD_COORD', {
                'RA': '{:2.05f}'.format(ra.to(u.hourangle).value),
                'DEC': '{:2.02f}'.format(dec.value)
            })
        self.set_property('ON_COORD_SET', {'SLEW': 'Off', 'SYNC': 'Off', 'TRACK': 'On'})
        self.set_target_coordinates(coords)
        self.slew_to_target()

##################################################################################################
# NotImplemented Methods - child class
##################################################################################################
<commit_msg>Assert Target<commit_after>import time

from astropy import units as u
from astropy.coordinates import SkyCoord
from astropy.time import Time

from ..utils.logger import has_logger
from ..utils.indi import PanIndiDevice
from ..utils import error

from ..scheduler import Target

@has_logger
class Mount(PanIndiDevice):

    def __init__(self,
                 config=dict(),
                 location=None,
                 ):
        """
        Abstract Base class for controlling a mount. This provides the basic functionality
        for the mounts. Sub-classes should override the `initialize` method for mount-specific
        issues as well as any helper methods specific mounts might need. See "NotImplemented Methods"
        section of this module.

        Sets the following properies:

            - self.non_sidereal_available = False
            - self.PEC_available = False
            - self.is_initialized = False

        Args:
            config (dict):              Custom configuration passed to base mount. This is usually
                                        read from the main system config.

            commands (dict):            Commands for the telescope. These are read from a yaml file
                                        that maps the mount-specific commands to common commands.

            location (EarthLocation):   An astropy.coordinates.EarthLocation that contains location information.
        """
        config['driver'] = 'indi_ieq_telescope'
        config['init_commands'] = {}

        super().__init__(config)

        # Set the initial location
        self._location = location

        # Set some initial commands
        self.config['init_commands'].update({
            'TELESCOPE_SLEW_RATE': {'SLEW_MAX': 'On'},
            'GUIDE_RATE': {'GUIDE_RATE': '0.90'},
            'DEVICE_PORT': {'PORT': config['port']},
        })

        # We set some initial mount properties. May come from config
        # self.non_sidereal_available = self.mount_config.setdefault('non_sidereal_available', False)
        # self.PEC_available = self.mount_config.setdefault('PEC_available', False)

        # Initial states
        self.is_initialized = False
        self._is_parked = False
        self._is_slewing = False
        self._is_parking = False
        self._is_tracking = False
        self._is_home = False

        self._status_lookup = dict()

        # Set initial coordinates
        self._target_coordinates = None
        self._current_coordinates = None
        self._park_coordinates = None

        self.initialize()

##################################################################################################
# Properties
##################################################################################################

    @property
    def location(self):
        """ astropy.coordinates.SkyCoord: The location details for the mount.

        When a new location is set,`_setup_location_for_mount` is called, which will update the mount
        with the current location. It is anticipated the mount won't change locations while observing
        so this should only be done upon mount initialization.

        """
        return self._location

    @location.setter
    def location(self, location):
        self._location = location
        # If the location changes we need to update the mount
        self._setup_location_for_mount()

    @property
    def is_parked(self):
        """ bool: Mount parked status. """
        self._is_parked = False
        if self.get_property('TELESCOPE_PARK', '_STATE', result=True) == 'Ok':
            self._is_parked = True

        return self._is_parked

    @property
    def is_home(self):
        """ bool: Mount home status. """
        self._is_home = False
        self.logger.warning("is_home ISN'T WORKING RIGHT. DON'T TRUST RESULT")
        if self.get_property('HOME', '_STATE', result=True) == 'Ok':
            self._is_home = True

        return self._is_home

    @property
    def is_parking(self):
        """ bool: Mount parked status. """
        self._is_parking = False
        if self.get_property('TELESCOPE_PARK', '_STATE', result=True) == 'Busy':
            self._is_parking = True

        return self._is_parking

    @property
    def is_slewing(self):
        """ bool: Mount tracking status.  """
        self._is_slewing = False
        if self.get_property('EQUATORIAL_EOD_COORD', '_STATE', result=True) == 'Busy':
            self._is_slewing = True

        return self._is_slewing

    @property
    def is_tracking(self):
        """ bool: Mount slewing status. """
        self._is_tracking = False
        if self.get_property('TELESCOPE_TRACK_RATE', '_STATE', result=True) == 'Busy':
            self._is_tracking = True

        return self._is_tracking

##################################################################################################
# Methods
##################################################################################################

    def initialize(self):
        """ Initialize the mount """
        self.logger.info('Initializing {} mount'.format(__name__))
        if not self.is_connected:
            self.connect()

        if self.is_connected and not self.is_initialized:
            self.is_initialized = False

            try:
                self._setup_location_for_mount()
            except Exception as e:
                raise error.MountNotFound('Problem initializing mount: {}'.format(e))
            else:
                self.get_all_properties()
                self.is_initialized = True

        self.logger.info('Mount initialized: {}'.format(self.is_initialized))

        return self.is_initialized

    def status(self):
        """ Gets the system status

        """
        pass

    def get_target_coordinates(self):
        """ Gets the RA and Dec for the mount's current target. This does NOT necessarily
        reflect the current position of the mount, see `get_current_coordinates`.

        Returns:
            astropy.coordinates.SkyCoord:
        """

        if self._target_coordinates is None:
            self.logger.info("Target coordinates not set")
        else:
            self.logger.info('Mount target_coordinates: {}'.format(self._target_coordinates))

        return self._target_coordinates

    def set_target_coordinates(self, coords):
        """ Sets the RA and Dec for the mount's current target.

        Args:
            coords (astropy.coordinates.SkyCoord): coordinates specifying target location

        Returns:
            bool:  Boolean indicating success
        """
        assert isinstance(coords, Target), self.logger.warning("Must be SkyCoord for target")
        # Save the skycoord coordinates
        self._target_coordinates = coords.coord

        return True

    def get_current_coordinates(self):
        """ Reads out the current coordinates from the mount.

        Note:
            See `_mount_coord_to_skycoord` and `_skycoord_to_mount_coord` for translation of
            mount specific coordinates to astropy.coordinates.SkyCoord

        Returns:
            astropy.coordinates.SkyCoord
        """
        self.logger.debug('Getting current mount coordinates')

        dec = self.get_property('EQUATORIAL_EOD_COORD', 'DEC', result=True)
        ra = self.get_property('EQUATORIAL_EOD_COORD', 'RA', result=True)

        # Turn the mount coordinates into a SkyCoord
        self._current_coordinates = SkyCoord(ra=float(ra) * u.hourangle, dec=float(dec) * u.degree)

        return self._current_coordinates

    def set_park_coordinates(self, ha=-170 * u.degree, dec=-10 * u.degree):
        """ Calculates the RA-Dec for the the park position.

        This method returns a location that points the optics of the unit down toward the ground.

        The RA is calculated from subtracting the desired hourangle from the local sidereal time. This requires
        a proper location be set.

        Note:
            Mounts usually don't like to track or slew below the horizon so this will most likely require a
            configuration item be set on the mount itself.

        Args:
            ha (Optional[astropy.units.degree]): Hourangle of desired parking position. Defaults to -165 degrees
            dec (Optional[astropy.units.degree]): Declination of desired parking position. Defaults to -165 degrees

        Returns:
            park_skycoord (astropy.coordinates.SkyCoord): A SkyCoord object representing current parking position.
        """
        self.logger.debug('Setting park position')

        park_time = Time.now()
        park_time.location = self.location

        lst = park_time.sidereal_time('apparent')
        self.logger.debug("LST: {}".format(lst))
        self.logger.debug("HA: {}".format(ha))

        ra = lst - ha
        self.logger.debug("RA: {}".format(ra))
        self.logger.debug("Dec: {}".format(dec))

        self._park_coordinates = SkyCoord(ra, dec)

        self.set_property('TELESCOPE_PARK_POSITION', {
            'PARK_DEC': '{:2.05f}'.format(self._park_coordinates.dec.value),
            'PARK_RA': '{:2.05f}'.format(self._park_coordinates.ra.to(u.hourangle).value)
        })

        self.logger.info("Park Coordinates RA-Dec: {}".format(self._park_coordinates))

##################################################################################################
# Movement methods
##################################################################################################

    def slew_to_coordinates(self, coords, ra_rate=15.0, dec_rate=0.0):
        """ Slews to given coordinates.

        Note:
            Slew rates are not implemented yet.

        Args:
            coords (astropy.SkyCoord): Coordinates to slew to
            ra_rate (Optional[float]): Slew speed - RA tracking rate in arcsecond per second. Defaults to 15.0
            dec_rate (Optional[float]): Slew speed - Dec tracking rate in arcsec per second. Defaults to 0.0

        Returns:
            bool: indicating success
        """
        assert isinstance(coords, tuple), self.logger.warning(
            'slew_to_coordinates expects RA-Dec coords')

        response = 0

        if not self.is_parked:
            # Set the coordinates
            if self.set_target_coordinates(coords):
                response = self.slew_to_target()
            else:
                self.logger.warning("Could not set target_coordinates")

        return response

    def slew_to_target(self):
        """ Slews to the current _target_coordinates

        Returns:
            bool: indicating success
        """
        response = 0

        if not self.is_parked:
            assert self._target_coordinates is not None, self.logger.warning(
                "Target Coordinates not set")

            ra = self._target_coordinates.ra
            dec = self._target_coordinates.dec

            self.logger.debug("Setting RA/Dec: {} {}".format(ra.value, dec.value))

            self.set_property('TIME_UTC', {
                'UTC': Time.now().isot.split('.')[0],
                'OFFSET': '{}'.format(self.config.get('utc_offset'))
            })
            self.set_property('ON_COORD_SET', {'SLEW': 'Off', 'SYNC': 'Off', 'TRACK': 'On'})
            self.set_property(
                'EQUATORIAL_EOD_COORD', {
                    'RA': '{:2.05f}'.format(ra.to(u.hourangle).value),
                    'DEC': '{:2.02f}'.format(dec.value)
                })

        else:
            self.logger.info('Mount is parked')

        return response

    def slew_to_home(self):
        """ Slews the mount to the home position.

        Note:
            Home position and Park position are not the same thing

        Returns:
            bool: indicating success
        """
        self.set_property('HOME', {'GoToHome': 'On'})

    def slew_to_zero(self):
        """ Calls `slew_to_home` in base class. Can be overridden.  """
        self.slew_to_home()

    def park(self):
        """ Slews to the park position and parks the mount.

        Note:
            When mount is parked no movement commands will be accepted.

        Returns:
            bool: indicating success
        """
        self.set_park_coordinates()
        self.set_target_coordinates(self._park_coordinates)
        if self.set_property('TELESCOPE_PARK', {'PARK': 'On'}) == 0:
            self.logger.debug('Slewing to park')
        else:
            self.logger.warning('Problem with slew_to_park')

    def unpark(self):
        """ Unparks the mount. Does not do any movement commands but makes them available again.

        Returns:
            bool: indicating success
        """
        if self.set_property('TELESCOPE_PARK', {'PARK': 'Off', 'UNPARK': 'On'}) == 0:
            self.logger.info('Mount unparked')
        else:
            self.logger.warning('Problem with unpark')

    def home_and_park(self):
        """ Slew to the home position and then slew to park """

        self.slew_to_home()
        while self.is_slewing:
            time.sleep(5)
            self.logger.info("Slewing to home, sleeping for 5 seconds")

        # INDI driver is parking once it gets to home setting.
        self.unpark()
        self.park()

        while self.is_slewing:
            time.sleep(5)
            self.logger.info("Slewing to park, sleeping for 5 seconds")

        if self.is_parked:
            self.logger.info("Mount parked")
        else:
            self.logger.error("MOUNT DID NOT PARK CORRECTLY")

    def move_direction(self, direction='north', seconds=1.0):
        """ Move mount in specified `direction` for given amount of `seconds`

        Args:
            direction(str):     Direction to move mount. One of four cardinal directions, defaults to 'north'.
            seconds(float):     Number of seconds to sleep, defaults to 1.0
        """
        seconds = float(seconds)
        assert direction in ['north', 'south', 'east', 'west']

        if direction in ['north', 'south']:
            move_command = 'TELESCOPE_MOTION_NS'
        else:
            move_command = 'TELESCOPE_MOTION_WE'

        self.logger.debug("Move command: {}".format(move_command))

        try:
            self.set_property(move_command, {'MOTION_{}'.format(direction.upper()): 'On'})
            time.sleep(seconds)
            self.set_property(move_command, {'MOTION_{}'.format(direction.upper()): 'Off'})
        except Exception as e:
            self.logger.warning("Problem moving command!! Make sure mount has stopped moving: {}".format(e))
        finally:
            # Note: We do this twice. That's fine.
            self.logger.debug("Stopping movement")
            self.set_property("TELESCOPE_ABORT_MOTION", {'ABORT': 'On'})

##################################################################################################
# Private Methods
##################################################################################################

    def _setup_location_for_mount(self):
        """
        Sets the mount up to the current location. Mount must be initialized first.

        This uses mount.location (an astropy.coords.EarthLocation) to set most of the params and the rest is
        read from a config file.  Users should not call this directly.

        Includes:
        * Latitude set_long
        * Longitude set_lat
        * Universal Time Offset set_gmt_offset
        * Current Date set_local_date
        * Current Time set_local_time
        """

        # East longitude for mount
        lon = (360 + self.location.longitude.to(u.degree).value) % 360

        self.config['init_commands'].update({
            'TIME_UTC': {
                'UTC': Time.now().isot.split('.')[0],
                'OFFSET': '{}'.format(self.config.get('utc_offset'))
            },
            'GEOGRAPHIC_COORD': {
                'LAT': "{:2.02f}".format(self.location.latitude.to(u.degree).value),
                'LONG': "{:2.02f}".format(lon),
                'ELEV': "{:.0f}".format(self.location.height.value),
            },
        })

    def _sync_coords(self, coords):
        """ Sync mount to given coordinates

        Note:
            Here be dragons.

        Args:
            corods(SkyCoord):   Coordinates to sync to
        """

        self.logger.debug("Sync coordinates to {}".format(coords))
        self.set_property('ON_COORD_SET', {'SLEW': 'Off', 'SYNC': 'On', 'TRACK': 'Off'})

        ra = coords.ra
        dec = coords.dec

        self.set_property(
            'EQUATORIAL_EOD_COORD', {
                'RA': '{:2.05f}'.format(ra.to(u.hourangle).value),
                'DEC': '{:2.02f}'.format(dec.value)
            })
        self.set_property('ON_COORD_SET', {'SLEW': 'Off', 'SYNC': 'Off', 'TRACK': 'On'})
        self.set_target_coordinates(coords)
        self.slew_to_target()

##################################################################################################
# NotImplemented Methods - child class
##################################################################################################
<|endoftext|>