beltway2 0.1.0__tar.gz

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.
Files changed (40) hide show
  1. beltway2-0.1.0/.github/workflows/publish_package.yml +36 -0
  2. beltway2-0.1.0/.github/workflows/unit_tests.yml +41 -0
  3. beltway2-0.1.0/.gitignore +187 -0
  4. beltway2-0.1.0/LICENSE +22 -0
  5. beltway2-0.1.0/MANIFEST.in +2 -0
  6. beltway2-0.1.0/PKG-INFO +50 -0
  7. beltway2-0.1.0/README.md +3 -0
  8. beltway2-0.1.0/_ci/is_dev_version.sh +11 -0
  9. beltway2-0.1.0/examples/client_test.py +32 -0
  10. beltway2-0.1.0/examples/server_test.py +54 -0
  11. beltway2-0.1.0/pyproject.toml +27 -0
  12. beltway2-0.1.0/requirements-dev.txt.py +1 -0
  13. beltway2-0.1.0/requirements-test.txt +4 -0
  14. beltway2-0.1.0/requirements.txt +4 -0
  15. beltway2-0.1.0/setup.cfg +4 -0
  16. beltway2-0.1.0/src/beltway2/__init__.py +4 -0
  17. beltway2-0.1.0/src/beltway2/_version.py +24 -0
  18. beltway2-0.1.0/src/beltway2/autolog.py +76 -0
  19. beltway2-0.1.0/src/beltway2/client.py +1667 -0
  20. beltway2-0.1.0/src/beltway2/util.py +850 -0
  21. beltway2-0.1.0/src/beltway2/wamp/__init__.py +0 -0
  22. beltway2-0.1.0/src/beltway2/wamp/auth.py +289 -0
  23. beltway2-0.1.0/src/beltway2/wamp/const.py +87 -0
  24. beltway2-0.1.0/src/beltway2/wamp/exception.py +263 -0
  25. beltway2-0.1.0/src/beltway2/wamp/message.py +3515 -0
  26. beltway2-0.1.0/src/beltway2/wamp/request.py +272 -0
  27. beltway2-0.1.0/src/beltway2/wamp/role.py +266 -0
  28. beltway2-0.1.0/src/beltway2/wamp/serializer.py +314 -0
  29. beltway2-0.1.0/src/beltway2/wamp/types.py +786 -0
  30. beltway2-0.1.0/src/beltway2/wamp/uri.py +321 -0
  31. beltway2-0.1.0/src/beltway2.egg-info/PKG-INFO +50 -0
  32. beltway2-0.1.0/src/beltway2.egg-info/SOURCES.txt +38 -0
  33. beltway2-0.1.0/src/beltway2.egg-info/dependency_links.txt +1 -0
  34. beltway2-0.1.0/src/beltway2.egg-info/requires.txt +16 -0
  35. beltway2-0.1.0/src/beltway2.egg-info/scm_file_list.json +34 -0
  36. beltway2-0.1.0/src/beltway2.egg-info/scm_version.json +8 -0
  37. beltway2-0.1.0/src/beltway2.egg-info/top_level.txt +1 -0
  38. beltway2-0.1.0/tests/__init__.py +0 -0
  39. beltway2-0.1.0/tests/test_template.py +10 -0
  40. beltway2-0.1.0/tox.ini +33 -0
@@ -0,0 +1,36 @@
1
+ name: Publish Python Package
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - '*'
7
+ workflow_dispatch:
8
+
9
+ jobs:
10
+ build-and-publish:
11
+ runs-on: ubuntu-latest
12
+
13
+ steps:
14
+ - name: Check out code
15
+ uses: actions/checkout@v4
16
+ with:
17
+ fetch-depth: 0 # required for setuptools-scm to determine version from tags
18
+
19
+ - name: Set up Python
20
+ uses: actions/setup-python@v4
21
+ with:
22
+ python-version: '3.12'
23
+
24
+ - name: Install dependencies
25
+ run: |
26
+ python -m pip install --upgrade pip
27
+ pip install build twine
28
+
29
+ - name: Build package
30
+ run: python -m build
31
+
32
+ - name: Publish to PyPI
33
+ env:
34
+ TWINE_USERNAME: __token__
35
+ TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }}
36
+ run: python -m twine upload --verbose dist/*
@@ -0,0 +1,41 @@
1
+ name: Unit-tests
2
+
3
+ on: [push]
4
+
5
+ jobs:
6
+ build:
7
+ runs-on: ubuntu-latest
8
+ strategy:
9
+ matrix:
10
+ python-version: ["3.12"]
11
+ steps:
12
+ - uses: actions/checkout@v4
13
+ with:
14
+ fetch-depth: 0
15
+ - name: Set up Python ${{ matrix.python-version }}
16
+ uses: actions/setup-python@v3
17
+ with:
18
+ python-version: ${{ matrix.python-version }}
19
+ - name: Install system dependencies
20
+ run: |
21
+ sudo apt-get update
22
+ sudo apt-get install -y --no-install-recommends portaudio19-dev
23
+ - name: Install dependencies
24
+ run: |
25
+ python -m pip install --upgrade pip
26
+ pip install .[test]
27
+ - name: Run tests
28
+ run: |
29
+ pytest -vv --log-level DEBUG \
30
+ --cov src \
31
+ --cov-branch \
32
+ --cov-report html:reports/pytest_coverage \
33
+ --cov-fail-under 0 \
34
+ --junitxml=reports/junit/test-results-${{ matrix.python-version }}.xml
35
+ - name: Upload pytest test results
36
+ uses: actions/upload-artifact@v4
37
+ with:
38
+ name: reports-${{ matrix.python-version }}
39
+ path: |
40
+ reports
41
+ if: ${{ always() }}
@@ -0,0 +1,187 @@
1
+ # Created by .ignore support plugin (hsz.mobi)
2
+ ### Python template
3
+ # Byte-compiled / optimized / DLL files
4
+ __pycache__/
5
+ *.py[cod]
6
+ *$py.class
7
+
8
+ # C extensions
9
+ *.so
10
+
11
+ # Distribution / packaging
12
+ .Python
13
+ env/
14
+ build/
15
+ develop-eggs/
16
+ dist/
17
+ downloads/
18
+ eggs/
19
+ .eggs/
20
+ lib64/
21
+ parts/
22
+ sdist/
23
+ var/
24
+ *.egg-info/
25
+ .installed.cfg
26
+ *.egg
27
+
28
+ # PyInstaller
29
+ # Usually these files are written by a python script from a template
30
+ # before PyInstaller builds the exe, so as to inject date/other infos into it.
31
+ *.manifest
32
+ *.spec
33
+
34
+ # Installer logs
35
+ pip-log.txt
36
+ pip-delete-this-directory.txt
37
+
38
+ # Unit test / coverage reports
39
+ htmlcov/
40
+ .tox/
41
+ .coverage
42
+ .coverage.*
43
+ .cache
44
+ nosetests.xml
45
+ coverage.xml
46
+ *,cover
47
+ .hypothesis/
48
+
49
+ # Translations
50
+ *.mo
51
+ *.pot
52
+
53
+ # Django stuff:
54
+ *.log
55
+ local_settings.py
56
+
57
+ # Flask stuff:
58
+ instance/
59
+ .webassets-cache
60
+
61
+ # Scrapy stuff:
62
+ .scrapy
63
+
64
+ # Sphinx documentation
65
+ docs/_build/
66
+
67
+ # PyBuilder
68
+ target/
69
+
70
+ # IPython Notebook
71
+ .ipynb_checkpoints
72
+
73
+ # pyenv
74
+ .python-version
75
+
76
+ # celery beat schedule file
77
+ celerybeat-schedule
78
+
79
+ # dotenv
80
+ .env
81
+
82
+ # virtualenv
83
+ venv/
84
+ ENV/
85
+
86
+ # Spyder project settings
87
+ .spyderproject
88
+
89
+ # Rope project settings
90
+ .ropeproject
91
+ ### VirtualEnv template
92
+ # Virtualenv
93
+ # http://iamzed.com/2009/05/07/a-primer-on-virtualenv/
94
+ #[Bb]in
95
+ [Ii]nclude
96
+ [Ll]ib64
97
+ [Ll]ocal
98
+ pyvenv.cfg
99
+ .venv
100
+ pip-selfcheck.json
101
+
102
+ ### JetBrains template
103
+ # Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
104
+ # Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
105
+
106
+ **/.idea/*
107
+ .idea/*
108
+ # User-specific stuff
109
+ .idea/**/workspace.xml
110
+ .idea/**/tasks.xml
111
+ .idea/**/usage.statistics.xml
112
+ .idea/**/dictionaries
113
+ .idea/**/shelf
114
+
115
+ # AWS User-specific
116
+ .idea/**/aws.xml
117
+
118
+ # Generated files
119
+ .idea/**/contentModel.xml
120
+
121
+ # Sensitive or high-churn files
122
+ .idea/**/dataSources/
123
+ .idea/**/dataSources.ids
124
+ .idea/**/dataSources.local.xml
125
+ .idea/**/sqlDataSources.xml
126
+ .idea/**/dynamic.xml
127
+ .idea/**/uiDesigner.xml
128
+ .idea/**/dbnavigator.xml
129
+
130
+ # Gradle
131
+ .idea/**/gradle.xml
132
+ .idea/**/libraries
133
+
134
+ # Gradle and Maven with auto-import
135
+ # When using Gradle or Maven with auto-import, you should exclude module files,
136
+ # since they will be recreated, and may cause churn. Uncomment if using
137
+ # auto-import.
138
+ # .idea/artifacts
139
+ # .idea/compiler.xml
140
+ # .idea/jarRepositories.xml
141
+ # .idea/modules.xml
142
+ # .idea/*.iml
143
+ # .idea/modules
144
+ # *.iml
145
+ # *.ipr
146
+
147
+ # CMake
148
+ cmake-build-*/
149
+
150
+ # Mongo Explorer plugin
151
+ .idea/**/mongoSettings.xml
152
+
153
+ # File-based project format
154
+ *.iws
155
+
156
+ # IntelliJ
157
+ out/
158
+
159
+ # mpeltonen/sbt-idea plugin
160
+ .idea_modules/
161
+
162
+ # JIRA plugin
163
+ atlassian-ide-plugin.xml
164
+
165
+ # Cursive Clojure plugin
166
+ .idea/replstate.xml
167
+
168
+ # SonarLint plugin
169
+ .idea/sonarlint/
170
+
171
+ # Crashlytics plugin (for Android Studio and IntelliJ)
172
+ com_crashlytics_export_strings.xml
173
+ crashlytics.properties
174
+ crashlytics-build.properties
175
+ fabric.properties
176
+
177
+ # Editor-based Rest Client
178
+ .idea/httpRequests
179
+
180
+ # Android studio 3.1+ serialized cache file
181
+ .idea/caches/build_file_checksums.ser
182
+
183
+ #vscode
184
+ .vscode/
185
+
186
+ **/test_reports
187
+ *~
beltway2-0.1.0/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2017 matthewh
4
+ Copyright (c) 2026 int2code
5
+
6
+ Permission is hereby granted, free of charge, to any person obtaining a copy
7
+ of this software and associated documentation files (the "Software"), to deal
8
+ in the Software without restriction, including without limitation the rights
9
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
+ copies of the Software, and to permit persons to whom the Software is
11
+ furnished to do so, subject to the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be included in all
14
+ copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
+ SOFTWARE.
@@ -0,0 +1,2 @@
1
+ include README.md
2
+ recursive-include src/beltway2 *.py
@@ -0,0 +1,50 @@
1
+ Metadata-Version: 2.4
2
+ Name: beltway2
3
+ Version: 0.1.0
4
+ Summary: Beltway2 is a threaded WAMP client (a loose port of Autobahn) and fork of original beltway.
5
+ Maintainer-email: Marcin Tomiczek <marcin.tomiczek@int2code.com>
6
+ License: MIT License
7
+
8
+ Copyright (c) 2017 matthewh
9
+ Copyright (c) 2026 int2code
10
+
11
+ Permission is hereby granted, free of charge, to any person obtaining a copy
12
+ of this software and associated documentation files (the "Software"), to deal
13
+ in the Software without restriction, including without limitation the rights
14
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
15
+ copies of the Software, and to permit persons to whom the Software is
16
+ furnished to do so, subject to the following conditions:
17
+
18
+ The above copyright notice and this permission notice shall be included in all
19
+ copies or substantial portions of the Software.
20
+
21
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
24
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
26
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
27
+ SOFTWARE.
28
+
29
+ Requires-Python: >=3.12
30
+ Description-Content-Type: text/markdown
31
+ License-File: LICENSE
32
+ Requires-Dist: six
33
+ Requires-Dist: structlog
34
+ Requires-Dist: txaio
35
+ Requires-Dist: ws4py
36
+ Provides-Extra: test
37
+ Requires-Dist: coverage; extra == "test"
38
+ Requires-Dist: pytest; extra == "test"
39
+ Requires-Dist: pytest-cov; extra == "test"
40
+ Requires-Dist: pytest-html; extra == "test"
41
+ Provides-Extra: dev
42
+ Requires-Dist: coverage; extra == "dev"
43
+ Requires-Dist: pytest; extra == "dev"
44
+ Requires-Dist: pytest-cov; extra == "dev"
45
+ Requires-Dist: pytest-html; extra == "dev"
46
+ Dynamic: license-file
47
+
48
+ This is a lightly modified fork of beltway, updated to drop Python 2 support and maintain
49
+ compatibility with modern Python and SSL defaults.
50
+ The original project remains credited and unmodified; this fork focuses on bug fixes and modern Python support.
@@ -0,0 +1,3 @@
1
+ This is a lightly modified fork of beltway, updated to drop Python 2 support and maintain
2
+ compatibility with modern Python and SSL defaults.
3
+ The original project remains credited and unmodified; this fork focuses on bug fixes and modern Python support.
@@ -0,0 +1,11 @@
1
+ #!/bin/bash
2
+
3
+ version=$(python -c "from setuptools_scm import get_version; print(get_version())" 2>/dev/null || cat version.txt 2>/dev/null || echo "")
4
+
5
+ if [[ $version =~ ^[0-9]+\.[0-9]+\.[0-9]+\.dev[0-9]+$ ]]; then
6
+ echo "Valid dev version."
7
+ exit 0
8
+ else
9
+ echo "Invalid dev version! Version must have .dev[xx] suffix."
10
+ exit 1
11
+ fi
@@ -0,0 +1,32 @@
1
+ import logging
2
+
3
+ from beltway2.client import WampWebSocketClient
4
+ from beltway2.autolog import log
5
+ from beltway2.wamp.exception import ApplicationError
6
+
7
+ class MyException(Exception):
8
+ pass
9
+
10
+
11
+ if __name__ == '__main__':
12
+ logging.basicConfig(level=logging.DEBUG, format='%(asctime)s,%(msecs)03d %(levelname)-8s [%(name)s] %(threadName)s %(message)s')
13
+
14
+ client = WampWebSocketClient('ws://127.0.0.1:8080/ws', timeout=5)
15
+ client.connect()
16
+ try:
17
+ client.joined_event.wait()
18
+
19
+ client.subscribe(lambda event: print("EVENT: {}".format(event)), 'hello')
20
+ client.define(MyException, 'myexc')
21
+
22
+ print(client.call('service.helloWorld'))
23
+ print()
24
+ print(client.call('service.sleepy', 2))
25
+ print()
26
+ print(client.call('service.twisty'))
27
+ except MyException as x:
28
+ print("THANK YOU, YES, I GOT IT.")
29
+ except Exception as x:
30
+ log.exception("You suck: {}".format(x.__class__))
31
+ finally:
32
+ client.close()
@@ -0,0 +1,54 @@
1
+ import logging
2
+ import time
3
+
4
+ from beltway2.client import WampWebSocketClient
5
+ from beltway2.autolog import log
6
+ from beltway2.wamp.exception import ApplicationError
7
+
8
+
9
+ class MyException(Exception):
10
+ pass
11
+
12
+ if __name__ == '__main__':
13
+ logging.basicConfig(level=logging.DEBUG, format='%(asctime)s,%(msecs)03d %(levelname)-8s [%(name)s] %(threadName)s %(message)s')
14
+
15
+ client = WampWebSocketClient('ws://127.0.0.1:8080/ws', timeout=5)
16
+ client.connect()
17
+
18
+ def hello_world():
19
+ log.info("In hello_worlld()")
20
+ client.publish('hello', "You called?")
21
+ return "Hello World"
22
+
23
+ def sleepy(seconds):
24
+ log.info("In sleepy()")
25
+ time.sleep(seconds)
26
+ raise MyException("Blah")
27
+ return "Slept for {} seconds".format(seconds)
28
+
29
+ def twisty():
30
+ log.info("In twisty()")
31
+ return client.call('service.twisty2')
32
+
33
+ def twisty2():
34
+ log.info("In twisty2()")
35
+ return "Woah. Twisted."
36
+
37
+ client.joined_event.wait(timeout=3.0)
38
+
39
+ client.define(MyException, 'myexc')
40
+
41
+ client.register(hello_world, 'service.helloWorld')
42
+ client.register(sleepy, 'service.sleepy')
43
+ client.register(twisty, 'service.twisty')
44
+ client.register(twisty2, 'service.twisty2')
45
+
46
+ try:
47
+ log.debug("Running forever")
48
+ client.run_forever()
49
+ except KeyboardInterrupt:
50
+ log.debug("CTRL-C")
51
+ client.close()
52
+ except:
53
+ log.exception("Unhandled error.")
54
+ client.close()
@@ -0,0 +1,27 @@
1
+ [project]
2
+ name = "beltway2"
3
+ description = "Beltway2 is a threaded WAMP client (a loose port of Autobahn) and fork of original beltway."
4
+ readme = "README.md"
5
+ requires-python = ">=3.12"
6
+ license = {file = "LICENSE"}
7
+ maintainers = [
8
+ {name = "Marcin Tomiczek", email = "marcin.tomiczek@int2code.com"},
9
+ ]
10
+ dynamic = ["dependencies", "version", "optional-dependencies"]
11
+
12
+ [tool.setuptools.packages.find]
13
+ where = ["src"]
14
+
15
+ [tool.setuptools.dynamic]
16
+ dependencies = {file = ["requirements.txt"]}
17
+
18
+ [tool.setuptools_scm]
19
+ write_to = "src/beltway2/_version.py"
20
+
21
+ [tool.setuptools.dynamic.optional-dependencies]
22
+ test = {file = ["requirements-test.txt"]}
23
+ dev = {file = ["requirements-test.txt", "requirements-dev.txt"]}
24
+
25
+ [build-system]
26
+ requires = ["setuptools>=64", "setuptools-scm>=8"]
27
+ build-backend = "setuptools.build_meta"
@@ -0,0 +1 @@
1
+ tox
@@ -0,0 +1,4 @@
1
+ coverage
2
+ pytest
3
+ pytest-cov
4
+ pytest-html
@@ -0,0 +1,4 @@
1
+ six
2
+ structlog
3
+ txaio
4
+ ws4py
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,4 @@
1
+ try:
2
+ from ._version import version as __version__
3
+ except ImportError:
4
+ __version__ = "unknown"
@@ -0,0 +1,24 @@
1
+ # file generated by vcs-versioning
2
+ # don't change, don't track in version control
3
+ from __future__ import annotations
4
+
5
+ __all__ = [
6
+ "__version__",
7
+ "__version_tuple__",
8
+ "version",
9
+ "version_tuple",
10
+ "__commit_id__",
11
+ "commit_id",
12
+ ]
13
+
14
+ version: str
15
+ __version__: str
16
+ __version_tuple__: tuple[int | str, ...]
17
+ version_tuple: tuple[int | str, ...]
18
+ commit_id: str | None
19
+ __commit_id__: str | None
20
+
21
+ __version__ = version = '0.1.0'
22
+ __version_tuple__ = version_tuple = (0, 1, 0)
23
+
24
+ __commit_id__ = commit_id = 'g5ae20c00a'
@@ -0,0 +1,76 @@
1
+ import inspect
2
+
3
+ import structlog
4
+
5
+
6
+ class AutoLogger:
7
+ """
8
+ A logger proxy object, with all of the methods and attributes of C{Logger}.
9
+
10
+ When an attribute (e.g., "debug") is requested, inspects the stack for the
11
+ calling module's name, and passes that name to C{logging.getLogger}.
12
+
13
+ What this means is that you can instantiate an C{AutoLogger} anywhere, and
14
+ when you call it, the log entry shows the module where you called it, not
15
+ where it was created.
16
+
17
+ C{AutoLogger} also inspects the local variables where it is called, looking
18
+ for C{self}. If C{self} exists, its classname is added to the module name.
19
+ """
20
+
21
+ def __init__(self):
22
+ pass
23
+
24
+ def __getattr__(self, name):
25
+
26
+ stack = inspect.stack()
27
+ frame = stack[1][0] if len(stack) > 1 else stack[0][0]
28
+
29
+ try:
30
+ # print("-" * 80)
31
+ # print(inspect.stack()[1])
32
+ # print("-" * 80)
33
+
34
+ if 'self' in frame.f_locals:
35
+ other = frame.f_locals['self']
36
+ caller_name = '%s.%s' % (other.__class__.__module__, other.__class__.__name__)
37
+ else:
38
+ caller_name = frame.f_globals['__name__']
39
+
40
+ logger = structlog.get_logger(caller_name)
41
+
42
+ return getattr(logger, name)
43
+ finally:
44
+ # See https://docs.python.org/3/library/inspect.html#the-interpreter-stack
45
+ del frame
46
+
47
+
48
+ log = AutoLogger()
49
+
50
+
51
+ def log_exceptions(fn):
52
+ """ A decorator designed to wrap a function and log any exception that method produces.
53
+
54
+ The exception will still be raised after being logged.
55
+
56
+ Also logs (at the trace level) the arguments to every call.
57
+
58
+ Currently this is only designed for module-level functions. Not sure what happens if a method is decorated
59
+ with this (since logger is resolved from module name).
60
+ """
61
+
62
+ def wrapper(*args, **kwargs):
63
+ try:
64
+ a = args or []
65
+ a = [str(x)[:255] for x in a]
66
+ kw = kwargs or {}
67
+ kw = dict([(str(k)[:255], str(v)[:255]) for k, v in kw.items()])
68
+ log.debug('Calling %s.%s %r %r' % (fn.__module__, fn.__name__, a, kw))
69
+ return fn(*args, **kwargs)
70
+ except Exception as e:
71
+ log.error("Error calling function %s: %s" % (fn.__name__, e))
72
+ log.exception(e)
73
+ raise
74
+
75
+ wrapper.__name__ = fn.__name__
76
+ return wrapper