morp 7.0.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
morp/__init__.py ADDED
@@ -0,0 +1,23 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ from .config import (
4
+ DsnConnection,
5
+ Connection,
6
+ )
7
+ from .interface import (
8
+ get_interface,
9
+ set_interface,
10
+ get_interfaces,
11
+ configure_environ,
12
+ find_environ,
13
+ )
14
+ from .message import Message
15
+ from .exception import (
16
+ InterfaceError,
17
+ ReleaseMessage,
18
+ AckMessage,
19
+ )
20
+
21
+
22
+ __version__ = "7.0.0"
23
+
morp/compat.py ADDED
@@ -0,0 +1,4 @@
1
+ # -*- coding: utf-8 -*-
2
+ from datatypes.compat import *
3
+ from datatypes import String, ByteString
4
+
morp/config.py ADDED
@@ -0,0 +1,173 @@
1
+ # -*- coding: utf-8 -*-
2
+ import base64
3
+
4
+ import dsnparse
5
+ from datatypes import ReflectName
6
+ from datatypes import property as cachedproperty
7
+ from datatypes.config import Environ
8
+
9
+
10
+ from .compat import *
11
+
12
+
13
+ class Connection(object):
14
+ """The base connection class, you will most likely always use DsnConnection
15
+ """
16
+ name = ""
17
+ """str, the name of this connection, handy when you have more than one used
18
+ interface (eg, nsq)"""
19
+
20
+ username = ""
21
+ """str, the username (if needed)"""
22
+
23
+ password = ""
24
+ """str, the password (if needed)"""
25
+
26
+ hosts = None
27
+ """list, a list of (hostname, port) tuples"""
28
+
29
+ path = None
30
+ """str, the path (if applicable)"""
31
+
32
+ interface_name = ""
33
+ """str, full Interface class name -- the interface that will connect to the
34
+ messaging backend"""
35
+
36
+ options = None
37
+ """dict, any other interface specific options you need"""
38
+
39
+ @property
40
+ def interface_class(self):
41
+ interface_class = ReflectName(self.interface_name).get_class()
42
+ return interface_class
43
+
44
+ @property
45
+ def interface(self):
46
+ interface_class = self.interface_class
47
+ return interface_class(self)
48
+
49
+ @property
50
+ def serializer(self):
51
+ serializer = self.options.get("serializer", "pickle")
52
+ if serializer not in set(["pickle", "json"]):
53
+ raise ValueError(f"Unknown serializer {serializer}")
54
+ return serializer
55
+
56
+ @cachedproperty(cached="_key")
57
+ def key(self):
58
+ """string -- an encryption key loaded from options['key'],
59
+ it must be 32 bytes long so this makes sure it is"""
60
+ key = self.options.get('key', "")
61
+ if key:
62
+ # Fernet key must be 32 url-safe base64-encoded bytes
63
+ bs = ByteString(ByteString(key).sha256())
64
+ key = base64.b64encode(bs[:32])
65
+ return key
66
+
67
+ def __init__(self, **kwargs):
68
+ """set all the values by passing them into this constructor, any
69
+ unrecognized kwargs get put into .options
70
+
71
+ :example:
72
+ c = Connection(
73
+ interface_name="...",
74
+ hosts=[("host", port), ("host2", port2)],
75
+ some_random_thing="foo"
76
+ )
77
+
78
+ print c.port # 5000
79
+ print c.options # {"some_random_thing": "foo"}
80
+ """
81
+ self.options = kwargs.pop('options', {})
82
+ self.hosts = []
83
+
84
+ for key, val in kwargs.items():
85
+ if hasattr(self, key):
86
+ setattr(self, key, val)
87
+
88
+ else:
89
+ self.options[key] = val
90
+
91
+ # 1 hour to process message
92
+ self.options.setdefault('max_timeout', 3600)
93
+
94
+ # failure backoff multiplier
95
+ self.options.setdefault('backoff_multiplier', 5)
96
+
97
+ def get_netlocs(self, default_port):
98
+ return [
99
+ "{}:{}".format(
100
+ h[0],
101
+ default_port if h[1] is None else h[1]
102
+ ) for h in self.hosts
103
+ ]
104
+
105
+ def get_option(self, key, default_val):
106
+ return getattr(self.options, key, default_val)
107
+
108
+
109
+ class DsnConnection(Connection):
110
+ """
111
+ Create a connection object from a dsn in the form
112
+
113
+ InterfaceName://username:password@host:port?opt1=val1#connection_name
114
+
115
+ example -- connect to amazon SQS
116
+
117
+ morp.interface.sqs.SQS://AWS_ID:AWS_KEY@
118
+ """
119
+ def __init__(self, dsn):
120
+ self.dsn = dsn
121
+ d = self.parse(dsn)
122
+ super().__init__(**d)
123
+
124
+ def parse(self, dsn):
125
+ d = {'options': {}, 'hosts': []}
126
+ parser = dsnparse.parse(dsn)
127
+ p = parser.fields
128
+ d["dsn"] = parser.parser.dsn
129
+
130
+ # get the scheme, which is actually our interface_name
131
+ d['interface_name'] = self.normalize_scheme(p["scheme"])
132
+
133
+ dsn_hosts = []
134
+ if p["hostname"]:
135
+ d['hosts'].append((p["hostname"], p.get('port', None)))
136
+
137
+ d['options'] = p["query_params"] or {}
138
+ d['name'] = p["fragment"]
139
+ d['username'] = p["username"]
140
+ d['password'] = p["password"]
141
+ d["path"] = p["path"]
142
+
143
+ return d
144
+
145
+ def normalize_scheme(self, v):
146
+ ret = v
147
+ d = {
148
+ "morp.interface.sqs:SQS": set(["sqs"]),
149
+ "morp.interface.dropfile:Dropfile": set(["dropfile", "file"]),
150
+ "morp.interface.postgres:Postgres": set([
151
+ "postgres",
152
+ "postgresql",
153
+ "psql",
154
+ ]),
155
+ }
156
+
157
+ kv = v.lower()
158
+ for interface_name, vals in d.items():
159
+ if kv in vals:
160
+ ret = interface_name
161
+ break
162
+
163
+ return ret
164
+
165
+
166
+ environ = Environ("MORP_") # Load any MORP_* environment variables
167
+
168
+ environ.setdefault('DISABLED', False, type=bool)
169
+ """If set to 1 then messages won't actually be sent"""
170
+
171
+ environ.setdefault('PREFIX', '')
172
+ """This prefix will be used to create the queue, see Message.get_name()"""
173
+
morp/exception.py ADDED
@@ -0,0 +1,35 @@
1
+ # -*- coding: utf-8 -*-
2
+ from __future__ import unicode_literals, division, print_function, absolute_import
3
+
4
+
5
+ class Error(Exception):
6
+ """error wrapper"""
7
+ def __init__(self, e, exc_info=None):
8
+ self.e = e
9
+ self.exc_info = exc_info
10
+ super(Error, self).__init__(str(e))
11
+
12
+
13
+ class InterfaceError(Error):
14
+ """specifically for wrapping interface errors"""
15
+ pass
16
+
17
+
18
+ class ReleaseMessage(InterfaceError):
19
+ """Can be raised anytime in a recv() with block or target() method to get the
20
+ message to be released
21
+
22
+ :param delay_seconds: int, how long until message is released to be processed
23
+ again, the visibility timeout
24
+ """
25
+ def __init__(self, delay_seconds=0):
26
+ super(ReleaseMessage, self).__init__("")
27
+ self.delay_seconds = delay_seconds
28
+
29
+
30
+ class AckMessage(InterfaceError):
31
+ """Can be raised anytime in a recv() with block or target() method to get the
32
+ message to be acknowledged"""
33
+ def __init__(self):
34
+ super(AckMessage, self).__init__("")
35
+
@@ -0,0 +1,81 @@
1
+ # -*- coding: utf-8 -*-
2
+
3
+ import dsnparse
4
+
5
+ from ..compat import *
6
+ from ..config import DsnConnection
7
+
8
+
9
+ interfaces = {}
10
+ """holds all configured interfaces"""
11
+
12
+
13
+ def get_interfaces():
14
+ global interfaces
15
+ if not interfaces:
16
+ configure_environ()
17
+ return interfaces
18
+
19
+
20
+ def get_interface(connection_name=""):
21
+ """get the configured interface that corresponds to connection_name"""
22
+ global interfaces
23
+ if not interfaces:
24
+ configure_environ()
25
+ return interfaces[connection_name]
26
+
27
+
28
+ def set_interface(interface, connection_name=""):
29
+ """bind an .interface.Interface() instance to connection_name"""
30
+ global interfaces
31
+ interfaces[connection_name] = interface
32
+
33
+
34
+ def find_environ(dsn_env_name='MORP_DSN', connection_class=DsnConnection):
35
+ """Returns Connection instances found in the environment
36
+
37
+ configure interfaces based on environment variables
38
+
39
+ by default, when morp is imported, it will look for MORP_DSN, and MORP_DSN_N
40
+ (where N is 1 through infinity) in the environment, if it finds them, it
41
+ will assume they are dsn urls that morp understands and will configure
42
+ connections with them. If you don't want this behavior (ie, you want to
43
+ configure morp manually) then just make sure you don't have any environment
44
+ variables with matching names
45
+
46
+ The num checks (eg MORP_DSN_1, MORP_DSN_2) go in order, so you can't do
47
+ MORP_DSN_1, MORP_DSN_3, because it will fail on _2 and move on, so make sure
48
+ your num dsns are in order (eg, 1, 2, 3, ...)
49
+
50
+ :param dsn_env_name: str, the environment variable name to find the DSN,
51
+ this will aslo check for values of <dsn_env_name>_N where N is 1 -> N,
52
+ so you can configure multiple DSNs in the environment and this will pick
53
+ them all up
54
+ :param connection_class: Connection, the class that will receive the dsn
55
+ values
56
+ :returns: generator<connection_class>
57
+ """
58
+ return dsnparse.parse_environs(dsn_env_name, parse_class=connection_class)
59
+
60
+
61
+ def configure_environ():
62
+ """auto hook to configure the environment"""
63
+ for c in find_environ():
64
+ inter = c.interface
65
+ set_interface(c.interface, c.name)
66
+
67
+
68
+ def configure(dsn, connection_class=DsnConnection):
69
+ """
70
+ configure an interface to be used to send messages to a backend
71
+
72
+ you use this function to configure an Interface using a dsn, then you can
73
+ get that interface using the get_interface() method
74
+
75
+ :param dsn: string, a properly formatted morp dsn, see DsnConnection for how
76
+ to format the dsn
77
+ """
78
+ #global interfaces
79
+ c = dsnparse.parse(dsn, parse_class=connection_class)
80
+ set_interface(c.interface, c.name)
81
+