Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
b33d5c2
Add Character and tests for Character
jbh May 14, 2018
aea658b
Actually add tests for Character
jbh May 14, 2018
593571c
Add tests for character
jbh May 24, 2018
b08e9ee
Fix the way Character is built
jbh Jun 7, 2018
58141e3
Fix the way program parameters are built for multiple.
jbh Jun 7, 2018
9b566cf
Add Integers and Floats
jbh Jun 7, 2018
5699a63
Rename test classes
jbh Jun 7, 2018
f009c39
Clean up ServiceProgram test
jbh Jul 19, 2018
db70ae6
Fix merge conflict
jbh Jul 19, 2018
4c4ba0f
Remove print from test
jbh Jul 19, 2018
d003132
Make service program extension of program, and add parameter parent c…
jordiwes Jul 23, 2018
c690807
Combine classes into module
jordiwes Jul 23, 2018
7b2047a
update unit tests for module change
jordiwes Jul 23, 2018
8e5991e
updated init
jordiwes Jul 24, 2018
e519f36
oops
jordiwes Jul 24, 2018
3b5782a
combine into program module
jordiwes Jul 24, 2018
2c48f1d
add isreturn to types
jordiwes Jul 24, 2018
d7ae862
fix imports
jordiwes Jul 24, 2018
15db895
add by value and first attempt at varying char
jordiwes Jul 24, 2018
c88f046
add decimal tyoe, consolidate tests
jordiwes Jul 24, 2018
c100b78
improt types
jordiwes Jul 24, 2018
12b8cec
fix issues
jordiwes Jul 24, 2018
b2b99b8
fix issues
jordiwes Jul 24, 2018
a326c36
minor fixes for tests
jordiwes Jul 24, 2018
d023d68
make varlen a string
jordiwes Jul 24, 2018
4f8e0f4
fix test
jordiwes Jul 24, 2018
2574aa7
Fix line lengths
jbh Jul 24, 2018
d8bd434
Remove semicolons
jbh Jul 24, 2018
050abb9
Change types to use super class __init__
jbh Jul 24, 2018
302688a
separate Types class and update everything else for this
jordiwes Jul 27, 2018
58ad5bb
import parameter
jordiwes Jul 27, 2018
9de5a1c
import parameter
jordiwes Jul 27, 2018
9a95a4a
intialize payload
jordiwes Jul 27, 2018
75a6bbb
fix test
jordiwes Jul 27, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions pykit/__init__.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,10 @@
__version__ = "0.2.0"
from .ClCommand import ClCommand
from .types import Integer
from .types import Float
from .types import Character
from .types import Decimal
from .types import Binary
from .types import Parameter
from .program import Program
from .program import ServiceProgram
45 changes: 45 additions & 0 deletions pykit/program.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
class Program:
"""
Object for calling an IBM i Program.
"""
def __init__(self, name, library=''):
self.name = name
self.library = library
self.parameters = []
self.payload = {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If these are going to be private fields, we should think about indicating as so with a preceding single or double underscore: https://docs.python.org/3/tutorial/classes.html#private-variables

Not something to hold this up, but something to think about.

"pgm": [{"name": name, "lib": library}]
}

def add_parameter(self, parameter):
"""

:param parameter: Parameter to be added
:return:
"""
self.parameters.append(parameter)


def get_payload(self):
if len(self.parameters) is 1:
self.payload["pgm"].append({"s": self.parameters[0].get_payload()})
else:
self.payload["pgm"].append({"s":[]})
for p in self.parameters:
self.payload["pgm"][-1]["s"].append(p.get_payload())

return self.payload


class ServiceProgram(Program):
"""
Object for calling an IBM i Program.
"""
def __init__(self, name, library='', function=''):
self.name = name
self.library = library
self.parameters = []
self.payload = {
"pgm": [{"name": name, "lib": library, "func": function}]
}


1 change: 0 additions & 1 deletion pykit/tests/test_command.py
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,5 @@ def test_execute_exec_command(self):
self.assertTrue(response)


self.assertTrue(response)
if __name__ == '__main__':
unittest.main()
1 change: 0 additions & 1 deletion pykit/tests/test_http.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,5 @@ def test_execute_payload(self):
self.assertTrue(response)


self.assertTrue(response)
if __name__ == '__main__':
unittest.main()
49 changes: 49 additions & 0 deletions pykit/tests/test_program.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import unittest
import os
from pykit.transport.http import connect
from pykit import *


class TestProgram(unittest.TestCase):
def setUp(self):
char = Parameter(Character("char", 128, "Hi there"))
self.hello_prog = Program("HELLO", "DB2JSON")
self.hello_prog.add_parameter(char)

def test_hello_world(self):
self.assertEqual(
self.hello_prog.get_payload(),
{"pgm":[{"name":"HELLO", "lib":"DB2JSON"}, {"s":{"name":"char", "type":"128a", "value":"Hi there"}}]}
)

# WIP
def test_rainbow(self):
int = Parameter(Integer("aint8", 3, 1))
float = Parameter(Float("afloat", 4, 2, 5.55))
char = Parameter(Character("achar", 32, "A"))
rainbow = Program("RAINBOW", "DB2JSON")
rainbow.add_parameter(int)
rainbow.add_parameter(float)
rainbow.add_parameter(char)
self.assertEqual(
rainbow.get_payload(),
{"pgm":[{"name":"RAINBOW", "lib":"DB2JSON"}, {"s": [
{"name":"aint8", "type":"3i0", "value":1},
{"name":"afloat", "type":"4f2", "value":5.55},
{"name":"achar", "type":"32a", "value":"A"}
]}]}
)

def test_execute_hello_world(self):
connection = connect(
os.environ['PK_DB2SOCK_URL'],
db2sock_auth=(
os.environ['PK_DB2SOCK_USER'], os.environ['PK_DB2SOCK_PASS']))
toolkit = connection.toolkit()
toolkit.add(self.hello_prog)
response = toolkit.execute()
self.assertEqual(response, {"script": [{"pgm": ["HELLO", "DB2JSON", {"char": "Hello World"}]}]})


if __name__ == '__main__':
unittest.main()
43 changes: 43 additions & 0 deletions pykit/tests/test_service_program.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import unittest
import os
from pykit.transport.http import connect
from pykit import *


class TestServiceProgram(unittest.TestCase):
def setUp(self):
char = Parameter(Character("char", 128, "Hi there"))
char_return = Parameter(Character("char", 128, "Hi back"), direction='out', byValue=False, isReturn=True)
self.hello_prog = ServiceProgram("HELLOSRV", "DB2JSON", "HELLO")
self.hello_prog.add_parameter(char)
self.hello_prog_return = ServiceProgram("HELLOSRV", "DB2JSON", "HELLOAGAIN")
self.hello_prog_return.add_parameter(char)
self.hello_prog_return.add_parameter(char_return)


def test_hello(self):
self.assertEqual(
self.hello_prog.get_payload(),
{"pgm":[{"name":"HELLOSRV", "lib":"DB2JSON", "func": "HELLO"},
{"s":{"name":"char", "type":"128a", "value":"Hi there"}}]}
)
self.assertEqual(
self.hello_prog_return.get_payload(),
{"pgm":[{"name":"HELLOSRV", "lib":"DB2JSON", "func": "HELLOAGAIN"},
{"s":[{"name":"char", "type":"128a", "value":"Hi there"},
{"name":"char", "type":"128a", "value":"Hi back", "by":"return"}]}]}
)

def test_execute_hello_world(self):
connection = connect(
os.environ['PK_DB2SOCK_URL'],
db2sock_auth=(
os.environ['PK_DB2SOCK_USER'], os.environ['PK_DB2SOCK_PASS']))
toolkit = connection.toolkit()
toolkit.add(self.hello_prog)
response = toolkit.execute()
self.assertEqual(response, {"script": [{"pgm": ["HELLOSRV", "DB2JSON", "HELLO", {"char": "Hello World"}]}]})


if __name__ == '__main__':
unittest.main()
2 changes: 1 addition & 1 deletion pykit/tests/test_toolkit.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from pykit.transport.http import connect


class TestHttpConnection(unittest.TestCase):
class TestToolkit(unittest.TestCase):
def setUp(self):
self.connection = connect(
os.environ['PK_DB2SOCK_URL'],
Expand Down
49 changes: 49 additions & 0 deletions pykit/tests/test_types.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
import unittest
import os
from pykit import Integer, Float, Character, Decimal, Binary, Parameter


class TestInteger(unittest.TestCase):
def test_integer_payload(self):
new_int = Parameter(Integer('aint8', 3, 1))
self.assertEqual(new_int.get_payload(), {"name":"aint8", "type":"3i0", "value":1})

def test_unsigned_integer_payload(self):
new_int = Parameter(Integer('auint8', 3, 1, False))
self.assertEqual(new_int.get_payload(), {"name":"auint8", "type":"3u0", "value":1})


class TestFloat(unittest.TestCase):
def test_float_payload(self):
new_float = Parameter(Float('afloat', 4, 2, 5.55))
self.assertEqual(new_float.get_payload(), {"name":"afloat", "type":"4f2", "value":5.55})


class TestCharacter(unittest.TestCase):
def test_character_payload(self):
char = Parameter(Character('achar', 10, "Hello"))
self.assertEqual(char.get_payload(), {"name":"achar", "type":"10a", "value":"Hello"})

def test_character_varying_payload(self):
char = Parameter(Character('bchar', 100, "Bye", 2))
self.assertEqual(char.get_payload(), {"name":"bchar", "type":"100av2", "value":"Bye"})


class TestDecimal(unittest.TestCase):
def test_decimal_payload(self):
dec = Parameter(Decimal('adec', 10, 2, 850.2))
self.assertEqual(dec.get_payload(), {"name":"adec", "type":"10p2", "value":850.2})

def test_signed_decimal_payload(self):
dec = Parameter(Decimal('bdec', 9, 0, 800, True))
self.assertEqual(dec.get_payload(), {"name":"bdec", "type":"9s0", "value":800})

class TestBinary(unittest.TestCase):
def test_binary_payload(self):
bin = Parameter(Binary('abin', 2, "0000"))
self.assertEqual(bin.get_payload(), {"name":"abin", "type":"2b", "value":"0000"})



if __name__ == '__main__':
unittest.main()
157 changes: 157 additions & 0 deletions pykit/types.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,157 @@
class Parameter:
def __init__(self, parameterType, direction='inout', byValue=False, isReturn=False):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The way dbsock handles return values is weird and unintuitive. A return value by definition cannot be an input, so why does it have a direction? I think there should have been a separate way to specify the return value instead of trying to shoe-horn it in as a "parameter".

I'd like to see us hide this quirk as much as possible. Can we split out the return value handling in to a separate class, maybe ReturnValue and then add a new method add_return?

"""
:param name:
:param value:
"""
self.parameterType = parameterType
self.direction = direction
self.isReturn = isReturn
self.byValue = False
self.payload = {}

def get_payload(self):
self.payload = self.parameterType.get_payload()
if self.isReturn:
self.payload['by'] = 'return'
if self.byValue:
self.payload['by'] = 'val'

return self.payload


class ParameterType:
"""
Object for IBM i ParameterTypes.
"""
def __init__(self, name, value):
"""
:param name:
:param value:
"""
self.value = value
self.name = str(name)
self.payload = {"name":self.name}


def get_payload(self):
self.payload['value'] = self.value
return self.payload

class Float(ParameterType):
"""
Object for IBM i Float.
"""
def __init__(self, name, length, precision, value):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can a float have a precision? I think it has to be specified as 0. If so, just like the Integer class, leave it off the parameter list.

"""
How much error checking do we want here?
Should we check that length is positive?
Should we check if value matches length?
Should we truncate the value to the length?

:param length:
:param precision:s
:param value:
"""
ParameterType.__init__(self, name, value)
self.length = length
self.precision = precision

def get_payload(self):
self.payload['type'] = str(self.length) + 'f' + str(self.precision)
ParameterType.get_payload(self)
return self.payload


class Integer(ParameterType):
"""
Object for IBM i Integer.
"""
def __init__(self, name, length, value, signed=True):
"""
How much error checking do we want here?
Should we check that length is positive?
Should we check if value matches length?
Should we truncate the value to the length?

:param length:
:param value:
"""
ParameterType.__init__(self, name, value)
self.length = length
self.signed = signed

def get_payload(self):
self.payload['type'] = str(self.length) + ('i' if self.signed else 'u') + '0'
ParameterType.get_payload(self)

return self.payload


class Character(ParameterType):
"""
Object for IBM i Character.
"""
def __init__(self, name, length, value, varying=''):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe a separate VarChar class instead of a parameter?

"""
:param length:
:param value:
"""
ParameterType.__init__(self, name, value)
self.length = length
self.varying = varying

def get_payload(self):
self.payload['type'] = str(self.length) + 'a'
if self.varying:
self.payload['type'] = self.payload['type'] + 'v' + str(self.varying)

ParameterType.get_payload(self)
return self.payload


class Decimal(ParameterType):
"""
Object for IBM i Decimal.
"""
def __init__(self, name, length, decimals, value, signed=False):
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Huh, apparently indeed the s does stand for "signed". There's also an "l" and "r" types which have a leading or trailing sign digit separate from the numeric data (I wonder if anyone actually uses those?). I don't know of anybody that refers to them as "signed decimal" though (could be showing ignorance here) - I've always heard it referred to as zoned decimal. Indeed the the free-format names use ZONED and PACKED. Perhaps instead, we just flip the condition and use packed=True?

Alternatively instead of a parameter, use separate classes: ZonedDecimal and PackedDecimal (or Numeric and Decimal) You could use this class as an internal base class.

"""
How much error checking do we want here?
Should we check that length is positive?
Should we check if value matches length?
Should we truncate the value to the length?

:param length:
:param value:
"""
ParameterType.__init__(self, name, value)
self.length = length
self.signed = signed
self.decimals = decimals

def get_payload(self):
self.payload['type'] = str(self.length) + ('s' if self.signed else 'p') + str(self.decimals)
ParameterType.get_payload(self)

return self.payload


class Binary(ParameterType):
"""
Object for IBM i Binary.
"""
def __init__(self, name, length, value):
"""

:param length:
:param value:
"""
ParameterType.__init__(self, name, value)
self.length = length

def get_payload(self):
self.payload['type'] = str(self.length) + 'b'
ParameterType.get_payload(self)
return self.payload