Пример #1
0
    def get_siblings(self, node_requested, limiter=None):
        """
        Returns a deferred! A sibling is defined as nodes having the same parent id.

        :raises YomboWarning: For invalid requests.
        :raises KeyError: When item requested cannot be found.
        :param node_requested: The node ID or node label to search for.
        :type node_requested: string
        :param limiter_override: Default: .89 - A value between .5 and .99. Sets how close of a match it the search should be.
        :type limiter_override: float
        :return: Pointer to requested node.
        :rtype: dict
        """
        try:
            node = self.get_meta(node_requested, limiter=limiter)
        except Exception as e:
            logger.warn("Unable to find requested node: {node}.  Reason: {e}", node=node_requested, e=e)
            raise YomboWarning()

        if node.parent_id is not None:
            siblings = {}
            for node_id, node_obj in self.nodes.items():
                if node_obj.parent_id == node.parent_id:
                    siblings[node_id] = node_obj
            return siblings
        else:
            raise YomboWarning("Node has no parent_id.")
Пример #2
0
    def start(self):
        self.fileInfo = yield self._SQLDict.get("yombo.utils-filereader",
                                                "fileInfo")

        if "startLocation" not in self.fileInfo:
            self.fileInfo["startLocation"] = 0
        self.fp_in = None
        self.watch_loop = None

        try:
            if not os.path.exists(self.filename):
                if self.make_if_missing is True:  # if file doesn't exist
                    Path.touch(
                        self.filename
                    )  # Touch the file if it doesn't exists and set the counter to 0.
                    self.fileInfo["startLocation"] = 0
                else:
                    raise YomboWarning(
                        "File does not exist, told not cannot create one.",
                        554224, "__init__", "FileReader")
            else:  # else, exists. If smaller than last run, reset the file pointer.
                file_info = os.stat(self.filename)
                if file_info.st_size < self.fileInfo["startLocation"]:
                    self.fileInfo["startLocation"] = 0

            self.fp_in = codecs.open(self.filename, encoding="utf-8")
            self.fp_in.seek(self.fileInfo["startLocation"])
        except IOError as e:
            (errno, strerror) = e.args
            raise YomboWarning(
                f"FileReader could not open file for reading. Reason: {strerror}",
                265743, "__init__", "FileReader")
        else:
            self.watch_loop = LoopingCall(self._watch)
            self.watch_loop.start(self.frequency)
Пример #3
0
 def _check_results(self, response, method, url, request_data):
     if response.content_type == "string":
         logger.warn("-----==( Error: API received an invalid response, got a string back )==----")
         logger.warn("Request: {request}", request=response.request.__dict__)
         logger.warn("URL: {method} {uri}", method=response.request.method, uri=response.request.uri)
         logger.warn("Header: {request_headers}", request_headers=response.request.headers)
         logger.warn("Request Data: {request_data}", request_data=request_data)
         logger.warn("Response Headers: {headers}", headers=response.headers)
         logger.warn("Content: {content}", content=response.content)
         logger.warn("--------------------------------------------------------")
     elif response.response_code >= 300:
         logger.warn("-----==( Error: API received an invalid response )==----")
         logger.warn("Request: {request}", request=response.request.__dict__)
         logger.warn("Code: {code}", code=response.response_code)
         logger.warn("URL: {method} {uri}", method=response.request.method, uri=response.request.uri)
         logger.warn("Header: {request_headers}", request_headers=response.request.headers)
         logger.warn("Request Data: {request_data}", request_data=request_data)
         logger.warn("Response Headers: {headers}", headers=response.headers)
         logger.warn("Content: {content}", content=response.content)
         logger.warn("--------------------------------------------------------")
         message = ""
         logger.warn("Error with API request: {method} {url}", method=method, url=url)
         if "errors" in response.content:
             errors = response.content["errors"]
             for error in errors:
                 if len(message) == 0:
                     message += ", "
                 message += f"{message}  {error['title']} - {error['detail']}"
                 raise YomboWarning(response.content["errors"], response.response_code, "update_results",
                                    "yomboapi", meta=response)
         else:
             message = f"{response.response_code} - {response.response_phrase}"
         raise YomboWarning(message, response.response_code, "update_results", "yomboapi", meta=response)
Пример #4
0
    def _validate_name(self, bucket_name):
        """
        Validates the bucket_name being submitted is valid. No point in sending badly bucket_named
        items to the server, as the server will simply perform this same check and
        discard any invalid ones.

        .. note::

            If the server detects too many invalid bucket_names, the gateway will be blocked from
            saving statistics in the future.

        :param bucket_name: Label for the statistic
        :type bucket_name: string
        """
        parts = bucket_name.split('.', 10)
        if len(parts) < 3:
            raise YomboWarning(
                "bucket_name must have at least 3 parts, preferably at least 4."
            )
        elif len(parts) > 8:
            raise YomboWarning(
                "bucket_name has too many parts, no more than 8.")

        for count in range(0, len(parts)):
            if len(parts[count]) < 3:
                raise YomboWarning(
                    "'%s' is too short, must be at least 3 characters: " %
                    parts[count])
Пример #5
0
    def encrypt(
        self,
        in_text,
        destination=None,
    ):
        """
        Encrypt text and output as ascii armor text.

        :param in_text: Plain text to encrypt.
        :type in_text: string
        :param destination: Key id of the destination.
        :type destination: string
        :return: Ascii armored text.
        :rtype: string
        :raises: YomboException - If encryption failed.
        """
        if in_text.startswith('-----BEGIN PGP MESSAGE-----'):
            return in_text

        if destination is None:
            destination = self.mykeyid()

        try:
            # output = self.gpg.encrypt(in_text, destination, sign=self.mykeyid())
            output = yield threads.deferToThread(self._gpg_encrypt, in_text,
                                                 destination)
            # output = self.gpg.encrypt(in_text, destination)
            if output.status != "encryption ok":
                raise YomboWarning("Unable to encrypt string. Error 1.")
            return output.data
        except Exception as e:
            raise YomboWarning("Unable to encrypt string. Error 2.: %s" % e)
Пример #6
0
    def download_file(self, url: str, destination_filename: str):
        """
        Downloads a file from the given url and saves it to the requested destination.

        Returns a deferred!
        :param url:
        :param destination_filename:
        :return:
        """
        try:
            treq_response = yield treq.request("GET", url)
        except ConnectionRefusedError as e:
            raise YomboWarning(f"Connection was refused to '{url}': {e}")
        except ConnectError as e:
            raise YomboWarning(f"Error connecting to '{url}': {e}")
        except Exception as e:
            logger.info("Requests download_file error: {error}", error=e)
            logger.error(
                "---------------==(Traceback)==--------------------------")
            logger.error("{url}", url=url)
            logger.error("{trace}", trace=traceback.format_exc())
            logger.error(
                "--------------------------------------------------------")
            logger.warn(
                "An exception of type {etype} occurred in yombo.lib.yomboapi:import_component. Message: {msg}",
                etype=type(e),
                msg=e)
            logger.error(
                "--------------------------------------------------------")
            raise e

        raw_content = yield treq.content(treq_response)
        yield self._Files.save(destination_filename, raw_content)
Пример #7
0
    def verify_asymmetric(self, in_text):
        """
        Verifys a signature. Returns the data if valid, otherwise False.
        """
        if in_text is None:
            return False

        if type(in_text) is str and in_text.startswith(
                "-----BEGIN PGP SIGNED MESSAGE-----"):
            try:
                verified = self.gpg_module.verify(in_text)
                if verified.status == "signature valid":
                    if verified.stderr.find("TRUST_ULTIMATE") > 0:
                        pass
                    elif verified.stderr.find("TRUST_FULLY") > 0:
                        pass
                    else:
                        raise YomboWarning(
                            "Encryption not from trusted source!")
                    out = self.gpg_module.decrypt(in_text)
                    return out.data
                else:
                    return False
            except Exception as e:
                raise YomboWarning(
                    "Error with GPG system. Unable to verify signed text: {e}",
                    e=e)
        return False
Пример #8
0
    def set(self,
            content: Union[bytes, str],
            mime_type: Optional[str] = None,
            charset: Optional[str] = None,
            created_at: Optional[Union[int, float]] = None) -> None:
        """
        Set the content for the class. The content can accept two input types:
          * str - If a string, it's assumed the content is a pathname to read and load into memory.
          * bytes - Store contents directly into memory without reading any files.

        :param content: Str filename to read, or bytes to store.
        :param mime_type:
        :param charset:
        :param created_at:
        """
        if isinstance(content, bytes):  # we have a raw image:
            pass
        elif isinstance(content, str):  # we should have a file path:
            if os.path.exists(self.yombo_toml_path) is False:
                raise YomboWarning(
                    f"String types must be a path/filename to an file to load."
                )
            content = yield self._Files.read(content)
        else:
            raise YomboWarning("Unknown input type.")

        if mime_type is None or charset is None:
            meta = yield self._Files.mime_type_from_buffer(content)
        self.content = content
        self.mime_type = mime_type or meta["mime_type"]
        self.charset = charset or meta["charset"]
        self.created_at = created_at or time()
Пример #9
0
    def _check_filter_platform(self, rule, portion):
        """
        Help function to add_rule.  It checks to make sure a any 'filter': {'platform': 'platform_name'} exists. It
        tests against self.filters that was gathered from various modules and modules using hooks.

        :param rule: the rule
        :param portion: source to check
        :return:
        """
        filter_platform = portion['filter']['platform']
        if filter_platform in self.filters:
            validate_filter_callback_function = self.filters[filter_platform][
                'validate_filter_callback']
            try:
                rule = validate_filter_callback_function(rule, portion)
            except YomboWarning as e:
                logger.warn(
                    "Rule '{rule}': Has invalid filter platform params. filter_platform: {filter_platform}.",
                    rule=rule['name'],
                    filter_platform=filter_platform)
                raise YomboWarning(
                    "Rule '%s': Has invalid filter platform params. filter_platform: %s."
                    % (rule['name'], filter_platform), 113,
                    '_check_filter_platform', 'automation')
        else:
            logger.warn(
                "Rule '{rule}': Filter doesn't have platform: {filter_platform}",
                rule=rule['name'],
                filter_platform=filter_platform)
            raise YomboWarning(
                "Rule '%s': Filter doesn't have platform: %s" %
                (rule['name'], filter_platform), 114, '_check_filter_platform',
                'automation')

        return rule
    def thumbnail(self, size=None, image_type=None, **kwargs):
        """
        Create a thumbnail version of the image.

        :param size:
        :param quality:
        :return:
        """
        if self.content_type.startswith("image") is False:
            raise YomboWarning("Cannot create thumb from non-image type.")

        if size is None:
            size = (300, 300)
        else:
            size = (size, size)
        if image_type is None:
            image_type = self.content_type
        else:
            image_type = image_type.lower()
            if image_type not in TYPE_MAP:
                raise YomboWarning(f"Unknown image_type to output to: {iamge_type}")

        if "quality" not in kwargs:
            kwargs["quality"] = 50
        thumb_pil = self.image_pil.copy()
        thumb_pil.thumbnail(size, PILImage.BICUBIC)
        results = yield self.get_contents(thumb_pil, image_type)
        return results
Пример #11
0
    def __init__(self, parent, video_url, output_format=None, quality=None):
        """
        Setup the sensor. The quality setting will only be used for streams that are not MJPEG, these will
        be directly pulled from the video stream unmodified for best results.

        :param video_url: File path or URL of the video.
        :param quality: The quality of the jpg, ranging from 1 to 32, 1 being best. Suggested: 2-5.

        :param parent: Library or module reference.
        """
        super().__init__(parent)
        self.video_url = video_url
        self.output_format = output_format or IMAGE_JPEG
        if self.output_format not in (IMAGE_JPEG, IMAGE_PNG):
            raise YomboWarning("output_format must be 'jpeg' or 'png'.")
        self.quality = quality

        self.detected_type = None
        self._already_streaming = False

        try:
            self.ffprobe_bin = self._Parent._Atoms.get("ffprobe_bin")
        except KeyError:
            self.ffprobe_bin = None
            raise YomboWarning(
                "ffprobe was not found, check that ffmpeg is installed and accessible from your path environment variable."
            )
Пример #12
0
    def check_duplicate_scene(self,
                              label=None,
                              machine_label=None,
                              scene_id=None):
        """
        Checks if a new/update scene label and machine_label are already in use.

        :param label:
        :param machine_label:
        :param scene_id: Ignore matches for a scene_id
        :return:
        """
        if label is None and machine_label is None:
            raise YomboWarning(
                "Must have at least label or machine_label, or both.")
        for temp_scene_id, scene in self.scenes.items():
            if scene_id is not None and scene.node_id == scene_id:
                continue
            if scene.label.lower() == label.lower():
                raise YomboWarning(
                    f"Scene with matching label already exists: {scene.node_id}"
                )
            if scene.machine_label.lower() == machine_label.lower():
                raise YomboWarning(
                    f"Scene with matching machine_label already exists: {scene.node_id}"
                )
Пример #13
0
    def encrypt(self, in_text, destination=None, unicode=None):
        """
        Encrypt text and output as ascii armor text.

        :param in_text: Plain text to encrypt.
        :type in_text: string
        :param destination: Key id of the destination.
        :type destination: string
        :return: Ascii armored text.
        :rtype: string
        :raises: YomboException - If encryption failed.
        """
        if in_text.startswith("-----BEGIN PGP MESSAGE-----"):
            return in_text

        if hasattr(self, "myfingerprint") is False:
            return in_text

        if destination is None:
            destination = self.myfingerprint()

        # print("gpg encrypt destination: %s" % destination)
        try:
            # output = self.gpg.encrypt(in_text, destination, sign=self.myfingerprint())
            output = yield threads.deferToThread(self._gpg_encrypt, in_text, destination)
            # output = self.gpg.encrypt(in_text, destination)
            # print("gpg output: %s" % output)
            # print("gpg %s: %s" % (in_text, output.status))
            if output.status != "encryption ok":
                raise YomboWarning("Unable to encrypt string. Error 1.")
            if unicode is False:
                return output.data
            return bytes_to_unicode(output.data)
        except Exception as e:
            raise YomboWarning(f"Unable to encrypt string. Error 2.: {e}")
Пример #14
0
    def amqp_incoming(self, channel, deliver, properties, message):
        """
        Receives the raw AMQP message from the AMQP server. First, call 'AMQPYombo::incoming_raw' to validate and get
        something usable. Then, route the message to the proper handler.

        This callback is setup from the subscribe() method when the gateway is connected.

        :param channel:
        :param deliver:
        :param properties:
        :param message:
        :return:
        """
        data = self._AMQPYombo.incoming_raw(channel, deliver, properties, message)

        # logger.info("Received incoming message: {headers}", body=headers)
        # logger.info("Received incoming message: {body}", body=body)

        # Do one last final check.
        message_headers = data["message_headers"]
        if message_headers["message_type"] == "request":
            if "request_type" not in message_headers:
                raise YomboWarning("Discarding request message, header 'request_type' is missing.")
        if message_headers["message_type"] == "response":
            if "response_type" not in message_headers:
                raise YomboWarning("Discarding request message, header 'response_type' is missing.")

        yield self.amqp_incoming_routing(**data)
Пример #15
0
    def process_response(self, response):
        """
        Receives a treq response, collects the content, and attempts to smartly decode
        the content based on the response headers.

        If headers don't match, a YomboWarning will be raised.

        :param response:
        :param headers:
        :return:
        """
        raw_content = yield treq.content(response)
        content = raw_content
        headers = self.clean_headers(response, True)

        if HEADER_CONTENT_TYPE in headers:
            content_type = headers[HEADER_CONTENT_TYPE][0]
        else:
            content_type = None
        if content_type == HEADER_CONTENT_TYPE:
            try:
                content = yield treq.json_content(response)
                content_type = "dict"
            except Exception as e:
                raise YomboWarning(f"Receive response reported json, but found an error: {e}")
        elif content_type == CONTENT_TYPE_MSGPACK:
            try:
                content = msgpack.loads(raw_content)
            except Exception:
                if len(content) == 0:
                    return "dict", {}
                raise YomboWarning(f"Receive response reported msgpack, but isn't: {content}")
        else:
            content_type = "string"
            try:
                content = json.loads(raw_content)
                content_type = "dict"
            except Exception:
                try:
                    content = msgpack.loads(raw_content)
                    content_type = "dict"
                except Exception:
                    content = raw_content

        content = bytes_to_unicode(content)
        # return {
        #     "content_type": content_type,
        #     "headers": response.all_headers,
        # }

        self.content = bytes_to_unicode(content)
        self.content_raw = raw_content
        self.request = response.request.original
        self.response = response
        self.content_type = content_type
        self.response_phrase = bytes_to_unicode(response.phrase)
        self.response_code = response.code
        self.headers = headers
Пример #16
0
    def render_api(self, request, session, data_type, attributes=None, meta=None, included=None,
                   response_code=None):
        """
        Renders content to an API based client.
        :param request:
        :param session:
        :param data_type:
        :param attributes: A dictionary or list of dictionaries
        :param meta:
        :param included:
        :param response_code:
        :return:
        """
        if attributes is None:
            raise YomboWarning("Attributes is required for rendering to API.")

        response = {}

        if isinstance(attributes, list):
            response["data"] = []
            for item in attributes:
                response["data"].append({
                    "type": data_type,
                    "id": item["id"],
                    "attributes": item,
                })
        elif isinstance(attributes, dict):
            response["data"] = {
                "type": data_type,
                "id": attributes["id"],
                "attributes": attributes,
            }
        else:
            raise YomboWarning("Attributes must be a dictionary or list of dictionaries.")

        if included is not None:
            response["included"] = included
        if meta is not None:
            response["meta"] = meta

        if response_code is None:
            response_code = 200

        request.setResponseCode(response_code)
        request.setHeader("Content-Type", CONTENT_TYPE_JSON)
        idempotence = request.getHeader("x-idempotence")
        if idempotence is None:
            arguments = args_to_dict(request.args)
            idempotence = arguments.get("_idempotence", None)
        if idempotence is not None:
            idempotence = sha256_compact(f"{session.auth_id}:{idempotence}")
            self.idempotence[idempotence] = zlib.compress(msgpack.packb(json.dumps(
                {
                    "data": response,
                    "response_code": response_code,
                }
            )))
        return json.dumps(response)
Пример #17
0
    def request(self, method, url, **kwargs):
        """
        Make an HTTP request using treq. This basically uses treq, but parses the response
        and attempts to decode the data if it's json or msgpack.

        This must be called with "yield".

        It returns a dictionary with these keys:
           * content - The processed content. Convert JSON and msgpack to a dictionary.
           * content_raw - The raw content from server, only passed through bytes to unicode.
           * response - Raw treq response, with "all_headers" injected; which is a cleaned up headers version of
             response.headers.
           * content_type - NOT related to HTTP headers. This will be either "dict" if it's a dictionary, or "string".
           * request - The original request object. Contains attributes such as: method, uri, and headers,

        First two arguments:

        * method (str) – HTTP method. Example: "GET", "HEAD". "PUT", "POST".
        * url (str) – http or https URL, which may include query arguments.

        Keyword arguments for fine tuning:

        * headers (Headers or None) – Optional HTTP Headers to send with this request.
        * params (dict w/ str or list/tuple of str values, list of 2-tuples, or None.) – Optional parameters to be append as the query string to the URL, any query string parameters in the URL already will be preserved.
        * data (str, file-like, IBodyProducer, or None) – Optional request body.
        * json (dict, list/tuple, int, string/unicode, bool, or None) – Optional JSON-serializable content to pass in body.
        * persistent (bool) – Use persistent HTTP connections. Default: True
        * allow_redirects (bool) – Follow HTTP redirects. Default: True
        * auth (tuple of ("username", "password").) – HTTP Basic Authentication information.
        * cookies (dict or CookieJar) – Cookies to send with this request. The HTTP kind, not the tasty kind.
        * timeout (int) – Request timeout seconds. If a response is not received within this timeframe, a connection is aborted with CancelledError.
        * browser_like_redirects (bool) – Use browser like redirects (i.e. Ignore RFC2616 section 10.3 and follow redirects from POST requests). Default: False
        * unbuffered (bool) – Pass True to to disable response buffering. By default treq buffers the entire response body in memory.

        :return:
        """
        logger.debug("Request receive: {method} : {url}", method=method, url=url)
        method = method.upper()
        try:
            treq_response = yield treq.request(method, url, **kwargs)
        except ConnectionRefusedError as e:
            raise YomboWarning(f"Connection was refused to '{url}': {e}")
        except ConnectError as e:
            raise YomboWarning(f"Error connecting to '{url}': {e}")
        except Exception as e:
            logger.info("Requests error: {error}", error=e)
            logger.error("---------------==(Traceback)==--------------------------")
            logger.error("{method} {url}", method=method, url=url)
            logger.error("{trace}", trace=traceback.format_exc())
            logger.error("--------------------------------------------------------")
            logger.warn("An exception of type {etype} occurred in yombo.lib.yomboapi:import_component. Message: {msg}",
                        etype=type(e), msg=e)
            logger.error("--------------------------------------------------------")
            raise e

        response = WebResponse(self)
        yield response.process_response(treq_response)
        return response
Пример #18
0
    def add_notice(self,
                   notice,
                   from_db=False,
                   persist=True,
                   create_event=False):
        """
        Add a new notice.

        :param notice: A dictionary containing notification details.
        :type record: dict
        :returns: Pointer to new notice. Only used during unittest
        """
        print("adding notice1: %s" % notice)
        if 'id' not in notice:
            notice['id'] = random_string(length=16)
        if 'type' not in notice:
            notice['type'] = 'system'
        if 'priority' not in notice:
            notice['priority'] = 'normal'
        if 'source' not in notice:
            notice['source'] = ''

        if 'expire' not in notice:
            if 'timeout' in notice:
                notice['expire'] = int(time()) + notice['timeout']
            else:
                notice['expire'] = int(time()) + 3600
        else:
            if notice['expire'] > int(time()):
                YomboWarning(
                    "New notification is set to expire before current time.")
        if 'created_at' not in notice:
            notice['created_at'] = int(time())

        if 'acknowledged' not in notice:
            notice['acknowledged'] = False
        else:
            if notice['acknowledged'] not in (True, False):
                YomboWarning(
                    "New notification 'acknowledged' must be either True or False."
                )

        if 'title' not in notice:
            raise YomboWarning("New notification requires a title.")
        if 'message' not in notice:
            raise YomboWarning("New notification requires a message.")
        if 'meta' not in notice:
            notice['meta'] = {}

        logger.debug("notice: {notice}", notice=notice)
        if from_db is False:
            self._LocalDB.add_notification(notice)
            self.notifications.prepend(notice['id'], Notification(notice))
        else:
            self.notifications[notice['id']] = Notification(notice)
            # self.notifications = OrderedDict(sorted(self.notifications.items(), key=lambda x: x[1]['created_at']))
            pass
        return notice['id']
Пример #19
0
    def delete(self,
               request_type,
               data_id,
               purge=None,
               session=None,
               index=None):
        """
        Used to delete (delete) items to the Yombo Gateway. This can be used to delete commands, devices, etc.

        Optionally, we can ask the YomboAPI to purge the data. Typically, data is marked for deletion in 30 days.
        This allows some items to be recovered. A purge request will ask the Yombo API to just delete it now without
        tossing it into the trash.

        There is no guarantee that items can be recovered - many items are deleted immediately.

        Some requests, such as the gateway modules, require the ID of the gateway to modify. This is specified
        in the index parameter.

        :param request_type: Request type such as "commands", "variables_data", or "modules".
        :type request_type: str
        :param data_id: The item id to delete. Such as variable_data_id.
        :type data_id: str
        :param purge: Set to True if the data should be deleted instead of the trash bin.
        :type purge: bool
        :param session: A session to associate the change. Should be provided if the change is a the result of a web request.
        :type session: websession instance
        :param index: Some requests, such as gateway modules, require an additional ID to work.
        :type index: str
        :return: Returns a dictionary containing various attributes on success. Raises YomboWarning on failure.
        :rtype: dict
        """
        request_details = REQUEST_TYPES[request_type]
        if "DELETE" not in request_details["methods"]:
            raise YomboWarning(
                f"'{request_type} cannot perform 'DELETE' request.")

        try:
            response = yield self._YomboAPI.request("DELETE",
                                                    self._generate_uri(
                                                        request_details,
                                                        data_id=data_id,
                                                        index=index),
                                                    session=session)
        except YomboWarning as e:
            raise YomboWarning(
                {
                    "title": request_details["title_failed"],
                    "detail": e.message,
                },
                component_name="YomboAPI::InteractionsMixin::delete",
                component_type="library")

        return {
            "status": "ok",
            "content": response.content,
            "response": response
        }
Пример #20
0
    def edit_node(self, node_id, node_data, source=None, authorization=None, **kwargs):
        """
        This shouldn't be used by outside calls, instead, tp update the node, simply edit
        the node attributes directly. That will cause the node to update the database and Yombo API.

        This is used by other internal libraries to update a node's data in bulk and
        optionally

        :param node_id: Node ID to bulk update.
        :param node_data: Dictionary of items to update
        :param source: Should be: local or remote. Default is local.
        :param kwargs:
        :return:
        """
        gateway_id = self.gateway_id

        if source is None:
            source = "local"

        # print("editing node: %s" % node_id)
        if isinstance(node_data, dict) is False:
            raise YomboWarning("edit_node() only accepts dictionaries for 'node_data' argument.")

        if "data" not in node_data:
            raise YomboWarning("Cannot edit node, 'data' not found")


        global_invoke_all("_node_before_update_",
                          called_by=self,
                          node_id=node_id,
                          node=node_data,
                          in_memory=node_id in self.nodes,
                          )

        if source == "local":
            api_data = deepcopy(node_data)
            node_data["data"] = data_pickle(api_data["data"], api_data["data_content_type"])
            # print("new node data: %s" % api_data)
            api_to_send = {k: v for k, v in bytes_to_unicode(api_data).items() if v}

            response = yield self.patch_node(node_id=node_id, api_data=api_to_send, authorization=authorization)

            node_data = response.content["data"]['attributes']

        # Now we have the final data, lets update the local info.
        node_id = node_data["id"]
        if node_id in self.nodes:
            self.nodes[node_id].update_attributes(node_data)  # Update existing node data.
            self.nodes[node_id].save_to_db()

        global_invoke_all("_node_updated_",
                          called_by=self,
                          node_id=node_id,
                          node=self.nodes[node_id],
                          )
        return self.nodes[node_id]
Пример #21
0
    def decode_results(self, content, headers, code, phrase):
        # print("decode_results headers: %s" % headers)

        content_type = headers['content-type'][0]

        # print( "######  content: %s" % content)
        if content_type == 'application/json':
            try:
                content = json.loads(content)
                content_type = "dict"
            except Exception:
                raise YomboWarning(
                    "Receive yombo api response reported json, but isn't: %s" %
                    content)
        elif content_type == 'application/msgpack':
            try:
                content = msgpack.loads(content)
                content_type = "dict"
            except Exception:
                raise YomboWarning(
                    "Receive yombo api response reported msgpack, but isn't.")
        else:
            try:
                content = json.loads(content)
                content_type = "dict"
            except Exception:
                try:
                    content = msgpack.loads(content)
                    content_type = "dict"
                except Exception:
                    content_type = "string"

        results = {
            'content': content,
            'content_type': content_type,
            'code': code,
            'phrase': phrase,
            'headers': headers,
        }
        if content_type == "string":
            results['code'] = 500
            results['data'] = []
            results['content'] = {
                'message': 'Unknown api error',
                'html_message': 'Unknown api error',
            }
            print("Error content: %s" % content)
            return results
        else:
            if 'response' in content:
                if 'locator' in content['response']:
                    results['data'] = content['response'][content['response']
                                                          ['locator']]
                else:
                    results['data'] = []
            return results
Пример #22
0
def data_unpickle(data, encoder=None, zip=None):
    """
    Unpack data packed with data_pickle.

    :param data:
    :param encoder:
    :param zip: True if incoming data is zipped...

    :return:
    """
    if data is None:
        return None
    data = bytes_to_unicode(data)

    if encoder is None:
        # if zip_level is True or isinstance(zip_level, int):
        #     encoder = "msgpack_base85_zip"
        # else:
        encoder = "msgpack_base85"

    # Sometimes empty dictionaries are encoded...  This is a simple shortcut.
    if encoder == "msgpack_base85_zip" and data == "cwTD&004mifd":
        return {}
    elif encoder == "msgpack_base85" and data == "fB":
        return {}

    if "json" in encoder and "msgack" in encoder:
        raise YomboWarning(
            "Unpickle data can only have json or msgpack, not both.")
    if "base64" in encoder and "base85" in encoder:
        raise YomboWarning(
            "Unpickle data can only have base64 or base85, not both.")

    if "base64" in encoder:
        data = data + "=" * (-len(data) % 4)
        data = base64.b64decode(data)
    elif "base85" in encoder:
        data = base64.b85decode(data)

    try:
        data = zlib.decompress(data)
    except Exception as e:
        pass

    if "json" in encoder:
        try:
            data = bytes_to_unicode(json.loads(data))
        except Exception as e:
            raise YomboWarning(f"Error encoding json: {e}")
    elif "msgpack" in encoder:
        try:
            data = bytes_to_unicode(msgpack.unpackb(data))
        except Exception as e:
            raise YomboWarning(f"Error encoding msgpack: {e}")

    return data
Пример #23
0
    def __init__(self, owner_object, filename, **kwargs):
        """
        Generate a new File Reader instance. 

        The params defined refer to kwargs and become class variables.

        :param filename: **Required.** The path and file to monitor.
        :type filename: string
        :param fileid: Assign a unique file id. Used if the filename may change
            in the future, but you want to persist tracking if the filename changes.
            defaults to filename is not supplied.
        :type fileid: string
        :param encoding: If using text, specify the encoding - default: utf-8
        :param callback: **Required.** A function to call with new content found in file.
        :type callback: pointer to function
        :param make_if_missing: If true, will create an empty file if file doesn't
            already exist.  Default: True
        :type make_if_missing: bool
        :param frequency: How often, in seconds, to check for new content. Default: 1
        :type frequency: int
        """
        if "encoding" in kwargs:
            self.encoding = kwargs["encoding"]
        else:
            self.encoding = "utf-8"

        if filename is None or filename == "":
            raise YomboWarning("FileReader requires a file name to read from,",
                               652321, "__init__", "FileWriter")
        if filename.startswith("/") is False:
            filename = f"./{filename}"

        try:
            self.filename = kwargs["filename"]
        except:
            raise YomboWarning("FileReader requires a filename to read.",
                               232124, "__init__", "FileReader")

        if os.path.exists(self.filename) is False:
            raise YomboWarning(
                f"FileReader cannot find the requested file to open for monitoring: {self.filename}",
                423215, "__init__", "FileReader")
        try:
            self.callback = kwargs["callback"]
        except:
            raise YomboWarning(
                "FileReader requires a callback to send data to.", 654231,
                "__init__", "FileReader")

        self._owner_object = owner_object

        self.fileid = kwargs.get("fileid", self.filename)
        self.make_if_missing = kwargs.get("make_if_missing", True)
        self.frequency = kwargs.get("frequency", 1)
        self._SQLDict = get_component("yombo.lib.sqldict")
        reactor.callLater(0.001, self.start)
Пример #24
0
    def generate_message_response(self,
                                  exchange_name=None,
                                  source=None,
                                  destination=None,
                                  headers=None,
                                  body=None,
                                  routing_key=None,
                                  callback=None,
                                  correlation_id=None,
                                  message_type=None,
                                  response_type=None,
                                  data_type=None,
                                  route=None,
                                  gateway_routing=None,
                                  previous_properties=None,
                                  previous_headers=None):
        if self._Loader.operating_mode != "run":
            return {}
        if previous_properties is None:
            raise YomboWarning(
                "generate_message_response() requires 'previous_properties' argument."
            )
        if previous_headers is None:
            raise YomboWarning(
                "generate_message_response() requires 'previous_headers' argument."
            )

        if message_type is None:
            message_type = "response"

        if "correlation_id" in previous_headers and previous_headers["correlation_id"] is not None and \
            previous_headers["correlation_id"][0:2] != "xx_":
            reply_to = previous_headers["correlation_id"]
            if headers is None:
                headers = {}
            headers["reply_to"] = reply_to

        response_msg = self.generate_message(
            exchange_name=exchange_name,
            source=source,
            destination=destination,
            message_type=message_type,
            data_type=data_type,
            body=body,
            routing_key=routing_key,
            callback=callback,
            correlation_id=correlation_id,
            headers=headers,
            route=route,
            gateway_routing=gateway_routing,
        )
        if response_type is not None:
            response_msg["body"]["headers"]["response_type"] = response_type

        return response_msg
Пример #25
0
 def set(self, key, val):
     if key == 'is_valid':
         raise YomboWarning("Use expire_session() method to expire this session.")
     if key == 'id':
         raise YomboWarning("Cannot change the ID of this session.")
     if key not in ('last_access', 'created_at', 'updated_at'):
         self.data['updated_at'] = int(time())
         self.data[key] = val
         self.is_dirty = 200
         return val
     raise KeyError("Session doesn't have key: %s" % key)
Пример #26
0
 def gateway_get(self, gateway_id, session=None):
     results = yield self.request("GET", "/v1/gateway/%s" % gateway_id,
                                  None, session)
     if results['code'] == 200:
         returnValue(results)
     elif results['code'] == 404:
         raise YomboWarning("Server cannot find requested gateway: %s" %
                            gateway_id)
     else:
         raise YomboWarning("Unknown error: %s" %
                            results['content']['message'])
Пример #27
0
 def gateway_index(self, session=None):
     results = yield self.request("GET", "/v1/gateway", None, session)
     if results['code'] == 200:
         returnValue(results)
     elif results['code'] == 404:
         raise YomboWarning("Server cannot get gateways")
     else:
         if results['content']['message'] == "Invalid Token.":
             raise YomboWarningCredentails(
                 "URI: '%s' requires credentials." %
                 results['content']['response']['uri'])
         raise YomboWarning("Unknown error: %s" % results['content'])
Пример #28
0
    def new(self,
            request_type: str,
            data: dict,
            url_format: Optional[dict] = None) -> dict:
        """
        Uses (POST) to add items to the Yombo Gateway. This can be used to create new commands, devices, etc.

        Some requests, such as the gateway modules, require the ID of the gateway to modify. This is specified
        in the index parameter.

        :param request_type: Request type such as "commands", "variables_data", or "modules".
        :param data: Fields to send to the Yombo API.
        :param url_format: A dictionary to send the format() function for the url.
        :return: Returns a dictionary containing various attributes on success. Raises YomboWarning on failure.
        """
        logger.debug("api::new, data: ({data_type}) - {data}",
                     data_type=type(data),
                     data=data)
        request_details = self.request_data(request_type)
        if "POST" not in request_details["methods"]:
            raise YomboWarning(
                f"'{request_type} cannot perform 'POST' request.")

        if isinstance(url_format, dict) is False:
            url_format = {}

        if "gw_id" in request_details[
                "endpoints"] and "gw_id" not in url_format:
            url_format["gw_id"] = self._gateway_id

        try:
            response = yield self._YomboAPI.request(
                "POST",
                request_details["endpoints"].format(**url_format),
                body=data)
        except YomboWarning as e:
            logger.warn("YomboAPI::InteractionsMixin::New - error: {e}", e=e)
            raise YomboWarning(
                {
                    "title": "Error with API request.",
                    "detail": "Unknown error.",
                },
                component_name="YomboAPI::InteractionsMixin::new",
                component_type="library")

        if response.response_code != 201:
            raise YomboWarning(response.content["errors"])

        return {
            "status": "ok",
            "content": response.content,
            "response": response
        }
Пример #29
0
    def statistic_get_range(self, names, start, stop, minimal=None):
        if isinstance(names, list) is False:
            raise YomboWarning(
                "statistic_get_range: names argument expects a list.")
        if isinstance(start, int) is False and isinstance(start,
                                                          float) is False:
            raise YomboWarning(
                f"statistic_get_range: start argument expects an int or float, got: {start}"
            )
        if isinstance(stop, int) is False and isinstance(stop, float) is False:
            # print("stop is typE: %s" % type(stop))
            raise YomboWarning(
                f"statistic_get_range: stop argument expects an int or float, got: {stop}"
            )

        # names_str = ", ".join(map(str, names))
        names_str = ", ".join(f'"{w}"' for w in names)
        sql = """SELECT id, bucket_time, bucket_size, bucket_lifetime, bucket_type, bucket_name,
     bucket_value, bucket_average_data, anon, uploaded, finished, updated_at 
     FROM  statistics WHERE bucket_name in (%s) AND bucket_time >= %s
            AND bucket_time <= %s
            ORDER BY bucket_time""" % (names_str, start, stop)
        # print("statistic_get_range: %s" % sql)
        records = yield self.db_pool.runQuery(sql)
        results = []
        for record in records:
            if minimal in (None, False):
                results.append({
                    "id": record[0],
                    "bucket_time": record[1],
                    "bucket_size": record[2],
                    "bucket_lifetime": record[3],
                    "bucket_type": record[4],
                    "bucket_name": record[5],
                    "bucket_value": record[6],
                    "bucket_average_data": record[7],
                    "anon": record[8],
                    "uploaded": record[9],
                    "finished": record[10],
                    "updated_at": record[11],
                })
            else:
                results.append({
                    "id": record[0],
                    "bucket_time": record[1],
                    "bucket_size": record[2],
                    "bucket_lifetime": record[3],
                    "bucket_type": record[4],
                    "bucket_name": record[5],
                    "bucket_value": record[6],
                })

        return results
Пример #30
0
    def generate_message_response(self,
                                  exchange_name=None,
                                  source=None,
                                  destination=None,
                                  headers=None,
                                  body=None,
                                  routing_key=None,
                                  callback=None,
                                  correlation_id=None,
                                  message_type=None,
                                  response_type=None,
                                  data_type=None,
                                  previous_properties=None,
                                  previous_headers=None):
        if self._Loader.operating_mode != 'run':
            return
        if previous_properties is None:
            raise YomboWarning(
                "generate_message_response() requires 'previous_properties' argument."
            )
        if previous_headers is None:
            raise YomboWarning(
                "generate_message_response() requires 'previous_headers' argument."
            )

        if message_type is None:
            message_type = "response"

        reply_to = None
        if 'correlation_id' in previous_headers and previous_headers['correlation_id'] is not None and \
            previous_headers['correlation_id'][0:2] != "xx_":
            reply_to = previous_headers['correlation_id']
            if headers is None:
                headers = {}
            headers['reply_to'] = reply_to

        response_msg = self.generate_message(
            exchange_name=exchange_name,
            source=source,
            destination=destination,
            message_type=message_type,
            data_type=data_type,
            body=body,
            routing_key=routing_key,
            callback=callback,
            correlation_id=correlation_id,
            headers=headers,
        )
        if response_type is not None:
            response_msg['body']['headers']['response_type'] = response_type

        return response_msg