Browse Source

Revert "Revert "Python 3 style conversion""

This reverts commit 2eb3a8737d.
master
James Hilliard 7 years ago
parent
commit
5a0d5fe72f
  1. 5
      conftest.py
  2. 4
      jmbase/jmbase/__init__.py
  3. 3
      jmbase/jmbase/bigstring.py
  4. 147
      jmbase/jmbase/commands.py
  5. 6
      jmbase/jmbase/support.py
  6. 2
      jmbase/setup.py
  7. 4
      jmbase/test/test_base_support.py
  8. 4
      jmbase/test/test_commands.py
  9. 2
      jmbitcoin/jmbitcoin/secp256k1_transaction.py
  10. 2
      jmbitcoin/setup.py
  11. 2
      jmclient/setup.py
  12. 6
      jmdaemon/jmdaemon/__init__.py
  13. 8
      jmdaemon/jmdaemon/daemon_protocol.py
  14. 4
      jmdaemon/jmdaemon/enc_wrapper.py
  15. 4
      jmdaemon/jmdaemon/irc.py
  16. 10
      jmdaemon/jmdaemon/message_channel.py
  17. 6
      jmdaemon/jmdaemon/orderbookwatch.py
  18. 9
      jmdaemon/jmdaemon/protocol.py
  19. 4
      jmdaemon/jmdaemon/socks.py
  20. 2
      jmdaemon/setup.py
  21. 4
      jmdaemon/test/dummy_mc.py
  22. 3
      jmdaemon/test/msgdata.py
  23. 4
      jmdaemon/test/test_daemon_protocol.py
  24. 25
      jmdaemon/test/test_enc_wrapper.py
  25. 6
      jmdaemon/test/test_irc_messaging.py
  26. 8
      jmdaemon/test/test_message_channel.py
  27. 4
      jmdaemon/test/test_orderbookwatch.py

5
conftest.py

@ -1,3 +1,6 @@
from __future__ import (absolute_import, division,
print_function, unicode_literals)
from builtins import *
import pytest
import os
import time
@ -16,7 +19,7 @@ def local_command(command, bg=False, redirect=''):
elif OS == 'Linux':
command.extend(['>', '/dev/null', '2>&1'])
else:
print "OS not recognised, quitting."
print("OS not recognised, quitting.")
elif redirect:
command.extend(['>', redirect])

4
jmbase/jmbase/__init__.py

@ -1,4 +1,6 @@
from __future__ import print_function
from __future__ import (absolute_import, division,
print_function, unicode_literals)
from builtins import *
from .support import (get_log, chunks, debug_silence, debug_dump_object,
joinmarket_alert, core_alert, get_password, _byteify,

3
jmbase/jmbase/bigstring.py

@ -1,3 +1,6 @@
from __future__ import (absolute_import, division,
print_function, unicode_literals)
from builtins import * # noqa: F401
try:
from cStringIO import StringIO
except ImportError: #pragma: no cover

147
jmbase/jmbase/commands.py

@ -1,18 +1,20 @@
from __future__ import print_function
from __future__ import (absolute_import, division,
print_function, unicode_literals)
from builtins import * # noqa: F401
"""
Commands defining client-server (daemon)
messaging protocol (*not* Joinmarket p2p protocol).
Used for AMP asynchronous messages.
"""
from twisted.protocols.amp import Boolean, Command, Integer, String
from bigstring import BigString
from twisted.protocols.amp import Boolean, Command, Integer, Unicode
from .bigstring import BigString
class DaemonNotReady(Exception):
pass
class JMCommand(Command):
#a default response type
response = [('accepted', Boolean())]
response = [(b'accepted', Boolean())]
"""COMMANDS FROM CLIENT TO DAEMON
=================================
@ -27,44 +29,44 @@ class JMInit(JMCommand):
Blockchain source is communicated only as a naming
tag for messagechannels (currently IRC 'realname' field).
"""
arguments = [('bcsource', String()),
('network', String()),
('irc_configs', String()),
('minmakers', Integer()),
('maker_timeout_sec', Integer())]
errors = {DaemonNotReady: 'daemon is not ready'}
arguments = [(b'bcsource', Unicode()),
(b'network', Unicode()),
(b'irc_configs', Unicode()),
(b'minmakers', Integer()),
(b'maker_timeout_sec', Integer())]
errors = {DaemonNotReady: b'daemon is not ready'}
class JMStartMC(JMCommand):
"""Will restart message channel connections if config
has changed; otherwise will only change nym/nick on MCs.
"""
arguments = [('nick', String())]
arguments = [(b'nick', Unicode())]
class JMSetup(JMCommand):
"""Communicates which of "MAKER" or "TAKER"
roles are to be taken by this client; for MAKER
role, passes initial offers for announcement (for TAKER, this data is "none")
"""
arguments = [('role', String()),
('initdata', String())]
arguments = [(b'role', Unicode()),
(b'initdata', Unicode())]
class JMMsgSignature(JMCommand):
"""A response to a request for a bitcoin signature
on a message-channel layer message from the daemon
"""
arguments = [('nick', String()),
('cmd', String()),
('msg_to_return', String()),
('hostid', String())]
arguments = [(b'nick', Unicode()),
(b'cmd', Unicode()),
(b'msg_to_return', Unicode()),
(b'hostid', Unicode())]
class JMMsgSignatureVerify(JMCommand):
"""A response to a request to verify the bitcoin signature
of a message-channel layer message from the daemon
"""
arguments = [('verif_result', Boolean()),
('nick', String()),
('fullmsg', String()),
('hostid', String())]
arguments = [(b'verif_result', Boolean()),
(b'nick', Unicode()),
(b'fullmsg', Unicode()),
(b'hostid', Unicode())]
"""TAKER specific commands
"""
@ -77,24 +79,24 @@ class JMRequestOffers(JMCommand):
class JMFill(JMCommand):
"""Fill an offer/order
"""
arguments = [('amount', Integer()),
('commitment', String()),
('revelation', String()),
('filled_offers', String())]
arguments = [(b'amount', Integer()),
(b'commitment', Unicode()),
(b'revelation', Unicode()),
(b'filled_offers', Unicode())]
class JMMakeTx(JMCommand):
"""Send a hex encoded raw bitcoin transaction
to a set of counterparties
"""
arguments = [('nick_list', String()),
('txhex', String())]
arguments = [(b'nick_list', Unicode()),
(b'txhex', Unicode())]
class JMPushTx(JMCommand):
"""Pass a raw hex transaction to a specific
counterparty (maker) for pushing (anonymity feature in JM)
"""
arguments = [('nick', String()),
('txhex', String())]
arguments = [(b'nick', Unicode()),
(b'txhex', Unicode())]
"""MAKER specific commands
"""
@ -104,27 +106,27 @@ class JMAnnounceOffers(JMCommand):
to the daemon, along with new announcement
and cancellation lists (deltas).
"""
arguments = [('to_announce', String()),
('to_cancel', String()),
('offerlist', String())]
arguments = [(b'to_announce', Unicode()),
(b'to_cancel', Unicode()),
(b'offerlist', Unicode())]
class JMIOAuth(JMCommand):
"""Send contents of !ioauth message after
verifying Taker's auth message
"""
arguments = [('nick', String()),
('utxolist', String()),
('pubkey', String()),
('cjaddr', String()),
('changeaddr', String()),
('pubkeysig', String())]
arguments = [(b'nick', Unicode()),
(b'utxolist', Unicode()),
(b'pubkey', Unicode()),
(b'cjaddr', Unicode()),
(b'changeaddr', Unicode()),
(b'pubkeysig', Unicode())]
class JMTXSigs(JMCommand):
"""Send signatures on the bitcoin transaction
sent by TAKER
"""
arguments = [('nick', String()),
('sigs', String())]
arguments = [(b'nick', Unicode()),
(b'sigs', Unicode())]
"""COMMANDS FROM DAEMON TO CLIENT
=================================
@ -137,10 +139,10 @@ class JMInitProto(JMCommand):
(that key being controlled by the client; the daemon knows nothing
about bitcoin).
"""
arguments = [('nick_hash_length', Integer()),
('nick_max_encoded', Integer()),
('joinmarket_nick_header', String()),
('joinmarket_version', Integer())]
arguments = [(b'nick_hash_length', Integer()),
(b'nick_max_encoded', Integer()),
(b'joinmarket_nick_header', Unicode()),
(b'joinmarket_version', Integer())]
class JMUp(JMCommand):
"""Used to signal readiness of message channels to client.
@ -157,47 +159,46 @@ class JMRequestMsgSig(JMCommand):
"""Request the client to sign a message-channel
layer message with the bitcoin key for the nick
"""
arguments = [('nick', String()),
('cmd', String()),
('msg', String()),
('msg_to_be_signed', String()),
('hostid', String())]
arguments = [(b'nick', Unicode()),
(b'cmd', Unicode()),
(b'msg', Unicode()),
(b'msg_to_be_signed', Unicode()),
(b'hostid', Unicode())]
class JMRequestMsgSigVerify(JMCommand):
"""Request the client to verify a counterparty's
message-channel layer message against the provided nick
"""
arguments = [('msg', String()),
('fullmsg', String()),
('sig', String()),
('pubkey', String()),
('nick', String()),
('hashlen', Integer()),
('max_encoded', Integer()),
('hostid', String())]
arguments = [(b'msg', Unicode()),
(b'fullmsg', Unicode()),
(b'sig', Unicode()),
(b'pubkey', Unicode()),
(b'nick', Unicode()),
(b'hashlen', Integer()),
(b'max_encoded', Integer()),
(b'hostid', Unicode())]
""" TAKER-specific commands
"""
class JMOffers(JMCommand):
"""Return the entire contents of the
orderbook to TAKER, as a json-ified dict;
note uses BigString because can be very large
orderbook to TAKER, as a json-ified dict.
"""
arguments = [('orderbook', BigString())]
arguments = [(b'orderbook', BigString())]
class JMFillResponse(JMCommand):
"""Returns ioauth data from MAKER if successful.
"""
arguments = [('success', Boolean()),
('ioauth_data', String())]
arguments = [(b'success', Boolean()),
(b'ioauth_data', Unicode())]
class JMSigReceived(JMCommand):
"""Returns an individual bitcoin transaction signature
from a MAKER
"""
arguments = [('nick', String()),
('sig', String())]
arguments = [(b'nick', Unicode()),
(b'sig', Unicode())]
"""MAKER-specific commands
"""
@ -208,17 +209,17 @@ class JMAuthReceived(JMCommand):
allowing the MAKER to verify against btc library
before setting up encryption and continuing.
"""
arguments = [('nick', String()),
('offer', String()),
('commitment', String()),
('revelation', String()),
('amount', Integer()),
('kphex', String())]
arguments = [(b'nick', Unicode()),
(b'offer', Unicode()),
(b'commitment', Unicode()),
(b'revelation', Unicode()),
(b'amount', Integer()),
(b'kphex', Unicode())]
class JMTXReceived(JMCommand):
"""Send back transaction template provided
by TAKER, along with offerdata to verify fees.
"""
arguments = [('nick', String()),
('txhex', String()),
('offer', String())]
arguments = [(b'nick', Unicode()),
(b'txhex', Unicode()),
(b'offer', Unicode())]

6
jmbase/jmbase/support.py

@ -1,4 +1,6 @@
from __future__ import absolute_import, print_function
from __future__ import (absolute_import, division,
print_function, unicode_literals)
from builtins import * # noqa: F401
import sys
@ -48,7 +50,7 @@ def set_logging_level(level):
consoleHandler.setLevel(level)
def chunks(d, n):
return [d[x:x + n] for x in xrange(0, len(d), n)]
return [d[x:x + n] for x in range(0, len(d), n)]
def get_password(msg): #pragma: no cover
return getpass(msg)

2
jmbase/setup.py

@ -9,5 +9,5 @@ setup(name='joinmarketbase',
author_email='',
license='GPL',
packages=['jmbase'],
install_requires=['twisted==18.9.0', 'service-identity'],
install_requires=['future', 'twisted==18.9.0', 'service-identity'],
zip_safe=False)

4
jmbase/test/test_base_support.py

@ -1,5 +1,7 @@
#! /usr/bin/env python
from __future__ import print_function
from __future__ import (absolute_import, division,
print_function, unicode_literals)
from builtins import * # noqa: F401
from jmbase.support import debug_dump_object, joinmarket_alert
def test_debug_dump_object():

4
jmbase/test/test_commands.py

@ -1,5 +1,7 @@
#! /usr/bin/env python
from __future__ import print_function
from __future__ import (absolute_import, division,
print_function, unicode_literals)
from builtins import *
from twisted.internet import protocol, reactor, task
from twisted.internet.error import (ConnectionLost, ConnectionAborted,
ConnectionClosed, ConnectionDone)

2
jmbitcoin/jmbitcoin/secp256k1_transaction.py

@ -46,7 +46,7 @@ def json_changebase(obj, changer):
# Transaction serialization and deserialization
def deserialize(tx):
if isinstance(tx, str) and re.match('^[0-9a-fA-F]*$', tx):
if isinstance(tx, (str, unicode)) and re.match('^[0-9a-fA-F]*$', tx):
#tx = bytes(bytearray.fromhex(tx))
return json_changebase(
deserialize(binascii.unhexlify(tx)), lambda x: safe_hexlify(x))

2
jmbitcoin/setup.py

@ -9,5 +9,5 @@ setup(name='joinmarketbitcoin',
author_email='',
license='GPL',
packages=['jmbitcoin'],
install_requires=['secp256k1',],
install_requires=['future', 'secp256k1',],
zip_safe=False)

2
jmclient/setup.py

@ -9,5 +9,5 @@ setup(name='joinmarketclient',
author_email='',
license='GPL',
packages=['jmclient'],
install_requires=['joinmarketbase==0.4.2', 'mnemonic', 'qt4reactor', 'argon2_cffi', 'bencoder.pyx', 'pyaes'],
install_requires=['future', 'joinmarketbase==0.4.2', 'mnemonic', 'qt4reactor', 'argon2_cffi', 'bencoder.pyx', 'pyaes'],
zip_safe=False)

6
jmdaemon/jmdaemon/__init__.py

@ -1,7 +1,9 @@
from __future__ import print_function
from __future__ import (absolute_import, division,
print_function, unicode_literals)
from builtins import *
import logging
from protocol import *
from .protocol import *
from .enc_wrapper import as_init_encryption, decode_decrypt, \
encrypt_encode, init_keypair, init_pubkey, get_pubkey, NaclError
from .irc import IRCMessageChannel

8
jmdaemon/jmdaemon/daemon_protocol.py

@ -1,5 +1,7 @@
#! /usr/bin/env python
from __future__ import print_function
from __future__ import (absolute_import, division,
print_function, unicode_literals)
from builtins import *
from .message_channel import MessageChannelCollection
from .orderbookwatch import OrderbookWatch
@ -292,7 +294,7 @@ class JMDaemonServerProtocol(amp.AMP, OrderbookWatch):
are stored in the active_orders dict keyed by the nick of the Taker.
"""
nick, utxolist, pubkey, cjaddr, changeaddr, pubkeysig = [_byteify(
x) for x in nick, utxolist, pubkey, cjaddr, changeaddr, pubkeysig]
x) for x in (nick, utxolist, pubkey, cjaddr, changeaddr, pubkeysig)]
if not self.role == "MAKER":
return
if not nick in self.active_orders:
@ -574,7 +576,7 @@ class JMDaemonServerProtocol(amp.AMP, OrderbookWatch):
).fetchone()
if crow is None:
return
counterparty = crow['counterparty']
counterparty = crow[b'counterparty']
#TODO de-hardcode hp2
log.msg("Sending commitment to: " + str(counterparty))
self.mcc.prepare_privmsg(counterparty, 'hp2', commit)

4
jmdaemon/jmdaemon/enc_wrapper.py

@ -1,4 +1,6 @@
from __future__ import absolute_import, print_function
from __future__ import (absolute_import, division,
print_function, unicode_literals)
from builtins import * # noqa: F401
# A wrapper for public key
# authenticated encryption

4
jmdaemon/jmdaemon/irc.py

@ -1,4 +1,6 @@
from __future__ import absolute_import, print_function
from __future__ import (absolute_import, division,
print_function, unicode_literals)
from builtins import *
#TODO: SSL support (can it be done without back-end openssl?)
from twisted.internet import reactor, protocol

10
jmdaemon/jmdaemon/message_channel.py

@ -1,5 +1,7 @@
#! /usr/bin/env python
from __future__ import print_function
from __future__ import (absolute_import, division,
print_function, unicode_literals)
from builtins import * # noqa: F401
import abc
import base64
import threading
@ -12,7 +14,7 @@ from functools import wraps
log = get_log()
class CJPeerError(StandardError):
class CJPeerError(Exception):
pass
@ -223,7 +225,7 @@ class MessageChannelCollection(object):
log.debug('error, dont have encryption box object for ' + nick +
', dropping message')
return
message = encrypt_encode(message, box)
message = encrypt_encode(message.encode('ascii'), box)
#Anti-replay measure: append the message channel identifier
#to the signature; this prevents cross-channel replay but NOT
@ -901,7 +903,7 @@ class MessageChannel(object):
# need to decrypt everything after the command string
to_decrypt = ''.join(_chunks[1:])
try:
decrypted = decode_decrypt(to_decrypt, box)
decrypted = decode_decrypt(to_decrypt, box).decode('ascii')
except Exception as e:
log.debug('Error when decrypting, skipping: ' +
repr(e))

6
jmdaemon/jmdaemon/orderbookwatch.py

@ -1,5 +1,7 @@
#! /usr/bin/env python
from __future__ import absolute_import, print_function
from __future__ import (absolute_import, division,
print_function, unicode_literals)
from builtins import * # noqa: F401
import sqlite3
import sys
@ -44,7 +46,7 @@ class OrderbookWatch(object):
min_version = int(params[0])
max_version = int(params[1])
alert = msg[msg.index(params[1]) + len(params[1]):].strip()
except ValueError, IndexError:
except (ValueError, IndexError):
continue
if min_version < JM_VERSION < max_version:
print('=' * 60)

9
jmdaemon/jmdaemon/protocol.py

@ -1,3 +1,6 @@
from __future__ import (absolute_import, division,
print_function, unicode_literals)
from builtins import * # noqa: F401
#Protocol version
JM_VERSION = 5
@ -16,8 +19,8 @@ offertypes = {"reloffer": [(int, "oid"), (int, "minsize"), (int, "maxsize"),
offername_list = offertypes.keys()
ORDER_KEYS = ['counterparty', 'oid', 'ordertype', 'minsize', 'maxsize', 'txfee',
'cjfee']
ORDER_KEYS = [b'counterparty', b'oid', b'ordertype', b'minsize', b'maxsize', b'txfee',
b'cjfee']
COMMAND_PREFIX = '!'
JOINMARKET_NICK_HEADER = 'J'
@ -33,5 +36,5 @@ commitment_broadcast_list = ["hp2"]
plaintext_commands += offername_list
plaintext_commands += commitment_broadcast_list
public_commands = commitment_broadcast_list + ["orderbook", "cancel"
] + offername_list
] + list(offername_list)
private_commands = encrypted_commands + plaintext_commands

4
jmdaemon/jmdaemon/socks.py

@ -29,7 +29,9 @@ This module provides a standard socket-like interface for Python
for tunneling connections through SOCKS proxies.
"""
from __future__ import (absolute_import, division,
print_function, unicode_literals)
from builtins import * # noqa: F401
import socket
import struct
import random

2
jmdaemon/setup.py

@ -9,5 +9,5 @@ setup(name='joinmarketdaemon',
author_email='',
license='GPL',
packages=['jmdaemon'],
install_requires=['txtorcon', 'pyopenssl', 'libnacl', 'joinmarketbase==0.4.2'],
install_requires=['future', 'txtorcon', 'pyopenssl', 'libnacl', 'joinmarketbase==0.4.2'],
zip_safe=False)

4
jmdaemon/test/dummy_mc.py

@ -1,4 +1,6 @@
from __future__ import absolute_import, print_function
from __future__ import (absolute_import, division,
print_function, unicode_literals)
from builtins import *
import time

3
jmdaemon/test/msgdata.py

@ -1,3 +1,6 @@
from __future__ import (absolute_import, division,
print_function, unicode_literals)
from builtins import * # noqa: F401
#orderbook
t_orderbook = [{u'counterparty': u'J5FA1Gj7Ln4vSGne', u'ordertype': u'reloffer', u'oid': 0,
u'minsize': 7500000, u'txfee': 1000, u'maxsize': 599972700, u'cjfee': u'0.0002'},

4
jmdaemon/test/test_daemon_protocol.py

@ -1,5 +1,7 @@
#! /usr/bin/env python
from __future__ import absolute_import
from __future__ import (absolute_import, division,
print_function, unicode_literals)
from builtins import *
'''test daemon-protocol interfacae.'''
from jmdaemon import MessageChannelCollection

25
jmdaemon/test/test_enc_wrapper.py

@ -1,3 +1,6 @@
from __future__ import (absolute_import, division,
print_function, unicode_literals)
from builtins import * # noqa: F401
import base64
import string
import random
@ -11,37 +14,37 @@ from jmdaemon import (init_keypair, get_pubkey, init_pubkey, as_init_encryption,
@pytest.mark.parametrize("ab_message,ba_message,num_iterations",
[
# short ascii
("Attack at dawn", "Not tonight Josephine!", 5),
(b"Attack at dawn", b"Not tonight Josephine!", 5),
# long base64 encoded
(base64.b64encode(''.join(random.choice(
string.ascii_letters) for _ in xrange(5000))),
string.ascii_letters) for _ in range(5000))),
base64.b64encode(''.join(random.choice(
string.ascii_letters) for _ in xrange(5000))),
string.ascii_letters) for _ in range(5000))),
5,),
# large number of messages on the same connection
('rand', 'rand', 40000),
(b'rand', b'rand', 40000),
# 1 character
('\x00', '\x00', 5),
(b'\x00', b'\x00', 5),
])
def test_enc_wrapper(alice_bob_boxes, ab_message, ba_message, num_iterations):
alice_box, bob_box = alice_bob_boxes
for i in range(num_iterations):
ab_message = ''.join(
ab_message = (''.join(
random.choice(string.ascii_letters)
for x in range(100)) if ab_message == 'rand' else ab_message
ba_message = ''.join(
for x in range(100))).encode('ascii') if ab_message == b'rand' else ab_message
ba_message = (''.join(
random.choice(string.ascii_letters)
for x in range(100)) if ba_message == 'rand' else ba_message
for x in range(100))).encode('ascii') if ba_message == b'rand' else ba_message
otw_amsg = alice_box.encrypt(ab_message)
bob_ptext = bob_box.decrypt(otw_amsg)
assert bob_ptext == ab_message, "Encryption test: FAILED. Alice sent: %s, Bob received: " % (
assert bob_ptext == ab_message, "Encryption test: FAILED. Alice sent: {}, Bob received: {}".format(
ab_message, bob_ptext)
otw_bmsg = bob_box.encrypt(ba_message)
alice_ptext = alice_box.decrypt(otw_bmsg)
assert alice_ptext == ba_message, "Encryption test: FAILED. Bob sent: %s, Alice received: " % (
assert alice_ptext == ba_message, "Encryption test: FAILED. Bob sent: {}, Alice received: {}".format(
ba_message, alice_ptext)
assert decode_decrypt(encrypt_encode(ab_message, bob_box), bob_box) == ab_message

6
jmdaemon/test/test_irc_messaging.py

@ -1,5 +1,7 @@
#! /usr/bin/env python
from __future__ import absolute_import
from __future__ import (absolute_import, division,
print_function, unicode_literals)
from builtins import * # noqa: F401
'''Tests of joinmarket bots end-to-end (including IRC and bitcoin) '''
import time
@ -41,7 +43,7 @@ def on_order_seen(dummy, counterparty, oid, ordertype, minsize,
yg_name = counterparty
def on_pubkey(pubkey):
print "received pubkey: " + pubkey
print("received pubkey: " + pubkey)
def junk_pubmsgs(mc):
#start a raw IRCMessageChannel instance in a thread;

8
jmdaemon/test/test_message_channel.py

@ -1,5 +1,7 @@
#! /usr/bin/env python
from __future__ import absolute_import
from __future__ import (absolute_import, division,
print_function, unicode_literals)
from builtins import *
'''test messagechannel management code.'''
import pytest
@ -76,9 +78,9 @@ def don_error():
def don_ioauth(nick, utxo_list, auth_pub, cj_addr,
change_addr, btc_sig):
jlog.debug("onioauth callback")
jlog.debug("Args are: " + ",".join([str(x) for x in nick,
jlog.debug("Args are: " + ",".join([str(x) for x in (nick,
utxo_list, auth_pub, cj_addr,
change_addr, btc_sig]))
change_addr, btc_sig)]))
def don_sig(nick, sig):
jlog.debug("calledback on-sig")

4
jmdaemon/test/test_orderbookwatch.py

@ -1,5 +1,7 @@
#!/usr/bin/env python
from __future__ import print_function
from __future__ import (absolute_import, division,
print_function, unicode_literals)
from builtins import * # noqa: F401
import pytest

Loading…
Cancel
Save