Skip to content

Commit

Permalink
Merge pull request #10 from hybridcluster/python3-1
Browse files Browse the repository at this point in the history
Python 3.3 support.

Fixes #1.
  • Loading branch information
itamarst committed Apr 24, 2014
2 parents 5e1a22f + 69f326d commit aa550bd
Show file tree
Hide file tree
Showing 24 changed files with 157 additions and 57 deletions.
13 changes: 11 additions & 2 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,15 +1,24 @@
language: python

env:
- TWISTED=Twisted RUNTESTS=trial

python:
- 2.7
- pypy

matrix:
include:
- python: 3.3
env: TWISTED=git+https://github.com/twisted/twisted.git RUNTESTS="python -m unittest discover"


install:
- python setup.py --version
- pip install -q -r requirements-dev.txt
- pip install -q $TWISTED --use-mirrors
- python setup.py -q install

script: trial eliot.tests
script: $RUNTESTS eliot.tests

notifications:
email: false
1 change: 1 addition & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ Features:
* Excellent support for unit testing your logging code.
* Twisted support.
* JSON output, usable by Logstash/Elasticsearch.
* Supports Python 2.7 and 3.3.

This is a **PREVIEW** and does not guarantee version stability across versions.

Expand Down
1 change: 1 addition & 0 deletions docs/source/index.rst
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ Contents:
fields
threads
development
news
:maxdepth: 2


Expand Down
10 changes: 10 additions & 0 deletions docs/source/news.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
What's New
==========

0.4.0
^^^^^

Features:

* Added support for Python 3.3.

2 changes: 2 additions & 0 deletions eliot/_action.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
from uuid import uuid4
from itertools import count

from six import text_type as unicode

try:
from twisted.python.failure import Failure
except ImportError:
Expand Down
2 changes: 1 addition & 1 deletion eliot/_message.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,4 +116,4 @@ def write(self, logger, action=None):
# The default Action to use as a context for messages, if no other Action is the
# context. This ensures all messages have a unique identity, as specified by
# task_uuid/task_level/action_counter.
_defaultAction = Action(None, unicode(uuid4()), "/", "eliot:default")
_defaultAction = Action(None, u"%s" % (uuid4(),), "/", "eliot:default")
27 changes: 18 additions & 9 deletions eliot/_output.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,25 @@

from __future__ import unicode_literals

try:
# ujson is pretty crappy... but much faster than built-in json module, at
# least on CPython. So we use it until we come up with some better. We
# import built-in module for use by the validation code path, since want
# to validate messages encode in all JSON encoders.
import ujson as json
import json as pyjson
except ImportError:
import json
from six import text_type as unicode, PY3
if PY3:
from . import _py3json as json
pyjson = json
else:
try:
# ujson has some issues, in particular it is far too lenient on bad
# inputs... but on the other hand it's much faster than built-in
# json module on CPython. So we use it until we come up with some
# better. We import built-in module for use by the validation code
# path, since we want to validate messages encode in all JSON
# encoders.
import ujson as json
import json as pyjson
except ImportError:
import json
pyjson = json



from zope.interface import Interface, implementer

Expand Down
34 changes: 34 additions & 0 deletions eliot/_py3json.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
"""
Python 3 JSON encoding/decoding, emulating Python 2's json module.
Python 3 json module doesn't support decoding bytes or encoding. Rather than
adding isinstance checks in main code path which would slow down Python 2,
instead we write our encoder that can support those.
"""

import json as pyjson


class JSONEncoder(pyjson.JSONEncoder):
"""
JSON encoder that supports L{bytes}.
"""
def default(self, o):
if isinstance(o, bytes):
return o.decode("utf-8")
return pyjson.JSONEncoder.default(self, o)

_encoder = JSONEncoder()



def loads(s):
if isinstance(s, bytes):
s = s.decode("utf-8")
return pyjson.loads(s)



def dumps(obj):
return _encoder.encode(obj).encode("utf-8")

5 changes: 4 additions & 1 deletion eliot/_traceback.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
import traceback
import sys

from six import exec_

from .serializers import identity
from ._util import safeunicode
from ._validation import MessageType, Field
Expand Down Expand Up @@ -53,7 +55,8 @@ def _get_traceback_no_io():
path = traceback.__file__
if path.endswith(".pyc") or path.endswith(".pyo"):
path = path[:-1]
execfile(path, module.__dict__, module.__dict__)
with open(path) as f:
exec_(f.read(), module.__dict__, module.__dict__)
class FakeLineCache(object):
def checkcache(self, *args, **kwargs):
None
Expand Down
2 changes: 2 additions & 0 deletions eliot/_util.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@

from __future__ import unicode_literals

from six import text_type as unicode


def safeunicode(o):
"""
Expand Down
14 changes: 8 additions & 6 deletions eliot/_validation.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,13 @@

from __future__ import unicode_literals

import types
from collections import namedtuple

from eliot._message import Message
from eliot._action import startAction, startTask
import six
unicode = six.text_type

from ._message import Message
from ._action import startAction, startTask


class ValidationError(Exception):
Expand All @@ -21,8 +23,8 @@ class ValidationError(Exception):


# Types that can be encoded to JSON:
_JSON_TYPES = {types.NoneType, int, long, float, unicode, list, dict, bytes, bool}

_JSON_TYPES = {type(None), int, float, unicode, list, dict, bytes, bool}
_JSON_TYPES |= set(six.integer_types)

class Field(object):
"""
Expand Down Expand Up @@ -128,7 +130,7 @@ def forTypes(klass, key, classes, description, extraValidator=None):
fixedClasses = []
for k in classes:
if k is None:
k = types.NoneType
k = type(None)
if k not in _JSON_TYPES:
raise TypeError("%s is not JSON-encodeable" % (k,))
fixedClasses.append(k)
Expand Down
18 changes: 12 additions & 6 deletions eliot/filter.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,24 @@
Command line program for filtering line-based Eliot logs.
"""

from __future__ import unicode_literals
from __future__ import unicode_literals, absolute_import

if __name__ == '__main__':
import eliot.filter
eliot.filter.main()


import sys
import json
from datetime import datetime, timedelta

from eliot import tai64n
from six import PY3

from . import tai64n

if PY3:
from . import _py3json as json
else:
import json


class _JSONEncoder(json.JSONEncoder):
Expand Down Expand Up @@ -57,15 +63,15 @@ def __init__(self, expr, incoming, output):

def run(self):
"""
For each incoming message, decode the JSON, evaluate expression, encode as
JSON and write that to the output file.
For each incoming message, decode the JSON, evaluate expression, encode
as JSON and write that to the output file.
"""
for line in self.incoming:
message = json.loads(line)
result = self._evaluate(message)
if result is self._SKIP:
continue
self.output.write(_encoder.encode(result) + b"\n")
self.output.write(_encoder.encode(result).encode("utf-8") + b"\n")


def _evaluate(self, message):
Expand Down
2 changes: 1 addition & 1 deletion eliot/logwriter.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
else:
from twisted.internet.selectreactor import SelectReactor as Reactor

from eliot import addDestination, removeDestination
from . import addDestination, removeDestination


class ThreadedFileWriter(Service):
Expand Down
3 changes: 2 additions & 1 deletion eliot/tai64n.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,8 @@ def encode(timestamp):
seconds = int(timestamp)
nanoseconds = int((timestamp - seconds) * 1000000000)
seconds = seconds + _OFFSET
return "@" + b2a_hex(struct.pack(_STRUCTURE, seconds, nanoseconds))
encoded = b2a_hex(struct.pack(_STRUCTURE, seconds, nanoseconds))
return "@" + encoded.decode("ascii")



Expand Down
10 changes: 7 additions & 3 deletions eliot/tests/test_action.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,8 @@
from unittest import TestCase, SkipTest
from threading import Thread

from six import text_type as unicode

try:
import twisted
from twisted.internet.defer import Deferred
Expand Down Expand Up @@ -454,7 +456,8 @@ def test_withLogsExceptionMessage(self):
"action_type": "sys:me",
"action_status": "failed",
"reason": "because",
"exception": "exceptions.RuntimeError"})
"exception": "%s.RuntimeError" % (
RuntimeError.__module__,)})


def test_withReturnValue(self):
Expand Down Expand Up @@ -537,7 +540,7 @@ def test_incrementMessageCounter(self):
"""
action = Action(MemoryLogger(), "uuid", "/1/", "sys:me")
self.assertEqual([action._incrementMessageCounter() for i in range(5)],
range(5))
list(range(5)))



Expand Down Expand Up @@ -616,7 +619,8 @@ def test_finishAfterFailure(self):
"action_status": "failed",
"x": 2,
"reason": "because",
"exception": "exceptions.RuntimeError"})
"exception":
"%s.RuntimeError" % (RuntimeError.__module__,)})
d.addErrback(lambda _: None) # don't let Failure go to Twisted logs


Expand Down
19 changes: 12 additions & 7 deletions eliot/tests/test_filter.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,21 @@
"""
from __future__ import unicode_literals

from six import PY3

import sys
import time
import json
if PY3:
from .. import _py3json as json
else:
import json
from unittest import TestCase
from datetime import datetime
from io import BytesIO
import inspect


from eliot.filter import EliotFilter, main, USAGE
from eliot import tai64n
from ..filter import EliotFilter, main, USAGE
from .. import tai64n


class EliotFilterTests(TestCase):
Expand All @@ -31,8 +36,8 @@ def test_expression(self):
self.assertEqual(f.getvalue(), b"")
efilter.run()
self.assertEqual(f.getvalue(),
b'{"x": 4, "orig": "abcd"}\n'
b'{"x": 2, "orig": [1, 2]}\n')
json.dumps({"x": 4, "orig": "abcd"}) + b"\n" +
json.dumps({"x": 2, "orig": [1, 2]}) + b'\n')


def evaluateExpression(self, expr, message):
Expand Down Expand Up @@ -122,7 +127,7 @@ def test_default(self):
"""
By default L{main} uses information from L{sys}.
"""
self.assertEqual(main.func_defaults, (sys,))
self.assertEqual(inspect.getargspec(main).defaults, (sys,))


def test_stdinOut(self):
Expand Down
4 changes: 2 additions & 2 deletions eliot/tests/test_logwriter.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@
else:
# Make sure we always import this if Twisted is available, so broken
# logwriter.py causes a failure:
from eliot.logwriter import ThreadedFileWriter
from ..logwriter import ThreadedFileWriter

from eliot import Logger, removeDestination
from .. import Logger, removeDestination


class BlockingFile(object):
Expand Down
2 changes: 1 addition & 1 deletion eliot/tests/test_message.py
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,7 @@ def test_actionCounter(self):
# We expect 6 messages: start action, 4 standalone messages, finish
# action:
self.assertEqual([m["action_counter"] for m in logger.messages],
range(6))
list(range(6)))


def test_writePassesSerializer(self):
Expand Down
Loading

0 comments on commit aa550bd

Please sign in to comment.