diff --git a/smartcitizen_connector/__init__.py b/smartcitizen_connector/__init__.py index 9931cff..d8f5c80 100644 --- a/smartcitizen_connector/__init__.py +++ b/smartcitizen_connector/__init__.py @@ -1,20 +1,24 @@ -from .models import (Sensor, Measurement, Kit, Owner, Location, - HardwareInfo, Postprocessing, Data, Device) +from .models import (Sensor, Measurement, Owner, User, Location, + HardwareInfo, Postprocessing, Data, Device, Experiment) +from .handler import HttpHandler from .device import SCDevice#, get_devices -from .sensor import SCSensor, get_sensors -from .measurement import SCMeasurement, get_measurements +from .sensor import SensorHandler, get_sensors +from .measurement import MeasurementHandler, get_measurements +from .experiment import ExperimentHandler, get_experiments from .search import search_by_query, global_search +from .user import UserHandler, get_users __all__ = [ "Device", - "Kit", "Sensor", "Measurement", + "User", "Owner", "Location", "Data", "Postprocessing", - "HardwareInfo" + "HardwareInfo", + "Experiment" ] __version__ = '1.1.3' diff --git a/smartcitizen_connector/_config/config.py b/smartcitizen_connector/_config/config.py index 9d42fc7..b1ea273 100644 --- a/smartcitizen_connector/_config/config.py +++ b/smartcitizen_connector/_config/config.py @@ -10,6 +10,8 @@ class Config(): DEVICES_URL = API_URL + 'devices/' SENSORS_URL = API_URL + 'sensors/' MEASUREMENTS_URL = API_URL + 'measurements/' + EXPERIMENTS_URL = API_URL + 'experiments/' + USERS_URL = API_URL + 'users/' FRONTEND_URL = 'https://smartcitizen.me/kits/' BASE_POSTPROCESSING_URL='https://raw.githubusercontent.com/fablabbcn/smartcitizen-data/master/' API_SEARCH_URL = API_URL + "search?q=" diff --git a/smartcitizen_connector/experiment/__init__.py b/smartcitizen_connector/experiment/__init__.py new file mode 100644 index 0000000..f4a81d4 --- /dev/null +++ b/smartcitizen_connector/experiment/__init__.py @@ -0,0 +1 @@ +from .experiment import ExperimentHandler, get_experiments diff --git a/smartcitizen_connector/experiment/experiment.py b/smartcitizen_connector/experiment/experiment.py new file mode 100644 index 0000000..6461c0d --- /dev/null +++ b/smartcitizen_connector/experiment/experiment.py @@ -0,0 +1,42 @@ +from smartcitizen_connector.models import Experiment +from smartcitizen_connector._config import config +from smartcitizen_connector.tools import * +from pydantic import TypeAdapter +from typing import Optional, List +from smartcitizen_connector.handler import HttpHandler + +# TODO - Can this inherit from experiment? +class ExperimentHandler(HttpHandler): + + def __init__(self, id: int = None, **kwargs): + self.id = id + super().__init__(config.EXPERIMENTS_URL) + + if self.id is not None: + r = self.get() + self.model = TypeAdapter(Experiment).validate_python(r.json()) + else: + self.model = Experiment(**kwargs) + + def __getattr__(self, attr): + return self.model.__getattribute__(attr) + +def get_experiments(): + isn = True + result = list() + url = config.EXPERIMENTS_URL + + while isn: + r = get(url) + r.raise_for_status() + # If status code OK, retrieve data + h = process_headers(r.headers) + result += TypeAdapter(List[Experiment]).validate_python(r.json()) + + if 'next' in h: + if h['next'] == url: isn = False + elif h['next'] != url: url = h['next'] + else: + isn = False + + return result diff --git a/smartcitizen_connector/handler/__init__.py b/smartcitizen_connector/handler/__init__.py new file mode 100644 index 0000000..2f15e8a --- /dev/null +++ b/smartcitizen_connector/handler/__init__.py @@ -0,0 +1 @@ +from .httphandler import HttpHandler diff --git a/smartcitizen_connector/handler/httphandler.py b/smartcitizen_connector/handler/httphandler.py new file mode 100644 index 0000000..5f49398 --- /dev/null +++ b/smartcitizen_connector/handler/httphandler.py @@ -0,0 +1,58 @@ +from requests import get, patch, post, delete +from os import environ +from smartcitizen_connector.tools import logger +from typing import Optional +import json + +class HttpHandler: + url: Optional[str] = None + + def __init__(self, path: str): + self.path = path + self.__set_headers__() + if self.id is not None: + self.url = f'{self.path}{self.id}' + + def __set_headers__(self): + + self.headers = { + 'Content-type': 'application/json' + } + + if 'SC_BEARER' not in environ: + logger.warning('No Auth Bearer set. Will not be able to POST, PATCH, DELETE. Include it environment variable with SC_BEARER') + return False + + logger.info('Using Auth Bearer') + self.headers['Authorization'] = 'Bearer ' + environ['SC_BEARER'] + + return True + + def get(self): + r = get(self.url) + r.raise_for_status() + return r + + def patch(self, property: str): + r = patch(self.url, + data=self.model.json(include=property, + exclude_none=True), + headers = self.headers + ) + r.raise_for_status() + return r + + def post(self): + r = post(self.path, + data=self.model.json(exclude_none=True), + headers = self.headers) + + r.raise_for_status() + return r + + def delete(self): + r = delete(self.url, + headers = self.headers) + + r.raise_for_status() + return r diff --git a/smartcitizen_connector/measurement/__init__.py b/smartcitizen_connector/measurement/__init__.py index 6da8764..7074c6a 100644 --- a/smartcitizen_connector/measurement/__init__.py +++ b/smartcitizen_connector/measurement/__init__.py @@ -1 +1 @@ -from .measurement import SCMeasurement, get_measurements \ No newline at end of file +from .measurement import MeasurementHandler, get_measurements \ No newline at end of file diff --git a/smartcitizen_connector/measurement/measurement.py b/smartcitizen_connector/measurement/measurement.py index f196ced..ffc0ecc 100644 --- a/smartcitizen_connector/measurement/measurement.py +++ b/smartcitizen_connector/measurement/measurement.py @@ -1,28 +1,25 @@ -from smartcitizen_connector.models import (Sensor, Measurement) +from smartcitizen_connector.models import Measurement from smartcitizen_connector._config import config from smartcitizen_connector.tools import * from pydantic import TypeAdapter -from typing import List -from requests import get +from typing import Optional, List +from smartcitizen_connector.handler import HttpHandler -class SCMeasurement: - id: int - url: str - page: str - json: Measurement +# TODO - Can this inherit from Measurement? +class MeasurementHandler(HttpHandler): - def __init__(self, id): + def __init__(self, id: int = None, **kwargs): self.id = id - self.url = f'{config.SENSORS_URL}{self.id}' - self.method = 'async' - r = self.__safe_get__(self.url) - self.json = TypeAdapter(Sensor).validate_python(r.json()) + super().__init__(config.MEASUREMENTS_URL) - def __safe_get__(self, url): - r = get(url) - r.raise_for_status() + if self.id is not None: + r = self.get() + self.model = TypeAdapter(Measurement).validate_python(r.json()) + else: + self.model = Measurement(**kwargs) - return r + def __getattr__(self, attr): + return self.model.__getattribute__(attr) def get_measurements(): isn = True @@ -40,4 +37,5 @@ def get_measurements(): elif h['next'] != url: url = h['next'] else: isn = False + return result diff --git a/smartcitizen_connector/models/__init__.py b/smartcitizen_connector/models/__init__.py index a1db987..da2cc7f 100644 --- a/smartcitizen_connector/models/__init__.py +++ b/smartcitizen_connector/models/__init__.py @@ -1,2 +1,3 @@ -from .models import (Sensor, Measurement, Kit, Owner, Location, Metric, - HardwareInfo, HardwarePostprocessing, Postprocessing, Data, Device, HardwareStatus, Policy) \ No newline at end of file +from .models import (Sensor, Measurement, Owner, User, Location, Metric, + HardwareInfo, HardwarePostprocessing, Postprocessing, + Data, Device, HardwareStatus, Policy, Experiment) \ No newline at end of file diff --git a/smartcitizen_connector/models/models.py b/smartcitizen_connector/models/models.py index bd5f56b..cba05af 100644 --- a/smartcitizen_connector/models/models.py +++ b/smartcitizen_connector/models/models.py @@ -1,11 +1,13 @@ from datetime import datetime -from typing import Optional, List, Dict +from typing import Optional, List, Dict, Any from pydantic import BaseModel class Measurement(BaseModel): id: int + uuid: str name: str description: str + definition: Optional[str] = None class Metric(BaseModel): id: Optional[int] = None @@ -20,26 +22,19 @@ class Metric(BaseModel): class Sensor(BaseModel): id: int + uuid: str name: str description: str unit: Optional[str] = None - measurement_id: Optional[int] = None measurement: Optional[Measurement] = None + datasheet: Optional[str] = None + unit_definition: Optional[str] = None value: Optional[float] = None prev_value: Optional[float] = None last_reading_at: Optional[datetime] = None tags: Optional[List[str]] = [] default_key: Optional[str] = [] -class Kit(BaseModel): - id: int - slug: str - name: str - description: str - created_at: datetime - updated_at: datetime - sensors: Optional[List[Sensor]] = None - class Owner(BaseModel): id: int username: str @@ -48,8 +43,8 @@ class Owner(BaseModel): class Location(BaseModel): city: Optional[str] = None - country_code: str - country: str + country_code: Optional[str] = None + country: Optional[str] = None exposure: Optional[str] = None elevation: Optional[float] = None geohash: Optional[str] = None @@ -103,9 +98,9 @@ class Notifications(BaseModel): stopped_publishing: bool class Policy(BaseModel): - is_private: bool - precise_location: bool - enable_forwarding: bool + is_private: Any + precise_location: Any + enable_forwarding: Any class Device(BaseModel): id: int @@ -117,12 +112,37 @@ class Device(BaseModel): hardware: HardwareInfo system_tags: List[str] user_tags: List[str] - # data_policy: Policy + data_policy: Optional[Policy] = None notify: Notifications last_reading_at: Optional[datetime] = None created_at: Optional[datetime] = None updated_at: datetime - owner: Owner - data: Data + owner: Optional[Owner] = None + data: Optional[Data] = None location: Optional[Location]= None device_token: str = 'FILTERED' + +class Experiment(BaseModel): + id: Optional[int] = None + name: str + description: Optional[str] = '' + owner_id: Optional[int] = None + active: Optional[bool] = None + is_test: bool + starts_at: Optional[datetime] = None + ends_at: Optional[datetime] = None + created_at: Optional[datetime] = None + updated_at: Optional[datetime] = None + device_ids: Optional[List[int]] = None + +class User(BaseModel): + id: int + uuid: str + username: str + role: Optional[str] = "" + devices: Optional[List[Device]] = None + profile_picture: str + location: Location + updated_at: datetime + forwarding_token: str + forwarding_username: str diff --git a/smartcitizen_connector/sensor/__init__.py b/smartcitizen_connector/sensor/__init__.py index a5d083f..19707e5 100644 --- a/smartcitizen_connector/sensor/__init__.py +++ b/smartcitizen_connector/sensor/__init__.py @@ -1 +1 @@ -from .sensor import SCSensor, get_sensors \ No newline at end of file +from .sensor import SensorHandler, get_sensors \ No newline at end of file diff --git a/smartcitizen_connector/sensor/sensor.py b/smartcitizen_connector/sensor/sensor.py index 86d1cd0..cd2fac5 100644 --- a/smartcitizen_connector/sensor/sensor.py +++ b/smartcitizen_connector/sensor/sensor.py @@ -1,33 +1,30 @@ -from smartcitizen_connector.models import (Sensor, Measurement) +from smartcitizen_connector.models import Sensor from smartcitizen_connector._config import config from smartcitizen_connector.tools import * from pydantic import TypeAdapter -from typing import List -from requests import get +from typing import Optional, List +from smartcitizen_connector.handler import HttpHandler -class SCSensor: - id: int - url: str - page: str - json: Sensor +class SensorHandler(HttpHandler): - def __init__(self, id): + def __init__(self, id: int = None, **kwargs): self.id = id - self.url = f'{config.SENSORS_URL}{self.id}' - self.method = 'async' - r = self.__safe_get__(self.url) - self.json = TypeAdapter(Sensor).validate_python(r.json()) + super().__init__(config.SENSORS_URL) - def __safe_get__(self, url): - r = get(url) - r.raise_for_status() + if self.id is not None: + r = self.get() + self.model = TypeAdapter(Sensor).validate_python(r.json()) + else: + self.model = Sensor(**kwargs) - return r + def __getattr__(self, attr): + return self.model.__getattribute__(attr) def get_sensors(): isn = True result = list() url = config.SENSORS_URL + while isn: r = get(url) r.raise_for_status() @@ -40,4 +37,5 @@ def get_sensors(): elif h['next'] != url: url = h['next'] else: isn = False + return result diff --git a/smartcitizen_connector/user/__init__.py b/smartcitizen_connector/user/__init__.py new file mode 100644 index 0000000..e68e397 --- /dev/null +++ b/smartcitizen_connector/user/__init__.py @@ -0,0 +1 @@ +from .user import UserHandler, get_users \ No newline at end of file diff --git a/smartcitizen_connector/user/user.py b/smartcitizen_connector/user/user.py new file mode 100644 index 0000000..a0bc2cb --- /dev/null +++ b/smartcitizen_connector/user/user.py @@ -0,0 +1,39 @@ +from smartcitizen_connector.models import User +from smartcitizen_connector._config import config +from smartcitizen_connector.tools import * +from pydantic import TypeAdapter +from typing import Optional, List +from smartcitizen_connector.handler import HttpHandler + +class UserHandler(HttpHandler): + + def __init__(self, id: int = None, **kwargs): + self.id = id + super().__init__(config.USERS_URL) + + if self.id is not None: + r = self.get() + self.model = TypeAdapter(User).validate_python(r.json()) + else: + self.model = User(**kwargs) + + def __getattr__(self, attr): + return self.model.__getattribute__(attr) + +def get_users(): + isn = True + result = list() + url = config.USERS_URL + while isn: + r = get(url) + r.raise_for_status() + # If status code OK, retrieve data + h = process_headers(r.headers) + result += TypeAdapter(List[User]).validate_python(r.json()) + + if 'next' in h: + if h['next'] == url: isn = False + elif h['next'] != url: url = h['next'] + else: + isn = False + return result