Git Product home page Git Product logo

pysurfline's People

Contributors

dependabot[bot] avatar giocaizzi avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar

pysurfline's Issues

add permissions

type(model_class) == Details returns True when model_class is an istance

of a class

# permissions : dict = None TODO: add permissions

"""api functions and classes"""

import requests

from .models.spots import Wave, Wind, Weather, SunlightTimes, Tides, Details


class ApiResponseObject:
    _data: dict = None  # spot/forecasts response
    _associated: dict = None
    _spot: dict = None  # spot/details response
    # permissions : dict = None TODO: add permissions
    _url: str = None
    model_class = None

    def __init__(self, response: requests.Response, model_class=None):
        if "data" in response.json():
            self._data = response.json()["data"]
        if "associated" in response.json():
            self._associated = response.json()["associated"]
        if "spot" in response.json():
            self._spot = response.json()["spot"]
        # urse
        self._url = response.url

        # parse data

        # type(model_class) is Details returns False as it is a class
        # type(model_class) == Details returns True when model_class is an istance
        # of a class
        if model_class is not None:
            self.model_class = model_class
            if self._data is not None:
                self._parse_data(model_class)

    @property
    def data(self):
        return self._data

    @property
    def associated(self):
        return self._associated

    @property
    def spot(self):
        return self._spot

    @property
    def url(self):
        return self._url

    def _parse_data(self, model_class) -> None:
        """parse data into model class"""
        # make keys lowercase
        self._data = {key.lower(): value for key, value in self._data.items()}

        self._data = [
            model_class(**item) for item in self._data[model_class.__name__.lower()]
        ]

    def __str__(self):
        if self.model_class is None:
            return f"ApiObject({self.url})"
        else:
            return f"{self.model_class.__name__}({self.url})"

    def __repr__(self):
        return str(self)


class SpotForecastsWave(ApiResponseObject):
    """spots/forecasts/wave endpoint"""

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs, model_class=Wave)


class SpotForecastsWind(ApiResponseObject):
    """spots/forecasts/wind endpoint"""

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs, model_class=Wind)


class SpotForecastsWeather(ApiResponseObject):
    """spots/forecasts/weather endpoint"""

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs, model_class=Weather)


class SpotForecastsSunlightTimes(ApiResponseObject):
    """spots/forecasts/sunlightTimes endpoint"""

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs, model_class=SunlightTimes)


class SpotForecastsTides(ApiResponseObject):
    """spots/forecasts/tides endpoint"""

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs, model_class=Tides)


class SpotDetails(ApiResponseObject):
    """spots/details endpoint"""

    def __init__(self, *args, **kwargs):
        super().__init__(*args, **kwargs, model_class=Details)

Fails on Python v3.10 and higher

Hey, great work on this :)

I've noticed that the module doesn't work on versions of python newer than version 3.10.
The following error is produced:
AttributeError: module 'collections' has no attribute 'MutableMapping'

This is occurring because collections.MutableMappings was replaced with collections.abc.MutableMappings in Python 3.10.

A quick and easy way this could be fixed, is by updating utils.py to import the new version when being run from newer versions of Python like this:

if sys.version_info.major == 3 and sys.version_info.minor >= 10:
    from collections.abc import MutableMapping
else:
    from collections import MutableMapping

API changed

  • Latelty API responses have changed (been changing?!?)

    • develop and main have same entrypoint but seems receive different responses (?)
  • It happens that seems like the latest release does not crash somehow.

Login and authentication

TODO: Login and authentication

from pysurfline.api.services import ApiService
from pysurfline.core import SpotForecasts


class SurflineClient:
    """surfline client

    Surfline API client.
    At the moment, does not require authentication.

    TODO: Login and authentication
    """

    _baseurl: str = "https://services.surfline.com/kbyg/"

    def get_spot_forecasts(
        self, spotId: str, intervalHours: int = None, days: int = None
    ) -> SpotForecasts:
        """create a SpotForecast object from API responses

        Arguments:
            spotId (str): spot id
            intervalHours (int, optional): interval hours. Defaults to None.
            days (int, optional): days. Defaults to None.

        Returns:
            SpotForecast: SpotForecast object
        """
        params = {}
        params["spotId"] = spotId

        # add optional parameters
        if intervalHours:
            params["intervalHours"] = intervalHours
        if days:
            params["days"] = days

        return SpotForecasts(
            spotId,
            # spot datails are integrated with forecast,
            # hence the different GET args
            details=ApiService(self, "spots/details").get({"spotId": spotId}),
            waves=ApiService(self, "spots/forecasts/wave").get(params=params),
            winds=ApiService(self, "spots/forecasts/wind").get(params=params),
            tides=ApiService(self, "spots/forecasts/tides").get(params=params),
            # weather and sunlight times are in the same response
            weather=ApiService(self, "spots/forecasts/weather").get(params=params)[0],
            sunlightTimes=ApiService(self, "spots/forecasts/weather").get(
                params=params
            )[1],
        )

spot/forecasts/conditions

# TODO: include "surf" in `surf` output

"""core module: SpotForecast"""

import pandas as pd

from .utils import flatten
from .api.objects import (
    SpotDetails,
    SpotForecastsWave,
    SpotForecastsWind,
    SpotForecastsTides,
    SpotForecastsSunlightTimes,
    SpotForecastsWeather,
)


class SpotForecasts:
    """spot forecasts data model

    Composite data model of all the spot forecasts data,
    - wave
    - condition (TODO)
    - wind
    - tides
    - weather and sunrise and sunset times

    TODO: add associated data and improve utcOffset
    """

    name: str = None

    def __init__(
        self,
        spotId: str,
        details: SpotDetails,
        waves: SpotForecastsWave,
        winds: SpotForecastsWind,
        tides: SpotForecastsTides,
        sunlightTimes: SpotForecastsSunlightTimes,
        weather: SpotForecastsWeather,
    ):
        self.spotId = spotId
        self.name = details.spot["name"]
        self.waves = waves.data
        self.wind = winds.data
        self.tides = tides.data
        self.weather = weather.data
        self.sunlightTimes = sunlightTimes.data

    def get_dataframe(self, attr="surf") -> pd.DataFrame:
        """pandas dataframe of selected attribute

        Use default to get the pandas dataframe of surf data,
        or of the selected attribute `attr`:
        - waves
        - wind
        - tides
        - weather
        - sunlightTimes

        Args:
            attr (str, optional): attribute to get dataframe from.
                Defaults to "surf".

        Raises:
            ValueError: if attr is not a valid attribute
        """
        if attr == "surf":
            # concat all dataframes
            data = []
            for attr in ["waves", "wind", "weather"]:
                # excluding "sunlightTimes" and "tides" due to different timestamps
                # TODO: include "surf" in `surf` output
                data.append(
                    pd.DataFrame(_flatten_objects(getattr(self, attr)))
                    .set_index("timestamp_dt")
                    .reset_index()
                )
            df = pd.concat(data, axis=1)
            # remove duplicated columns
            df = df.loc[:, ~df.columns.duplicated()]
            return df
        elif attr in ["waves", "wind", "tides", "weather", "sunlightTimes"]:
            # return single
            return pd.DataFrame(_flatten_objects(getattr(self, attr))).reset_index()
        else:
            raise ValueError(f"Attribute {attr} not supported. Use a valid attribute.")


def _flatten_objects(list_of_objects) -> list:
    """return list of flattened objects"""
    return [flatten(item.__dict__) for item in list_of_objects]

add associated data and improve handling of utcOffset

TODO: add associated data and improve utcOffset

"""core module: SpotForecast"""

import pandas as pd

from .utils import flatten
from .api.objects import (
    SpotDetails,
    SpotForecastsWave,
    SpotForecastsWind,
    SpotForecastsTides,
    SpotForecastsSunlightTimes,
    SpotForecastsWeather,
)


class SpotForecasts:
    """spot forecasts data model

    Composite data model of all the spot forecasts data,
    - wave
    - condition (TODO)
    - wind
    - tides
    - weather and sunrise and sunset times

    TODO: add associated data and improve utcOffset
    """

    name: str = None

    def __init__(
        self,
        spotId: str,
        details: SpotDetails,
        waves: SpotForecastsWave,
        winds: SpotForecastsWind,
        tides: SpotForecastsTides,
        sunlightTimes: SpotForecastsSunlightTimes,
        weather: SpotForecastsWeather,
    ):
        self.spotId = spotId
        self.name = details.spot["name"]
        self.waves = waves.data
        self.wind = winds.data
        self.tides = tides.data
        self.weather = weather.data
        self.sunlightTimes = sunlightTimes.data

    def get_dataframe(self, attr="surf") -> pd.DataFrame:
        """pandas dataframe of selected attribute

        Use default to get the pandas dataframe of surf data,
        or of the selected attribute `attr`:
        - waves
        - wind
        - tides
        - weather
        - sunlightTimes

        Args:
            attr (str, optional): attribute to get dataframe from.
                Defaults to "surf".

        Raises:
            ValueError: if attr is not a valid attribute
        """
        if attr == "surf":
            # concat all dataframes
            data = []
            for attr in ["waves", "wind", "weather"]:
                # excluding "sunlightTimes" and "tides" due to different timestamps
                # TODO: include "surf" in `surf` output
                data.append(
                    pd.DataFrame(_flatten_objects(getattr(self, attr)))
                    .set_index("timestamp_dt")
                    .reset_index()
                )
            df = pd.concat(data, axis=1)
            # remove duplicated columns
            df = df.loc[:, ~df.columns.duplicated()]
            return df
        elif attr in ["waves", "wind", "tides", "weather", "sunlightTimes"]:
            # return single
            return pd.DataFrame(_flatten_objects(getattr(self, attr))).reset_index()
        else:
            raise ValueError(f"Attribute {attr} not supported. Use a valid attribute.")


def _flatten_objects(list_of_objects) -> list:
    """return list of flattened objects"""
    return [flatten(item.__dict__) for item in list_of_objects]

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.