Beispiel #1
0
def _assemble_location(wd_location: wd.Location, properties: bool, histories: bool, weather_data: wd.WeatherData) -> Location:
    dto = Location.from_domain(wd_location)
    if properties:
        dto.properties = LocationProperties.from_domain(weather_data.history_properties(wd_location))
    if histories:
        histories = weather_data.history_date_ranges(wd_location)
        if histories:
            dto.histories = [DateRange.from_domain(dr) for dr in weather_data.history_date_ranges(wd_location)]
    return dto
Beispiel #2
0
 def execute(self, options: argparse.Namespace, weather_data: WeatherData):
     location = None
     if options.al_cmd == "df":
         try:
             location = self.location_from_fields(options, weather_data)
         except ValueError as err:
             log.error(str(err), exc_info=True)
     else:
         location = self.location_from_city(options, weather_data)
     if location:
         weather_data.add_location(location)
Beispiel #3
0
async def _get_location_hourly_history(
        name: str = Path(None, description="Location full name or alias."),
        starting: date = Query(None, description='Starting date (YYYY-MM-DD).', alias="from"),
        ending: date = Query(None, description='Ending date (YYYY-MM-DD).', alias="thru"),
        weather_data: wd.WeatherData = Security(_get_weather_data, scopes=[Permissions.ReadWeatherData])
) -> WeatherHistory:
    location = _get_weather_data_location(name, weather_data)
    date_range = wd.DateRange(starting, ending)
    history_dates = weather_data.history_dates(location, date_range.low, date_range.high)
    histories = [HourlyHistory.from_domain(hourly_history)
                 for hourly_history in weather_data.get_history(location, history_dates, hourly_history=True)]
    return WeatherHistory(location=Location.from_domain(location),
                          date_range=DateRange.from_domain(date_range),
                          hourly_histories=histories)
Beispiel #4
0
    def execute(self, args=None):
        options = self._parser.parse_args(args)
        weather_data = WeatherData(
            options.data) if options.data else WeatherData()

        if options.debug:
            log.getLogger().setLevel(log.DEBUG)
        log.debug("%s", options)

        if options.cmd:
            try:
                self._dispatcher[options.cmd](options, weather_data)
            except Exception as error:
                log.exception("Error executing '%s': %s...",
                              options.cmd,
                              str(error),
                              exc_info=error)
        else:
            self._parser.print_usage()
Beispiel #5
0
    def execute(self, options: argparse.Namespace, weather_data: WeatherData):
        locations = sorted(get_locations(weather_data, *options.locations), key=lambda l: l.name)
        histories: Dict[str, List[DateRange]] = {
            location.name: weather_data.history_date_ranges(location) for location in locations
        }

        if not (options.csv or options.json):
            field_widths = {
                "lw": max_width(locations, get=lambda l: l.name),
                "dw": len("YYYY-MM-DD to YYYY-MM-DD")
            }
            print("{:-^{lw}} {:-^{dw}}".format("Location", "History Dates", **field_widths))
            details = "{:<{lw}} {:<{dw}}"
            for location in locations:
                weather_ranges = histories.get(location.name)
                if not weather_ranges:
                    print(details.format(location.name, "None.", **field_widths))
                else:
                    for i, (start, end) in enumerate(weather_ranges):
                        print(details.format("" if i else location.name,
                                             "{} to {}".format(start, end) if start and end else str(start),
                                             **field_widths))
            return

        DictKeys = namedtuple('DictKeys', ["name", "dates", "start", "end"])

        def to_dict(_dict_keys: DictKeys) -> List[Dict]:
            return [
                {
                    _dict_keys.name: _location.name,
                    _dict_keys.dates: [
                        {
                            _dict_keys.start: str(_start),
                            _dict_keys.end: str(_end)
                        }
                        for _start, _end in histories.get(_location.name)
                    ]
                }
                for _location in locations
            ]

        if options.json:
            print(json.dumps({"history": to_dict(DictKeys("location", "dates", "start", "end"))}, indent=2))

        elif options.csv:
            fields = DictKeys("location", "dates", "start_date", "end_date")
            with CsvDictWriter([fields.name, fields.start, fields.end]).stdout() as dict_write:
                for history_dict in to_dict(fields):
                    location_dict = {fields.name: history_dict[fields.name]}
                    dates = history_dict[fields.dates]
                    if not dates:
                        dict_write(location_dict)
                    else:
                        for history_date in dates:
                            dict_write({**location_dict, **history_date})
Beispiel #6
0
    def __init__(self, master, **kwargs):
        if os.name == 'nt' and get_bool_setting("gui", "windows_native_theme"):
            style = Style()
            print("theme names: {}, current theme: {}".format(
                style.theme_names(), style.theme_use()))
            theme = "winnative"
            Style().theme_use(theme)
            print("theme set to: {}".format(style.theme_use()))

        master.title("Weather Data")
        master.columnconfigure(0, weight=1)
        master.rowconfigure(0, weight=1)

        frame = Frame(master, **kwargs)
        frame.grid(sticky=NSEW)
        frame.columnconfigure(0, weight=1)
        frame.rowconfigure(0, weight=1)

        notebook = NotebookWidget(frame)
        notebook.grid(row=0, column=0, sticky=NSEW)

        status = StatusWidget(frame)
        status.grid(row=1, column=0, sticky=(S, E, W))

        locations_view = LocationsWidget(frame)
        notebook.add_tab(locations_view, "Locations")

        domain = WeatherDomain(master, notebook, status, locations_view)
        controller = WeatherController(domain)
        # domain.root.configure(menu=controller.locations_menu)

        notebook.bind_notebook_tab_changed(controller.tab_change_event)
        locations_view.on_event("<Button-3>",
                                lambda e: controller.right_click_action())
        locations_view.on_event("<<TreeviewSelect>>",
                                controller.location_view_select)

        data_path = Path(WeatherData.WEATHER_DATA_DIR)
        # todo: change so WeatherData is always instantiated
        domain.load_locations(
            WeatherData(data_path)
            if data_path.exists() and data_path.is_dir() else None)

        window_width = frame.winfo_reqwidth()
        window_height = frame.winfo_reqheight()
        x_position = int(master.winfo_screenwidth() / 3 - window_width / 2)
        y_position = int(master.winfo_screenheight() / 3 - window_height / 2)
        frame.master.geometry("+{}+{}".format(x_position, y_position))
        frame.update()
        frame.master.wm_minsize(frame.winfo_width(), frame.winfo_height())

        master.protocol("WM_DELETE_WINDOW", master.quit)

        self._frame = frame
Beispiel #7
0
async def _get_location_history_dates(
        name: str = Path(..., description="Location full name or alias."),
        starting: date = Query(None, description='Starting date (YYYY-MM-DD).', alias="from"),
        ending: date = Query(None, description='Ending date (YYYY-MM-DD).', alias="thru"),
        weather_data: wd.WeatherData = Security(_get_weather_data, scopes=[Permissions.ReadWeatherData])
) -> List[date]:
    location = _get_weather_data_location(name, weather_data)
    if not ending:
        ending = starting
    elif ending < starting:
        raise HTTPException(status.HTTP_400_BAD_REQUEST, "From date after thru date.")
    return [hd for hd in weather_data.history_dates(location, starting, ending)]
Beispiel #8
0
    def execute(self, options: argparse.Namespace, weather_data: WeatherData):

        if not options.terse:
            log.setLevel(INFO)
        location = weather_data.get_location(options.location)
        if not location:
            log.error("Location '{}' was not found...".format(options.location))
            return

        one_day = timedelta(days=1)
        starting = to_date(options.starting)
        if not starting:
            return
        if not options.ending:
            ending = starting
        else:
            ending = to_date(options.ending)
            if not ending:
                return

        history_dates = weather_data.history_dates(location)
        existing_history = set(history_dates) if history_dates else set()
        history_dates = []
        history_dates_exist = []
        while starting <= ending:
            history_dates_exist.append(starting) if starting in existing_history else history_dates.append(starting)
            starting += one_day

        if history_dates_exist:
            with StringIO() as text:
                columns, _ = get_terminal_size(fallback=(80, 24))
                dates_per_line = int(columns / len(", YYYY-MM-DD"))
                for i, history_date in enumerate(history_dates_exist):
                    if i % dates_per_line:
                        text.write(", {}".format(history_date))
                    else:
                        text.write("\n{}".format(history_date))
                log.warning("History dates exists for:%s", text.getvalue())

        weather_data.add_history(location, history_dates, lambda hd: log.info(f"getting {hd}"))
Beispiel #9
0
 def change_weather_data_dir(self):
     if not self._weather_data:
         weather_data_dir = Path('.').absolute()
     elif self._weather_data.data_path().parent:
         weather_data_dir = self._weather_data.data_path().parent.absolute()
     else:
         weather_data_dir = self._weather_data.data_path().absolute()
     data_dir = filedialog.askdirectory(master=self._root,
                                        initialdir=weather_data_dir,
                                        title="Open Weather Data",
                                        mustexist=True)
     if data_dir:
         data_path = Path(data_dir)
         if not self._weather_data or data_path.absolute(
         ) != self._weather_data.data_path().absolute():
             if data_path.parent.absolute() == Path(".").absolute():
                 data_path = data_path.name
             if self._weather_data:
                 self._weather_data.close()
             log.debug("Creating weather data for {}".format(data_path))
             self._weather_data = WeatherData(data_path)
             self.load_locations()
Beispiel #10
0
class WeatherDomain:
    def __init__(self, root: Tk, notebook: NotebookWidget,
                 status: StatusWidget, locations_view: LocationsWidget):
        self._weather_data: Optional[WeatherData] = None
        self._view_ids: Dict[str, Union[Location, DateRange]] = {}
        self._history_id_to_location_id: Dict[str, str] = {}
        self._root = root
        self._locations_view = locations_view
        self._notebook = notebook
        self._status = status

    @property
    def root(self) -> Tk:
        return self._root

    @property
    def weather_data(self) -> WeatherData:
        return self._weather_data

    def exit(self, prompt_to_exit=False):
        if prompt_to_exit:
            if messagebox.askyesno("Exit", "Exit Weather Data?") == NO:
                return
        self._root.quit()

    def get_id_value(self, view_id: str) -> Union[Location, DateRange]:
        return self._view_ids.get(view_id)

    def get_history_location(self, history_id: str) -> Location:
        location_id = self._history_id_to_location_id.get(history_id)
        if location_id:
            return self._view_ids.get(location_id)

    def set_menu(self, menu):
        self._root.configure(menu=menu)

    def get_selections(self) -> List[LocationViewSelection]:
        return [(selection, self.get_id_value(selection))
                for selection in self._locations_view.get_selection()]

    def get_cursor_position(self) -> Coord:
        root = self._root
        return Coord(root.winfo_pointerx() - root.winfo_vrootx(),
                     root.winfo_pointery() - root.winfo_vrooty())

    def change_weather_data_dir(self):
        if not self._weather_data:
            weather_data_dir = Path('.').absolute()
        elif self._weather_data.data_path().parent:
            weather_data_dir = self._weather_data.data_path().parent.absolute()
        else:
            weather_data_dir = self._weather_data.data_path().absolute()
        data_dir = filedialog.askdirectory(master=self._root,
                                           initialdir=weather_data_dir,
                                           title="Open Weather Data",
                                           mustexist=True)
        if data_dir:
            data_path = Path(data_dir)
            if not self._weather_data or data_path.absolute(
            ) != self._weather_data.data_path().absolute():
                if data_path.parent.absolute() == Path(".").absolute():
                    data_path = data_path.name
                if self._weather_data:
                    self._weather_data.close()
                log.debug("Creating weather data for {}".format(data_path))
                self._weather_data = WeatherData(data_path)
                self.load_locations()

    def load_locations(self, weather_data: Optional[WeatherData] = None):
        self._locations_view.clear()
        self._history_id_to_location_id.clear()
        self._view_ids.clear()

        if weather_data:
            self._weather_data = weather_data
        elif self._weather_data:
            weather_data = self._weather_data
        else:
            # this may happen if the app is started and there is no weather_data dir
            return

        self._status.set_weather_data_dir(weather_data)
        if weather_data:
            locations = sorted([loc for loc in weather_data.locations()],
                               key=lambda l: l.name)
            log.debug("locations: {}".format([loc.name for loc in locations]))
            # noinspection PyTupleAssignmentBalance
            name, alias, longitude, latitude, tz, history = LocationsWidget.columns
            name_chars = name.min_chars
            alias_chars = alias.min_chars
            tz_chars = tz.min_chars
            for location in locations:
                name_chars = max(len(location.name), name_chars)
                alias_chars = max(len(location.alias), alias_chars)
                tz_chars = max(len(location.tz), tz_chars)
            self._locations_view.set_column_chars(name.id, name_chars)
            self._locations_view.set_column_chars(alias.id, alias_chars)
            self._locations_view.set_column_chars(tz.id, tz_chars)
            self._locations_view.set_column_chars(
                history.id, len("0000-00-00 to 0000-00-00"))

            for location in locations:
                location_id = self._locations_view.add_location(location)
                self._view_ids[location_id] = location
                for history_range in weather_data.history_date_ranges(
                        location):
                    history_id = self._locations_view.add_history(
                        location_id, history_range)
                    self._view_ids[history_id] = history_range
                    self._history_id_to_location_id[history_id] = location_id

    def add_location(self):
        if not self._weather_data:
            return
        city_db = CityDB()
        dialog = NewLocationDialog(
            self._root, city_db,
            [loc for loc in self._weather_data.locations()])
        if not dialog.canceled:
            self._weather_data.add_location(dialog.location)
            self.load_locations()

    def add_location_history(self):
        selections = self.get_selections()
        selection_count = len(selections)
        if 0 == selection_count:
            messagebox.showwarning(title="Add History",
                                   message="Select a location.")
            return

        if 1 == selection_count:
            # item_value = self.get_id_value(items[0])
            iid, item_value = selections[0]
            location = item_value if isinstance(
                item_value, Location) else self.get_history_location(iid)
        else:
            # make sure all the histories are from the same location
            location = self.get_history_location(selections[0][0])
            for item in selections[1:]:
                if self.get_history_location(item[0]) != location:
                    messagebox.showwarning(title="Add History",
                                           message="Select a single location.")
                    return

        add_history = AddWeatherHistoryDialog(self._root,
                                              title="Add History - {}".format(
                                                  location.name))
        if add_history.canceled:
            return

        date_range = add_history.date_range
        history_dates = self._weather_data.history_dates(location)
        existing_history = set(history_dates) if history_dates else set()
        add_dates = []
        for add_date in date_range.get_dates():
            if add_date not in existing_history:
                add_dates.append(add_date)

        if 0 == len(add_dates):
            messagebox.showwarning(
                title="Add History",
                message="History already exists\nfor the selected dates...")
        else:
            progress = self._status.create_progress_widget(
                "add history", len(add_dates))
            try:
                self._weather_data.add_history(location, add_dates,
                                               lambda a: progress.step())
            finally:
                progress.end()
            self.load_locations()

    def delete_location(self):
        selections = self.get_selections()
        selection_count = len(selections)
        if 0 == selection_count:
            messagebox.showwarning(title="Delete",
                                   message="Select a location.")
            return
        iid, item_value = selections[0]
        if not isinstance(item_value, Location):
            return
        if 1 < selection_count:
            messagebox.showwarning(
                title="Delete",
                message="Only 1 Location can be deleted at a time.")
            return
        if messagebox.askyesno(title="Delete",
                               message="Delete {}?".format(
                                   item_value.name)) == YES:
            self._weather_data.remove_location(item_value)
            self.load_locations()

    def weather_data_properties(self):
        if not self._weather_data:
            return
        history_properties = self._weather_data.all_history_properties()
        if not history_properties:
            messagebox.showinfo(title="Weather Data properties",
                                message="Weather Data is empty...")
        else:
            make_content = WeatherDataPropertiesDialog.Property.make
            contents: List[WeatherDataPropertiesDialog.Property] = []
            for location_properties in history_properties:
                location, properties = location_properties
                if not properties:
                    content = make_content(location.name)
                else:
                    content = make_content(
                        location.name,
                        size=properties.size,
                        entries=properties.entries,
                        entries_size=properties.entries_size,
                        compressed_size=properties.compressed_size)
                contents.append(content)
            WeatherDataPropertiesDialog(self._root, contents)

    DataConverters = Dict[DailyWeatherContent, Callable[[Any], Any]]
    DailyWeatherContentGenerator = Generator[Dict[DailyWeatherContent, Any],
                                             None, None]

    def report_history(self):
        selections = self.get_selections()
        if 1 != len(selections):
            raise RuntimeError(
                "Yikes... The length of selections for report history is {}!".
                format(len(selections)))

        # get the history selection metrics
        iid, date_range = selections[0]
        history_selection = WeatherHistoryDialog(
            self._root,
            date_range,
            selected_content=[
                DailyWeatherContent.TIME, DailyWeatherContent.TEMPERATURE_HIGH,
                DailyWeatherContent.TEMPERATURE_HIGH_TIME,
                DailyWeatherContent.TEMPERATURE_LOW,
                DailyWeatherContent.TEMPERATURE_LOW_TIME
            ])
        if history_selection.canceled:
            return

        location = self.get_history_location(iid)
        date_range = history_selection.get_date_range()
        selected_content = history_selection.get_content_selection()

        # add the daily weather widget to the notebook
        widget = DailyWeatherWidget(self._notebook,
                                    history_selection.get_content_selection())
        start, end = date_range
        if end.year > start.year:
            tab_dates = "{} to {}".format(start.strftime("%b %Y"),
                                          end.strftime("%b %Y"))
        elif end.month > start.month:
            tab_dates = "{} to {}".format(start.strftime("%b"),
                                          end.strftime("%b %Y"))
        else:
            tab_dates = "{} to {}".format(start.strftime("%b %d"),
                                          end.strftime("%d %Y"))
        self._notebook.add_tab(widget,
                               tab_name="{}\n{}".format(
                                   location.name, tab_dates))

        tz = pytz.timezone(location.tz)
        data_converters = {
            DailyWeatherContent.TIME:
            lambda v: DataConverter.to_date(v, tz, fmt="%b-%d-%Y"),
            DailyWeatherContent.TEMPERATURE_HIGH:
            lambda v: DataConverter.to_fahrenheit(v),
            DailyWeatherContent.TEMPERATURE_HIGH_TIME:
            lambda v: DataConverter.to_time(v, tz, fmt="%H:%M"),
            DailyWeatherContent.TEMPERATURE_LOW:
            lambda v: DataConverter.to_fahrenheit(v),
            DailyWeatherContent.TEMPERATURE_LOW_TIME:
            lambda v: DataConverter.to_time(v, tz, fmt="%H:%M"),
            DailyWeatherContent.TEMPERATURE_MAX:
            lambda v: DataConverter.to_fahrenheit(v),
            DailyWeatherContent.TEMPERATURE_MAX_TIME:
            lambda v: DataConverter.to_time(v, tz, fmt="%H:%M"),
            DailyWeatherContent.TEMPERATURE_MIN:
            lambda v: DataConverter.to_fahrenheit(v),
            DailyWeatherContent.TEMPERATURE_MIN_TIME:
            lambda v: DataConverter.to_time(v, tz, fmt="%H:%M"),
            DailyWeatherContent.WIND_SPEED:
            lambda v: DataConverter.to_str(v),
            DailyWeatherContent.WIND_GUST:
            lambda v: DataConverter.to_str(v),
            DailyWeatherContent.WIND_GUST_TIME:
            lambda v: DataConverter.to_time(v, tz, fmt="%H:%M"),
            DailyWeatherContent.WIND_BEARING:
            lambda v: DataConverter.wind_bearing(v),
            DailyWeatherContent.CLOUD_COVER:
            lambda v: DataConverter.to_str(v)
        }

        report_rows = []
        content_order = widget.content_order
        for history in self._get_location_histories(location, date_range,
                                                    selected_content,
                                                    data_converters):
            row = []
            for content in content_order:
                row.append(history[content])
            report_rows.append(row)

        widget.load(report_rows)

    def graph_history(self):

        selections = self.get_selections()
        selections_len = len(selections)
        if 0 == selections_len:
            return

        if 5 < selections_len:
            messagebox.showinfo(
                title="Graph",
                message="Current graph history only supports 5 selections.")
            return

        # to get here the controller should only have allowed a multiple selection of date ranges
        location_histories: List[LocationDateRange] = []
        for iid, selection in selections:
            assert isinstance(
                selection,
                DateRange), "Yikes... Selection for graph is not a DateRange!"
            location_histories.append(
                (self.get_history_location(iid), selection))

        history_date_mappings = self._get_history_date_mapping(
            location_histories)
        if not history_date_mappings:
            return

        date_selection = WeatherHistoryGraphDatesDialog(
            self._root, history_date_mappings)
        if date_selection.canceled:
            return

        def start_end_months(_month_span: List[bool]) -> Tuple[int, int]:
            _start_month = _month_span.index(True)
            try:
                _end_month = _month_span.index(False, _start_month) - 1
            except ValueError:
                _end_month = len(_month_span) - 1
            return _start_month, _end_month

        graph_start_month, graph_end_month = start_end_months(
            date_selection.month_selections)

        def graph_date(_year: int, _month: int, _day: Callable[[int, int],
                                                               int]) -> date:
            _year = (_year + 1) if 12 < _month else _year
            _month = (_month - 12) if 12 < _month else _month
            return date(_year, _month, _day(_year, _month))

        graph_date_range = DateRange(
            graph_date(MINYEAR, graph_start_month, lambda y, m: 1),
            graph_date(MINYEAR, graph_end_month,
                       lambda y, m: monthrange(y, m)[1]))

        locations = [hdm[0] for hdm in history_date_mappings]
        multiple_locations = locations.count(locations[0]) != len(locations)
        if multiple_locations:
            title = "Daily Temperatures for Multiple Locations"
            tab_label = "History Graph for\nMultiple Locations"
        else:
            title = "Daily Temperatures for {}".format(
                location_histories[0][0].name)
            tab_label = "{}\nHistory Graph".format(
                location_histories[0][0].name)
        graph = DailyTemperatureGraph(self._notebook,
                                      graph_date_range,
                                      title=title)
        self._notebook.add_tab(graph, tab_label)

        # sanitize the colors jic...
        colors = get_setting("gui", "graph_colors")
        accepted_color_names = {c.name.casefold() for c in get_colors()}
        accepted_hex_color_names = {c.to_hex() for c in get_colors()}
        for idx, color in enumerate(colors):
            if color.casefold(
            ) not in accepted_color_names and color not in accepted_hex_color_names:
                log.warning(
                    "'%s' is not an accepted color. Review UI graph colors in File->Settings.",
                    color)
                colors[idx] = "black"

        selected_content = [
            DailyWeatherContent.TIME, DailyWeatherContent.TEMPERATURE_LOW,
            DailyWeatherContent.TEMPERATURE_HIGH
        ]
        for location, location_date_range, location_month_slots in history_date_mappings:
            tz = pytz.timezone(location.tz)
            data_converters = {
                DailyWeatherContent.TIME:
                lambda v: DataConverter.to_binary_date(v, tz),
                DailyWeatherContent.TEMPERATURE_HIGH:
                lambda v: DataConverter.to_binary_float(v),
                DailyWeatherContent.TEMPERATURE_LOW:
                lambda v: DataConverter.to_binary_float(v)
            }

            location_start_month, location_end_month = start_end_months(
                location_month_slots)
            location_year = location_date_range.low.year
            if 12 < location_start_month and not location_date_range.spans_years(
            ):
                # adjust the location date range due to history being moved for date intersection
                location_year -= 1
            if location_start_month < graph_start_month:
                location_start_month = graph_start_month
            if location_end_month > graph_end_month:
                location_end_month = graph_end_month
            date_range = DateRange(
                graph_date(location_year, location_start_month,
                           lambda y, m: 1),
                graph_date(location_year, location_end_month,
                           lambda y, m: monthrange(y, m)[1]))

            daily_temperatures: List[DailyTemperature] = []
            for history in self._get_location_histories(
                    location, date_range, selected_content, data_converters):
                daily_temperatures.append(
                    DailyTemperature(
                        history.get(DailyWeatherContent.TIME),
                        history.get(DailyWeatherContent.TEMPERATURE_LOW),
                        history.get(DailyWeatherContent.TEMPERATURE_HIGH)))
            sorted(daily_temperatures, key=lambda dt: dt.ts)

            color = colors[graph.plot_count % len(colors)]
            starting = daily_temperatures[0].ts
            ending = daily_temperatures[-1].ts
            if starting.year < ending.year:
                label = "{} to {}".format(starting.year, ending.year)
            else:
                label = str(starting.year)
            if multiple_locations:
                label = "{}\n{}".format(location.name, label)

            graph.plot(daily_temperatures, color=color, label=label)

    def settings(self):
        dialog = SettingsDialog(self._notebook)
        if not dialog.canceled:
            # assume the logging levels have changed
            set_module_logging_levels()

    def _get_location_histories(
            self, location: Location, date_range: DateRange,
            selected_content: List[DailyWeatherContent],
            data_converters: DataConverters) -> DailyWeatherContentGenerator:
        low_t, high_t = date_range
        history_dates = self._weather_data.history_dates(
            location, low_t, high_t)
        data_converter = GenericDataConverter[DailyWeatherContent](
            data_converters)
        progress = self._status.create_progress_widget(
            "graph creation", maximum=len(history_dates))
        try:
            for history in self._weather_data.get_history(
                    location, history_dates):
                progress.step()
                yield data_converter.convert_contents(history,
                                                      selected_content)
        finally:
            progress.end()

    @staticmethod
    def _get_history_date_mapping(
        location_date_ranges: List[LocationDateRange]
    ) -> List[HistoryDateMapping]:

        location_date_ranges_len = len(location_date_ranges)
        assert 0 < location_date_ranges_len, "Yikes... Location date ranges are emtpy!"

        # move the date range to a year neutral format
        neutral_date_ranges = [
            ldr[1].as_neutral_date_range() for ldr in location_date_ranges
        ]

        # create the map location date months will go into to see where they might intersect
        month_slots: List[List[LocationDateRange]] = [[] for _ in range(25)]
        for idx, location_date_range in enumerate(location_date_ranges):
            date_range = neutral_date_ranges[idx]
            high_month = date_range.high.month
            if location_date_range[1].spans_years():
                high_month += 12
            for month in range(date_range.low.month, high_month + 1):
                month_slots[month].append(location_date_range)
        assert 0 == len(month_slots[0]), "Yikes... Month slot 0 has a value!"

        # make another pass across the months checking if 1-12 month dates should be moved to the next year
        for idx, month_slot in enumerate(month_slots):
            # only look at the first year of month slots
            if 12 < idx:
                break

            # check slots that have location date ranges and are not full
            if month_slot and len(month_slot) != location_date_ranges_len:
                month_slot_next_year = month_slots[idx + 12]
                if month_slot_next_year:
                    month_slot_next_year += [
                        ldr for ldr in month_slot if not ldr[1].spans_years()
                    ]
                    month_slot[:] = [
                        ldr for ldr in month_slot if ldr[1].spans_years()
                    ]

        # now search through the slots looking for intersecting month groups
        intersecting_months: List[Tuple[int, int]] = []
        start = end = 0
        for idx, month_slot in enumerate(month_slots):
            if month_slot:
                if start:
                    end = idx
                else:
                    start = end = idx
            elif start:
                intersecting_months.append((start, end))
                start = end = 0
        if start:
            intersecting_months.append((start, end))

        history_date_mappings: List[HistoryDateMapping] = []
        if 1 < len(intersecting_months):
            # todo: show the date groupings
            messagebox.showerror(
                title="Graph",
                message="History mapping selection is too complex...")
        else:
            # walk back through the intersecting months and get the location month slots for each location date range
            def eq(lhs: LocationDateRange, rhs: LocationDateRange) -> bool:
                return lhs[0].name == rhs[0].name and lhs[1].low == rhs[
                    1].low and lhs[1].high == rhs[1].high

            start_month, end_month = intersecting_months[0]
            for location_date_range in location_date_ranges:
                history_month_slots = [False for _ in range(len(month_slots))]
                for slot in range(start_month, end_month + 1):
                    for slot_ldr in month_slots[slot]:
                        if eq(location_date_range, slot_ldr):
                            history_month_slots[slot] = True
                            break
                history_date_mappings.append(
                    (location_date_range[0], location_date_range[1],
                     history_month_slots))
        return history_date_mappings
Beispiel #11
0
def get_location(weather_data: WeatherData, name: str, alias: str = None) -> Optional[Location]:
    location = weather_data.get_location(name)
    if not location and alias:
        location = weather_data.get_location(alias)
    return location
Beispiel #12
0
def _get_weather_data_location(name: str, weather_data: wd.WeatherData) -> wd.Location:
    location = weather_data.get_location(name)
    if not location:
        raise HTTPException(status.HTTP_404_NOT_FOUND, f'"{name}" was not found.')
    return location
Beispiel #13
0
def get_locations(weather_data: WeatherData, *location_names: str) -> List[Location]:
    if location_names:
        return [location for location in (weather_data.get_location(name) for name in location_names) if location]
    return [location for location in weather_data.locations()]
Beispiel #14
0
class HistoryPerformance:
    def __init__(self):
        self.weather_data = WeatherData()

    def run(self, single_threaded, skip_load):
        load_control_blocks: List[Tuple[Location, WeatherHistory]] = []
        for location in self.weather_data.locations():
            # noinspection PyProtectedMember
            weather_history = self.weather_data._get_weather_history(location)
            load_control_blocks.append((location, weather_history))

        for p in range(2):
            print("{} pass {}threaded {}loading json".format(
                "2nd" if p else "1st",
                "single " if single_threaded else "multi-",
                "skip " if skip_load else ""))
            read_stats: List[Tuple[Location, Tuple]] = []
            overall_stop_watch = StopWatch()
            if single_threaded:
                for location, weather_history in load_control_blocks:
                    read_stats.append(
                        (location, self.load_history(weather_history,
                                                     skip_load)))
            else:
                with ThreadPoolExecutor(max_workers=20) as executor:
                    future_to_location = {
                        executor.submit(self.load_history, wh, skip_load): loc
                        for loc, wh in load_control_blocks
                    }
                    for future in as_completed(future_to_location):
                        location = future_to_location[future]
                        if location:
                            try:
                                read_stats.append((location, future.result()))
                            except Exception as err:
                                print(f'"{location.name}" error: {str(err)}')
            overall_stop_watch.stop()
            print(f'History read took: {str(overall_stop_watch)}')
            for location, stats in read_stats:
                if stats:
                    stop_watch, histories = stats
                    loads_per_second = histories / stop_watch.elapsed(
                    ) if stop_watch.elapsed() else float(histories)
                    print('"{}":(elapsed={},histories={},loads/sec={:,.1f})'.
                          format(location.name, stop_watch, histories,
                                 loads_per_second))

    @staticmethod
    def load_history(weather_history: WeatherHistory, skip_load: bool):
        if not weather_history:
            return

        history_count = 0
        stop_watch = StopWatch()
        with weather_history.reader() as history_reader:
            for history_date in weather_history.dates():
                history_count += 1

                data = history_reader(
                    weather_history.make_pathname(history_date))
                if skip_load:
                    continue
                # full_history = json.loads(data)
                full_history = orjson.loads(data)
                # full_history = ujson.loads(data)

                hourly_history = full_history.get("hourly")
                assert hourly_history, "Did not find 'hourly' in weather history"

                daily_history = full_history.get("daily")
                assert daily_history, "Did not find 'daily' in weather history"
        stop_watch.stop()
        return stop_watch, history_count
Beispiel #15
0
    def execute(self, options: argparse.Namespace, weather_data: WeatherData):

        history_properties = weather_data.all_history_properties()
        if not history_properties:
            return

        location_field_name = "location"
        property_field_names = WeatherHistoryProperties._fields

        def to_dict(_location: Location, _properties: WeatherHistoryProperties) -> dict:
            _dict = {location_field_name: _location.name}
            if _properties:
                for _field in property_field_names:
                    _dict[_field] = getattr(_properties, _field)
            return _dict

        if options.csv:
            with CsvDictWriter([location_field_name] + list(property_field_names)).stdout() as write:
                for location, properties in history_properties:
                    write(to_dict(location, properties))
            return

        if options.json:
            location_list = [to_dict(location, properties) for location, properties in history_properties]
            print(json.dumps({"locations": location_list}, indent=2))
            return

        def fmt(value: int, to_kib=True) -> str:
            if not value:
                return ""
            return "{: >,d}".format(value) if not to_kib or value < 1024 else "{: >,d} kiB".format(round(value / 1024))

        Field = namedtuple('Field', ['label', 'key', 'header', 'detail', 'width'])
        fields = (
            Field("Location", "lnw", "{:^{lnw}}", "{:<{lnw}}",
                  lambda: max_width(history_properties, 10, lambda hp: hp[0].name)),
            Field("Overall Size", "osw", "{:^{osw}}", "{:>{osw}}", lambda: len(fields[1].label)),
            Field("History Count", "hcw", "{:^{hcw}}", "{:>{hcw}}", lambda: len(fields[2].label)),
            Field("Raw History Size", "hsw", "{:^{hsw}}", "{:>{hsw}}", lambda: len(fields[3].label)),
            Field("Compressed Size", "csw", "{:^{csw}}", "{:>{csw}}", lambda: len(fields[4].label)),
        )
        header = " ".join([field.header for field in fields])
        field_sizes = {field.key: field.width() for field in fields}

        print(header.format(*(field.label for field in fields), **field_sizes))
        details = " ".join([field.detail for field in fields])
        print(details.format(*("-" * field.width() for field in fields), **field_sizes))
        for location, properties in history_properties:
            print(details.format(
                location.name,
                fmt(properties.size),
                fmt(properties.entries, to_kib=False),
                fmt(properties.entries_size),
                fmt(properties.compressed_size),
                **field_sizes)
            )
        print(details.format(*("=" * field.width() for field in fields), **field_sizes))
        print(details.format(
            "Totals",
            fmt(sum(p.size for _, p in history_properties)),
            fmt(sum(p.entries for _, p in history_properties), to_kib=False),
            fmt(sum(p.entries_size for _, p in history_properties)),
            fmt(sum(p.compressed_size for _, p in history_properties)),
            **field_sizes
        ))
Beispiel #16
0
 def __init__(self):
     self.weather_data = WeatherData()
Beispiel #17
0
    def execute(self, options: argparse.Namespace, weather_data: WeatherData):

        location = weather_data.get_location(options.location)
        if not location:
            log.warning("Location '{}' was not found...".format(options.location))
            return
        if not weather_data.history_exists(location):
            log.warning("'{}' does not have any weather history.".format(location.name))
            return

        output_type = "json" if options.json else "csv" if options.csv else "txt"
        output_path = Path(options.file) if options.file else None
        if output_path:
            if output_path.suffix != output_type:
                output_path = output_path.with_suffix("." + output_type)
            if output_path.exists() and not output_path.is_file():
                raise ValueError("The {} destination, '{}', is not a file.".format(output_type, output_path))

        starting = to_date(options.starting)
        if not starting:
            return
        if not options.ending:
            ending = starting
        else:
            ending = to_date(options.ending)
            if not ending:
                return

        history_dates = weather_data.history_dates(location, starting, ending)

        # suck up all the histories
        histories = weather_data.get_history(location, history_dates, hourly_history=options.hourly)

        def to_json(_histories: Iterable) -> str:
            return json.dumps({
                "location": location.name,
                "type": "hourly_history" if options.hourly else "daily_history",
                "history": [_h for _h in _histories]
            }, indent=2)

        if options.raw:
            if output_path:
                output_path.write_text(to_json(histories))
            else:
                print(to_json(histories))
            return

        report_builder = self.ReportBuilder(location, options)

        if options.csv:
            csv_writer = CsvDictWriter(report_builder.data_converter.field_names())
            with csv_writer.file_writer(output_path) if output_path else csv_writer.stdout() as csv_write:
                for data in report_builder.data_generator(histories):
                    csv_write(data)
            return

        if options.json:
            history = to_json(report_builder.data_generator(histories))
            if output_path:
                output_path.write_text(history)
            else:
                print(history)
            return

        Field = namedtuple('Field', ['key', 'header', 'detail'])
        if report_builder.hourly:
            fields: Dict[Enum, Field] = {
                HourlyWeatherContent.TIME: Field("tw", "{:^{tw}}", "{:<{tw}}"),
                HourlyWeatherContent.TEMPERATURE: Field("tm", "{:^{tm}}", "{:<{tm}}"),
                HourlyWeatherContent.APPARENT_TEMPERATURE: Field("at", "{:^{at}}", "{:<{at}}"),
                HourlyWeatherContent.WIND_SPEED: Field("ws", "{:^{ws}}", "{:<{ws}}"),
                HourlyWeatherContent.WIND_GUST: Field("wg", "{:^{wg}}", "{:<{wg}}"),
                HourlyWeatherContent.WIND_BEARING: Field("wb", "{:^{wb}}", "{:<{wb}}"),
                HourlyWeatherContent.CLOUD_COVER: Field("cc", "{:^{cc}}", "{:<{cc}}"),
                HourlyWeatherContent.UV_INDEX: Field("uv", "{:^{uv}}", "{:<{uv}}"),
                HourlyWeatherContent.HUMIDITY: Field("hw", "{:^{hw}}", "{:<{hw}}"),
                HourlyWeatherContent.DEW_POINT: Field("dp", "{:^{dp}}", "{:<{dp}}"),
                HourlyWeatherContent.SUMMARY: Field("sw", "{:^{sw}}", "{:<{sw}})"),
            }
        else:
            fields: Dict[Enum, Field] = {
                DailyWeatherContent.TIME: Field("tw", "{:^{tw}}", "{:<{tw}}"),
                DailyWeatherContent.TEMPERATURE_HIGH: Field("th", "{:^{th}}", "{:^{th}}"),
                DailyWeatherContent.TEMPERATURE_HIGH_TIME: Field("tht", "{:^{tht}}", "{:^{tht}}"),
                DailyWeatherContent.TEMPERATURE_LOW: Field("tl", "{:^{tl}}", "{:^{tl}}"),
                DailyWeatherContent.TEMPERATURE_LOW_TIME: Field("tlt", "{:^{tlt}}", "{:^{tlt}}"),
                DailyWeatherContent.TEMPERATURE_MAX: Field("mx", "{:^{mx}}", "{:^{mx}}"),
                DailyWeatherContent.TEMPERATURE_MAX_TIME: Field("mxt", "{:^{mxt}}", "{:^{mxt}}"),
                DailyWeatherContent.TEMPERATURE_MIN: Field("mn", "{:^{mn}}", "{:^{mn}}"),
                DailyWeatherContent.TEMPERATURE_MIN_TIME: Field("mnt", "{:^{mnt}}", "{:^{mnt}}"),
                DailyWeatherContent.WIND_SPEED: Field("ws", "{:^{ws}}", "{:>{ws}}"),
                DailyWeatherContent.WIND_GUST: Field("wg", "{:^{wg}}", "{:>{wg}}"),
                DailyWeatherContent.WIND_GUST_TIME: Field("wgt", "{:^{wgt}}", "{:^{wgt}}"),
                DailyWeatherContent.WIND_BEARING: Field("wb", "{:^{wb}}", "{:^{wb}}"),
                DailyWeatherContent.CLOUD_COVER: Field("cc", "{:^{cc}}", "{:<{cc}}"),
                DailyWeatherContent.UV_INDEX: Field("uv", "{:^{uv}}", "{:>{uv}}"),
                DailyWeatherContent.UV_INDEX_TIME: Field("uvt", "{:^{uvt}}", "{:^{uvt}}"),
                DailyWeatherContent.SUMMARY: Field("sw", "{:<{sw}}", "{:<{sw}}"),
                DailyWeatherContent.HUMIDITY: Field("hw", "{:^{hw}}", "{:>{hw}}"),
                DailyWeatherContent.DEW_POINT: Field("dp", "{:^{dp}}", "{:<{dp}}"),
                DailyWeatherContent.SUNRISE_TIME: Field("sr", "{:^{sr}}", "{:^{sr}}"),
                DailyWeatherContent.SUNSET_TIME: Field("ss", "{:^{ss}}", "{:^{ss}}"),
                DailyWeatherContent.MOON_PHASE: Field("mp", "{:^{mp}}", "{:>{mp}}"),
            }

        selected_fields = report_builder.data_converter.keys()
        header = " ".join([fields[f].header for f in selected_fields])
        detail = " ".join([fields[f].detail for f in selected_fields])
        field_widths = {fields[f].key: report_builder.label_widths[f] for f in selected_fields}
        print(header.format(*(report_builder.labels[f][0] for f in selected_fields), **field_widths))
        print(header.format(*(report_builder.labels[f][1] for f in selected_fields), **field_widths))
        print(header.format(*("-" * field_widths[fields[f].key] for f in selected_fields), **field_widths))

        def safe_format(value):
            return "" if value is None else value

        for history in report_builder.data_generator(histories):
            print(detail.format(*(safe_format(history[f]) for f in selected_fields), **field_widths))
Beispiel #18
0
 def execute(self, options: argparse.Namespace, weather_data: WeatherData):
     location = get_location(weather_data, options.name)
     if not location:
         log.error("Location '%s' does not exist...", options.name)
     elif not weather_data.remove_location(location):
         log.error("Weather data for '%s' was not completely removed...", options.name)
Beispiel #19
0
async def _get_locations(
        properties: bool = Query(False, description="Optionally include location history statistics."),
        history: bool = Query(False, description="Optionally include a summary of location history."),
        weather_data: wd.WeatherData = Security(_get_weather_data, scopes=[Permissions.ReadWeatherData])
) -> List[Location]:
    return [_assemble_location(location, properties, history, weather_data) for location in weather_data.locations()]