~kf5jwc/sms-printer

3c4fb7d54d35b8c82904b54c5b6563d831f2eb43 — Kyle Jones 1 year, 8 months ago 72a9603
Split out the broker parsers library
12 files changed, 4 insertions(+), 309 deletions(-)

M Pipfile
M Pipfile.lock
D requirements.txt
D setup.cfg
D setup.py
D sms_broker_parsers/__init__.py
D sms_broker_parsers/errors.py
D sms_broker_parsers/parsers/__init__.py
D sms_broker_parsers/parsers/json/__init__.py
D sms_broker_parsers/parsers/json/bandwidth_v1.py
D sms_broker_parsers/parsers/json/bandwidth_v2.py
D sms_broker_parsers/types.py
M Pipfile => Pipfile +2 -3
@@ 4,11 4,10 @@ url = "https://pypi.python.org/simple"
name = "pypi"

[packages]
#sms_broker_parsers = { version="*", path="../sms_broker_parsers/" }
gunicorn = "*"
flask = "*"
pyxdg = "*"
jsonschema = "*"
automodinit = "*"

[dev-packages]
isort = "*"


@@ 18,7 17,7 @@ mypy = "*"
pre-commit = "*"

[requires]
python_version = "3.6"
python_version = "3.7"

[pipenv]
allow_prereleases = true

M Pipfile.lock => Pipfile.lock +2 -16
@@ 1,11 1,11 @@
{
    "_meta": {
        "hash": {
            "sha256": "e46f00be7060c1fda7945e0aa8f2bf8b3c4c18b680413676ae6e5dccf288c94d"
            "sha256": "90c9919f6c319f632e1e4e61e56c59a850a427ba4fa9e0a5d654670fac249682"
        },
        "pipfile-spec": 6,
        "requires": {
            "python_version": "3.6"
            "python_version": "3.7"
        },
        "sources": [
            {


@@ 16,20 16,6 @@
        ]
    },
    "default": {
        "attrs": {
            "hashes": [
                "sha256:10cbf6e27dbce8c30807caf056c8eb50917e0eaafe86347671b57254006c3e69",
                "sha256:ca4be454458f9dec299268d472aaa5a11f67a4ff70093396e1ceae9c76cf4bbb"
            ],
            "version": "==18.2.0"
        },
        "automodinit": {
            "hashes": [
                "sha256:fc0d340865be7378fe591c8db162609dea713cfef2e25d9b15836f4cd690c831"
            ],
            "index": "pypi",
            "version": "==0.16"
        },
        "click": {
            "hashes": [
                "sha256:2335065e6395b9e67ca716de5f7526736bfa6ceead690adf616d925bdc622b13",

D requirements.txt => requirements.txt +0 -3
@@ 1,3 0,0 @@
-i https://pypi.python.org/simple
automodinit==0.16
jsonschema==2.6.0

D setup.cfg => setup.cfg +0 -9
@@ 1,9 0,0 @@
[metadata]
name = sms-broker-parsers
author = Kyle Jones
author-email = kyle@kf5jwc.us
license = GPL-2

[files]
packages =
	sms_broker_parsers

D setup.py => setup.py +0 -8
@@ 1,8 0,0 @@
#!/usr/bin/env python

from setuptools import setup

setup(
    setup_requires=['pbr>=1.9', 'setuptools>=17.1'],
    pbr=True,
)

D sms_broker_parsers/__init__.py => sms_broker_parsers/__init__.py +0 -2
@@ 1,2 0,0 @@
from . import errors, types
from .parsers import get_mimetype_parser

D sms_broker_parsers/errors.py => sms_broker_parsers/errors.py +0 -10
@@ 1,10 0,0 @@
class NoMatchingSchema(Exception):
    """We weren't able to find a parser for this message"""

    pass


class NoParserForMimeType(Exception):
    """We weren't able to find a parser for the type matching this header"""

    pass

D sms_broker_parsers/parsers/__init__.py => sms_broker_parsers/parsers/__init__.py +0 -30
@@ 1,30 0,0 @@
import automodinit
import logging
from typing import Any, Callable, Dict, List
from .. import types
from .. import errors

MIMETYPES: Dict[str, Any] = {}
__all__: List[str] = []


def get_mimetype_parser(mimetype) -> Callable[[str], List[types.sms]]:
    """Get the class of parser, so that we can ask it for the actual parser"""
    try:
        return MIMETYPES[mimetype]

    except KeyError:
        raise errors.NoParserForMimeType


# This sets our __all__ instead of returning a list
automodinit.automodinit(__name__, __file__, globals())

for module_name in __all__:
    mod = globals()[module_name]
    for mimetype in getattr(mod, "MIMETYPES"):
        MIMETYPES[mimetype] = getattr(mod, "parse_sms")

del automodinit

__all__ = ["get_mimetype_parser"]

D sms_broker_parsers/parsers/json/__init__.py => sms_broker_parsers/parsers/json/__init__.py +0 -47
@@ 1,47 0,0 @@
"""
This module provides the filtering and parsing. They currently only
provide a list of messages with the sender number and message text.

Each module has a `SCHEMA` and a `Parser(Parser_Base)`

The `SCHEMA` is matched against incoming messages to determine the
correct parser to use for a message.

Adding more is simple enough. Define a new parser and its schema in
its own module next to the others, and add it to `SCHEMAS` here.

I use `genson` to generate schemas based on an example message.
"""


import automodinit
from typing import Dict, Type
from jsonschema import validate
from jsonschema.exceptions import ValidationError
import json
from ...errors import NoMatchingSchema
from ...types import Parser_Base

MIMETYPES = ["application/json", "application/json+xml"]
SCHEMAS: Dict[str, Type[Parser_Base]] = {}
__all__ = []

automodinit.automodinit(__name__, __file__, globals())
del automodinit
for module_name in __all__:
    mod = globals()[module_name]
    SCHEMAS[getattr(mod, "SCHEMA")] = getattr(mod, "Parser")


def parse_sms(sms_input: str) -> Type[Parser_Base]:
    for schema, sms_parser in SCHEMAS.items():
        try:
            validate(sms_input, json.loads(schema))
            return sms_parser(sms_input)
        except ValidationError:
            continue

    raise NoMatchingSchema


__all__ = ["MIMETYPES", "parse_sms"]

D sms_broker_parsers/parsers/json/bandwidth_v1.py => sms_broker_parsers/parsers/json/bandwidth_v1.py +0 -61
@@ 1,61 0,0 @@
from typing import Dict
from ...types import Parser_Base, sms


class Parser(Parser_Base):
    def __init__(self, sms_input: Dict) -> None:
        super().__init__()
        msg = sms(sms_input["from"], sms_input["text"])
        self.messages.append(msg)


SCHEMA = """
{
   "type" : "object",
   "properties" : {
      "direction" : {
         "type" : "string"
      },
      "from" : {
         "type" : "string"
      },
      "state" : {
         "type" : "string"
      },
      "messageUri" : {
         "type" : "string"
      },
      "applicationId" : {
         "type" : "string"
      },
      "text" : {
         "type" : "string"
      },
      "time" : {
         "type" : "string"
      },
      "messageId" : {
         "type" : "string"
      },
      "to" : {
         "type" : "string"
      },
      "eventType" : {
         "type" : "string"
      }
   },
   "required" : [
      "applicationId",
      "direction",
      "eventType",
      "from",
      "messageId",
      "messageUri",
      "state",
      "text",
      "time",
      "to"
   ],
   "$schema" : "http://json-schema.org/schema#"
}
"""

D sms_broker_parsers/parsers/json/bandwidth_v2.py => sms_broker_parsers/parsers/json/bandwidth_v2.py +0 -95
@@ 1,95 0,0 @@
from typing import List
from ...types import Parser_Base, sms


class Parser(Parser_Base):
    def __init__(self, sms_json: List) -> None:
        super().__init__()
        for msg_input in sms_json:
            msg = sms(msg_input["message"]["from"], msg_input["message"]["text"])
            self.messages.append(msg)


SCHEMA = """
{
   "items" : {
      "properties" : {
         "time" : {
            "type" : "string"
         },
         "message" : {
            "properties" : {
               "time" : {
                  "type" : "string"
               },
               "direction" : {
                  "type" : "string"
               },
               "owner" : {
                  "type" : "string"
               },
               "media" : {
                  "items" : {
                     "type" : "string"
                  },
                  "type" : "array"
               },
               "applicationId" : {
                  "type" : "string"
               },
               "id" : {
                  "type" : "string"
               },
               "segmentCount" : {
                  "type" : "integer"
               },
               "text" : {
                  "type" : "string"
               },
               "from" : {
                  "type" : "string"
               },
               "to" : {
                  "type" : "array",
                  "items" : {
                     "type" : "string"
                  }
               }
            },
            "required" : [
               "applicationId",
               "direction",
               "from",
               "id",
               "media",
               "owner",
               "segmentCount",
               "text",
               "time",
               "to"
            ],
            "type" : "object"
         },
         "to" : {
            "type" : "string"
         },
         "description" : {
            "type" : "string"
         },
         "type" : {
            "type" : "string"
         }
      },
      "type" : "object",
      "required" : [
         "description",
         "message",
         "time",
         "to",
         "type"
      ]
   },
   "$schema" : "http://json-schema.org/schema#",
   "type" : "array"
}
"""

D sms_broker_parsers/types.py => sms_broker_parsers/types.py +0 -25
@@ 1,25 0,0 @@
from typing import List


class sms(object):
    sender: str = ""
    text: str = ""

    def __init__(self, sender: str, text: str) -> None:
        self.sender = sender
        self.text = text


class Parser_Base(object):
    messages: List[sms] = []

    def __init__(self):
        self.messages = []

    def __iter__(self):
        return self

    def __next__(self) -> sms:
        if not bool(self.messages):
            raise StopIteration
        return self.messages.pop(0)