def test_dict_json_v2(): try: grid = hszinc.Grid(version=hszinc.VER_2_0) grid.column['comment'] = {} grid.column['value'] = {} grid.extend([{ 'comment': 'An empty dict', 'value': {}, }]) hszinc.dump(grid, mode=hszinc.MODE_JSON) assert False, 'Project Haystack 2.0 doesn\'t support dict' except ValueError: pass
def test_list_zinc_v2(): try: grid = hszinc.Grid(version=hszinc.VER_2_0) grid.column['comment'] = {} grid.column['value'] = {} grid.extend([{ 'comment': 'An empty list', 'value': [], }]) hszinc.dump(grid, mode=hszinc.MODE_ZINC) assert False, 'Project Haystack 2.0 doesn\'t support lists' except ValueError: pass
def _hzinc_response(data, **kwargs): if len(data.column) == 0: # trick to get an empty grid without crashing the dumper data.column['empty'] = {} return HttpResponse(hszinc.dump(data), content_type="text/zinc;charset=utf-8", **kwargs)
def _post_grid(self, url, grid, headers=None, **kwargs): """ Post a grid to the Haystack server. """ if not self.isConnected: self.authenticate() if self._zinc: content_type = 'application/json' data = hszinc.dump(grid, mode=hszinc.MODE_JSON) else: content_type = 'text/zinc' data = hszinc.dump(grid, mode=hszinc.MODE_ZINC) return self._post_request(url, content_type, data, headers, **kwargs)
def test_on_http_grid_response(self, server_session): (server, session) = server_session # Reset our counter session._on_http_grid_response_called = 0 session._on_http_grid_response_last_args = None # Fetch a grid op = session._get_grid("dummy", callback=lambda *a, **kwa: None) # The operation should still be in progress assert not op.is_done # There shall be one request assert server.requests() == 1 rq = server.next_request() # Make a grid to respond with expected = hszinc.Grid() expected.column["empty"] = {} rq.respond( status=200, headers={b"Content-Type": "text/zinc"}, content=hszinc.dump(expected, mode=hszinc.MODE_ZINC), ) # Our dummy function should have been called assert session._on_http_grid_response_called == 1 assert isinstance(session._on_http_grid_response_last_args, tuple) assert len(session._on_http_grid_response_last_args) == 3 (response, args, kwargs) = session._on_http_grid_response_last_args assert isinstance(response, HTTPResponse) assert len(args) == 0 assert len(kwargs) == 0
def __init__(self, session, uri, grid, args=None, post_format=hszinc.MODE_ZINC, **kwargs): """ Initialise a POST request for the grid with the given grid, URI and arguments. :param session: Haystack HTTP session object. :param uri: Possibly partial URI relative to the server base address to perform a query. No arguments shall be given here. :param grid: Grid (or grids) to be posted to the server. :param post_format: What format to post grids in? :param args: Dictionary of key-value pairs to be given as arguments. """ self._log = session._log.getChild("post_grid.%s" % uri) super(PostGridOperation, self).__init__(session=session, uri=uri, args=args, **kwargs) # Convert the grids to their native format self._body = hszinc.dump(grid, mode=post_format).encode("utf-8") if post_format == hszinc.MODE_ZINC: self._content_type = "text/zinc" else: self._content_type = "application/json"
def formats_view(request): g = hszinc.Grid() g.column['mime'] = {} g.column['read'] = {} g.column['write'] = {} g.extend([{ 'mime': 'text/zinc', 'read': hszinc.MARKER, 'write': hszinc.MARKER }]) return HttpResponse(hszinc.dump(g), content_type="text/zinc;charset=utf-8")
def test_formats(self, server_session): (server, session) = server_session op = session.formats() # The operation should still be in progress assert not op.is_done # There shall be one request assert server.requests() == 1 rq = server.next_request() # Request shall be a GET assert rq.method == "GET", "Expecting GET, got %s" % rq # Request shall be for base + 'api/formats' assert rq.uri == BASE_URI + "api/formats" # Accept header shall be given assert rq.headers[b"Accept"] == "text/zinc" # Make a grid to respond with expected = hszinc.Grid() expected.column["mime"] = {} expected.column["receive"] = {} expected.column["send"] = {} expected.extend([ { "mime": "text/csv", "receive": hszinc.MARKER, "send": hszinc.MARKER }, { "mime": "text/zinc", "receive": hszinc.MARKER, "send": hszinc.MARKER }, { "mime": "application/json", "receive": hszinc.MARKER, "send": hszinc.MARKER, }, ]) rq.respond( status=200, headers={b"Content-Type": "text/zinc"}, content=hszinc.dump(expected, mode=hszinc.MODE_ZINC), ) # State machine should now be done assert op.is_done actual = op.result grid_cmp(expected, actual)
def test_about(self, server_session): (server, session) = server_session op = session.about() # The operation should still be in progress assert not op.is_done # There shall be one request assert server.requests() == 1 rq = server.next_request() # Request shall be a GET assert rq.method == 'GET', 'Expecting GET, got %s' % rq # Request shall be for base + 'api/about' assert rq.uri == BASE_URI + 'api/about' # Accept header shall be given assert rq.headers[b'Accept'] == 'text/zinc' # Make a grid to respond with expected = hszinc.Grid() expected.column['haystackVersion'] = {} expected.column['tz'] = {} expected.column['serverName'] = {} expected.column['serverTime'] = {} expected.column['serverBootTime'] = {} expected.column['productName'] = {} expected.column['productUri'] = {} expected.column['productVersion'] = {} expected.column['moduleName'] = {} expected.column['moduleVersion'] = {} expected.append({ 'haystackVersion': '2.0', 'tz': 'UTC', 'serverName': 'pyhaystack dummy server', 'serverTime': datetime.datetime.now(tz=pytz.UTC), 'serverBootTime': datetime.datetime.now(tz=pytz.UTC), 'productName': 'pyhaystack dummy server', 'productVersion': '0.0.1', 'productUri': hszinc.Uri('http://pyhaystack.readthedocs.io'), 'moduleName': 'tests.client.base', 'moduleVersion': '0.0.1', }) rq.respond(status=200, headers={ b'Content-Type': 'text/zinc', }, content=hszinc.dump(expected, mode=hszinc.MODE_ZINC)) # State machine should now be done assert op.is_done actual = op.result grid_cmp(expected, actual)
def test_about(self, server_session): (server, session) = server_session op = session.about() # The operation should still be in progress assert not op.is_done # There shall be one request assert server.requests() == 1 rq = server.next_request() # Request shall be a GET assert rq.method == 'GET', 'Expecting GET, got %s' % rq # Request shall be for base + 'api/about' assert rq.uri == BASE_URI + 'api/about' # Accept header shall be given assert rq.headers['Accept'] == 'text/zinc' # Make a grid to respond with expected = hszinc.Grid() expected.column['haystackVersion'] = {} expected.column['tz'] = {} expected.column['serverName'] = {} expected.column['serverTime'] = {} expected.column['serverBootTime'] = {} expected.column['productName'] = {} expected.column['productUri'] = {} expected.column['productVersion'] = {} expected.column['moduleName'] = {} expected.column['moduleVersion'] = {} expected.append({ 'haystackVersion': '2.0', 'tz': 'UTC', 'serverName': 'pyhaystack dummy server', 'serverTime': datetime.datetime.now(tz=pytz.UTC), 'serverBootTime': datetime.datetime.now(tz=pytz.UTC), 'productName': 'pyhaystack dummy server', 'productVersion': '0.0.1', 'productUri': hszinc.Uri('http://pyhaystack.readthedocs.io'), 'moduleName': 'tests.client.base', 'moduleVersion': '0.0.1', }) rq.respond(status=200, headers={ 'Content-Type': 'text/zinc', }, content=hszinc.dump(expected, mode=hszinc.MODE_ZINC)) # State machine should now be done assert op.is_done actual = op.result grid_cmp(expected, actual)
def test_data_types_v3(): grid = hszinc.Grid(version=hszinc.VER_3_0) grid.column['comment'] = {} grid.column['value'] = {} grid.extend([ { 'comment': 'A NA', 'value': hszinc.NA, }, { 'comment': 'An empty list', 'value': [], }, { 'comment': 'A null value in a list', 'value': [None], }, { 'comment': 'A marker in a list', 'value': [hszinc.MARKER], }, { 'comment': 'Booleans', 'value': [True, False], }, { 'comment': 'References', 'value': [hszinc.Ref('a-ref'), hszinc.Ref('a-ref', 'a value')], }, { 'comment': 'A quantity', 'value': [hszinc.Quantity(500, 'miles')], }, { 'comment': 'A XStr', 'value': [hszinc.XStr("hex", 'deadbeef')], }, ]) grid_str = hszinc.dump(grid) ref_str = '''ver:"3.0" comment,value "A NA",NA "An empty list",[] "A null value in a list",[N] "A marker in a list",[M] "Booleans",[T,F] "References",[@a-ref,@a-ref "a value"] "A quantity",[500miles] "A XStr",[hex("deadbeef")] ''' assert grid_str == ref_str
def test_get_single_entity(self, server_session): (server, session) = server_session # Try retrieving an existing single entity op = session.get_entity("my.entity.id", single=True) # The operation should still be in progress assert not op.is_done # There shall be one request assert server.requests() == 1 rq = server.next_request() # Request shall be a GET assert rq.method == "GET", "Expecting GET, got %s" % rq # Request shall be for base + 'api/[email protected]' assert rq.uri == BASE_URI + "api/read?id=%40my.entity.id" # Accept header shall be given assert rq.headers[b"Accept"] == "text/zinc" # Make a grid to respond with response = hszinc.Grid() response.column["id"] = {} response.column["dis"] = {} response.column["randomTag"] = {} response.append({ "id": hszinc.Ref("my.entity.id", value="id"), "dis": "A test entity", "randomTag": hszinc.MARKER, }) rq.respond( status=200, headers={b"Content-Type": "text/zinc"}, content=hszinc.dump(response, mode=hszinc.MODE_ZINC), ) # State machine should now be done assert op.is_done entity = op.result # Response should be an entity assert isinstance(entity, Entity), "%r not an entity" % entity # The tags should be passed through from the response assert entity.id.name == "my.entity.id" assert entity.tags["dis"] == response[0]["dis"] assert entity.tags["randomTag"] == response[0]["randomTag"]
def test_formats(self, server_session): (server, session) = server_session op = session.formats() # The operation should still be in progress assert not op.is_done # There shall be one request assert server.requests() == 1 rq = server.next_request() # Request shall be a GET assert rq.method == 'GET', 'Expecting GET, got %s' % rq # Request shall be for base + 'api/formats' assert rq.uri == BASE_URI + 'api/formats' # Accept header shall be given assert rq.headers[b'Accept'] == 'text/zinc' # Make a grid to respond with expected = hszinc.Grid() expected.column['mime'] = {} expected.column['receive'] = {} expected.column['send'] = {} expected.extend([{ "mime": "text/csv", "receive": hszinc.MARKER, "send": hszinc.MARKER, }, { "mime": "text/zinc", "receive": hszinc.MARKER, "send": hszinc.MARKER, }, { "mime": "application/json", "receive": hszinc.MARKER, "send": hszinc.MARKER, }]) rq.respond(status=200, headers={ b'Content-Type': 'text/zinc', }, content=hszinc.dump(expected, mode=hszinc.MODE_ZINC)) # State machine should now be done assert op.is_done actual = op.result grid_cmp(expected, actual)
def test_get_single_entity(self, server_session): (server, session) = server_session # Try retrieving an existing single entity op = session.get_entity('my.entity.id', single=True) # The operation should still be in progress assert not op.is_done # There shall be one request assert server.requests() == 1 rq = server.next_request() # Request shall be a GET assert rq.method == 'GET', 'Expecting GET, got %s' % rq # Request shall be for base + 'api/[email protected]' assert rq.uri == BASE_URI + 'api/read?id=%40my.entity.id' # Accept header shall be given assert rq.headers[b'Accept'] == 'text/zinc' # Make a grid to respond with response = hszinc.Grid() response.column['id'] = {} response.column['dis'] = {} response.column['randomTag'] = {} response.append({ 'id': hszinc.Ref('my.entity.id', value='id'), 'dis': 'A test entity', 'randomTag': hszinc.MARKER }) rq.respond(status=200, headers={ b'Content-Type': 'text/zinc', }, content=hszinc.dump(response, mode=hszinc.MODE_ZINC)) # State machine should now be done assert op.is_done entity = op.result # Response should be an entity assert isinstance(entity, Entity), '%r not an entity' % entity # The tags should be passed through from the response assert entity.id.name == 'my.entity.id' assert entity.tags['dis'] == response[0]['dis'] assert entity.tags['randomTag'] == response[0]['randomTag']
def test_grid_types_json(): innergrid = hszinc.Grid(version=hszinc.VER_3_0) innergrid.column['comment'] = {} innergrid.extend([ { 'comment': 'A innergrid', }, ]) grid = hszinc.Grid(version=hszinc.VER_3_0) grid.column['inner'] = {} grid.extend([ { 'inner': innergrid, }, ]) grid_str = hszinc.dump(grid, mode=hszinc.MODE_JSON) grid_json = json.loads(grid_str) assert grid_json == { 'meta': { 'ver': '3.0' }, 'cols': [ { 'name': 'inner' }, ], 'rows': [ { 'inner': { 'meta': { 'ver': '3.0' }, 'cols': [ { 'name': 'comment' }, ], 'rows': [ { 'comment': 's:A innergrid' }, ], } }, ], }
def test_grid_meta_json(): grid_json = json.loads(hszinc.dump(make_grid_meta(), mode=hszinc.MODE_JSON)) assert grid_json == { 'meta': { 'ver': '2.0', 'aString': 's:aValue', 'aNumber': 'n:3.141590', 'aNull': None, 'aMarker': 'm:', 'aQuantity': 'n:123.000000 Hz', }, 'cols': [ {'name': 'empty'}, ], 'rows': [], }
def test_get_single_entity_missing(self, server_session): (server, session) = server_session # Try retrieving an existing single entity that does not exist op = session.get_entity("my.nonexistent.id", single=True) # The operation should still be in progress assert not op.is_done # There shall be one request assert server.requests() == 1 rq = server.next_request() # Request shall be a GET assert rq.method == "GET", "Expecting GET, got %s" % rq # Request shall be for base + 'api/[email protected]' assert rq.uri == BASE_URI + "api/read?id=%40my.nonexistent.id" # Accept header shall be given assert rq.headers[b"Accept"] == "text/zinc" # Make a grid to respond with. Note, server might also choose to # throw an error, but we'll pretend it doesn't. response = hszinc.Grid() response.column["id"] = {} response.column["dis"] = {} rq.respond( status=200, headers={b"Content-Type": "text/zinc"}, content=hszinc.dump(response, mode=hszinc.MODE_ZINC), ) # State machine should now be done assert op.is_done # This should trigger a name error: try: entity = op.result assert entity is None except NameError as e: assert str(e) == "No matching entity found"
def _try_dump_parse(ref_grid, mode): try: # Dump the randomised grid to a string grid_str = hszinc.dump(ref_grid, mode=mode) except: # Dump some detail about the grid print('Failed to dump grid.') dump_grid(ref_grid) raise # Parse the grid string try: parsed_grid = hszinc.parse(grid_str, mode=mode, single=True) except: print('Failed to parse dumped grid') dump_grid(ref_grid) print('--- Parsed string ---') print(grid_str) raise approx_check_grid(parsed_grid, ref_grid)
def test_read_one_id(self, server_session): (server, session) = server_session op = session.read(hszinc.Ref('my.entity.id')) # The operation should still be in progress assert not op.is_done # There shall be one request assert server.requests() == 1 rq = server.next_request() # Request shall be a GET assert rq.method == 'GET', 'Expecting GET, got %s' % rq # Request shall be for a specific URI assert rq.uri == BASE_URI + 'api/read?id=%40my.entity.id' # Accept header shall be given assert rq.headers[b'Accept'] == 'text/zinc' # Make a grid to respond with expected = hszinc.Grid() expected.column['id'] = {} expected.column['dis'] = {} expected.extend([{ "id": hszinc.Ref('my.entity.id'), "dis": 'my entity' }]) rq.respond(status=200, headers={ b'Content-Type': 'text/zinc', }, content=hszinc.dump(expected, mode=hszinc.MODE_ZINC)) # State machine should now be done assert op.is_done actual = op.result grid_cmp(expected, actual)
def test_get_single_entity_missing(self, server_session): (server, session) = server_session # Try retrieving an existing single entity that does not exist op = session.get_entity('my.nonexistent.id', single=True) # The operation should still be in progress assert not op.is_done # There shall be one request assert server.requests() == 1 rq = server.next_request() # Request shall be a GET assert rq.method == 'GET', 'Expecting GET, got %s' % rq # Request shall be for base + 'api/[email protected]' assert rq.uri == BASE_URI + 'api/read?id=%40my.nonexistent.id' # Accept header shall be given assert rq.headers[b'Accept'] == 'text/zinc' # Make a grid to respond with. Note, server might also choose to # throw an error, but we'll pretend it doesn't. response = hszinc.Grid() response.column['id'] = {} response.column['dis'] = {} rq.respond(status=200, headers={ b'Content-Type': 'text/zinc', }, content=hszinc.dump(response, mode=hszinc.MODE_ZINC)) # State machine should now be done assert op.is_done # This should trigger a name error: try: entity = op.result assert entity is None except NameError as e: assert str(e) == 'No matching entity found'
def test_read_one_id(self, server_session): (server, session) = server_session op = session.read(hszinc.Ref("my.entity.id")) # The operation should still be in progress assert not op.is_done # There shall be one request assert server.requests() == 1 rq = server.next_request() # Request shall be a GET assert rq.method == "GET", "Expecting GET, got %s" % rq # Request shall be for a specific URI assert rq.uri == BASE_URI + "api/read?id=%40my.entity.id" # Accept header shall be given assert rq.headers[b"Accept"] == "text/zinc" # Make a grid to respond with expected = hszinc.Grid() expected.column["id"] = {} expected.column["dis"] = {} expected.extend([{ "id": hszinc.Ref("my.entity.id"), "dis": "my entity" }]) rq.respond( status=200, headers={b"Content-Type": "text/zinc"}, content=hszinc.dump(expected, mode=hszinc.MODE_ZINC), ) # State machine should now be done assert op.is_done actual = op.result grid_cmp(expected, actual)
def test_scalar_dict_zinc_v3(): grid = hszinc.Grid(version=hszinc.VER_3_0) grid.column['comment'] = {} grid.column['value'] = {} grid.extend([ { 'comment': 'An empty dict', 'value': {}, }, { 'comment': 'A marker in a dict', 'value': { "marker": hszinc.MARKER }, }, { 'comment': 'A references in a dict', 'value': { "ref": hszinc.Ref('a-ref'), "ref2": hszinc.Ref('a-ref', 'a value') }, }, { 'comment': 'A quantity in a dict', 'value': { "quantity": hszinc.Quantity(500, 'miles') }, }, ]) grid_str = hszinc.dump(grid, mode=hszinc.MODE_ZINC) assert grid_str == ("ver:\"3.0\"\n" "comment,value\n" "\"An empty dict\",{}\n" "\"A marker in a dict\",{marker:M}\n" "\"A references in a dict\",{" + " ".join([str(k) + ":" + str(v) for k, v in {"ref": "@a-ref", "ref2": "@a-ref"}.items()]) \ .replace("ref2:@a-ref", "ref2:@a-ref \"a value\"") + \ "}\n" "\"A quantity in a dict\",{quantity:500miles}\n")
def test_grid_types_zinc(): innergrid = hszinc.Grid(version=hszinc.VER_3_0) innergrid.column['comment'] = {} innergrid.extend([ { 'comment': 'A innergrid', }, ]) grid = hszinc.Grid(version=hszinc.VER_3_0) grid.column['inner'] = {} grid.extend([ { 'inner': innergrid, }, ]) grid_str = hszinc.dump(grid, mode=hszinc.MODE_ZINC) assert grid_str == ('ver:"3.0"\n' 'inner\n' '<<ver:"3.0"\n' 'comment\n' '"A innergrid"\n' '>>\n')
def __init__(self, session, uri, grid, args=None, post_format=hszinc.MODE_ZINC, **kwargs): """ Initialise a POST request for the grid with the given grid, URI and arguments. :param session: Haystack HTTP session object. :param uri: Possibly partial URI relative to the server base address to perform a query. No arguments shall be given here. :param grid: Grid (or grids) to be posted to the server. :param post_format: What format to post grids in? :param args: Dictionary of key-value pairs to be given as arguments. """ self._log = session._log.getChild('post_grid.%s' % uri) super(PostGridOperation, self).__init__( session=session, uri=uri, args=args, **kwargs) # Convert the grids to their native format self._body = hszinc.dump(grid, mode=post_format).encode('utf-8') if post_format == hszinc.MODE_ZINC: self._content_type = 'text/zinc' else: self._content_type = 'application/json'
def test_read_many_id(self, server_session): (server, session) = server_session op = session.read([ hszinc.Ref('my.entity.id1'), hszinc.Ref('my.entity.id2'), hszinc.Ref('my.entity.id3'), ]) # The operation should still be in progress assert not op.is_done # There shall be one request assert server.requests() == 1 rq = server.next_request() # Request shall be a GET assert rq.method == 'POST', 'Expecting POST, got %s' % rq # Request shall be for a specific URI assert rq.uri == BASE_URI + 'api/read' # Body shall be in ZINC assert rq.headers['Content-Type'] == 'text/zinc' # Body shall be a single valid grid of this form: expected = hszinc.Grid() expected.column['id'] = {} expected.extend([{ "id": hszinc.Ref('my.entity.id1'), }, { "id": hszinc.Ref('my.entity.id2'), }, { "id": hszinc.Ref('my.entity.id3'), }]) actual = hszinc.parse(rq.body, mode=hszinc.MODE_ZINC) assert len(actual) == 1 grid_cmp(expected, actual[0]) # Accept header shall be given assert rq.headers['Accept'] == 'text/zinc' # Make a grid to respond with expected = hszinc.Grid() expected.column['id'] = {} expected.column['dis'] = {} expected.extend([{ "id": hszinc.Ref('my.entity.id1'), "dis": 'my entity 1' }, { "id": hszinc.Ref('my.entity.id2'), "dis": 'my entity 2' }, { "id": hszinc.Ref('my.entity.id3'), "dis": 'my entity 3' }]) rq.respond(status=200, headers={ 'Content-Type': 'text/zinc', }, content=hszinc.dump(expected, mode=hszinc.MODE_ZINC)) # State machine should now be done assert op.is_done actual = op.result grid_cmp(expected, actual)
def test_ops(self, server_session): (server, session) = server_session op = session.ops() # The operation should still be in progress assert not op.is_done # There shall be one request assert server.requests() == 1 rq = server.next_request() # Request shall be a GET assert rq.method == 'GET', 'Expecting GET, got %s' % rq # Request shall be for base + 'api/ops' assert rq.uri == BASE_URI + 'api/ops' # Accept header shall be given assert rq.headers[b'Accept'] == 'text/zinc' # Make a grid to respond with expected = hszinc.Grid() expected.column['name'] = {} expected.column['summary'] = {} expected.extend([{ "name": "about", "summary": "Summary information for server" }, { "name": "ops", "summary": "Operations supported by this server" }, { "name": "formats", "summary": "Grid data formats supported by this server" }, { "name": "read", "summary": "Read records by id or filter" }, { "name": "hisRead", "summary": "Read historical records" }, { "name": "hisWrite", "summary": "Write historical records" }, { "name": "nav", "summary": "Navigate a project" }, { "name": "watchSub", "summary": "Subscribe to change notifications" }, { "name": "watchUnsub", "summary": "Unsubscribe from change notifications" }, { "name": "watchPoll", "summary": "Poll for changes in watched points" }, { "name": "pointWrite", "summary": "Write a real-time value to a point" }, { "name": "invokeAction", "summary": "Invoke an action on an entity" }]) rq.respond(status=200, headers={ b'Content-Type': 'text/zinc', }, content=hszinc.dump(expected, mode=hszinc.MODE_ZINC)) # State machine should now be done assert op.is_done actual = op.result grid_cmp(expected, actual)
def test_multi_grid_json(): grids = [make_simple_grid(), make_metadata_grid()] grid_json = json.loads(hszinc.dump(grids, mode=hszinc.MODE_JSON)) assert grid_json[0] == SIMPLE_EXAMPLE_JSON assert grid_json[1] == METADATA_EXAMPLE_JSON
def test_metadata_json(): grid = make_metadata_grid() grid_json = json.loads(hszinc.dump(grid, mode=hszinc.MODE_JSON)) assert grid_json == METADATA_EXAMPLE_JSON
def test_read_many_id(self, server_session): (server, session) = server_session op = session.read([ hszinc.Ref("my.entity.id1"), hszinc.Ref("my.entity.id2"), hszinc.Ref("my.entity.id3"), ]) # The operation should still be in progress assert not op.is_done # There shall be one request assert server.requests() == 1 rq = server.next_request() # Request shall be a GET assert rq.method == "POST", "Expecting POST, got %s" % rq # Request shall be for a specific URI assert rq.uri == BASE_URI + "api/read" # Body shall be in ZINC assert rq.headers[b"Content-Type"] == "text/zinc" # Body shall be a single valid grid of this form: expected = hszinc.Grid() expected.column["id"] = {} expected.extend([ { "id": hszinc.Ref("my.entity.id1") }, { "id": hszinc.Ref("my.entity.id2") }, { "id": hszinc.Ref("my.entity.id3") }, ]) actual = hszinc.parse(rq.body.decode("utf-8"), mode=hszinc.MODE_ZINC) assert len(actual) == 1 grid_cmp(expected, actual[0]) # Accept header shall be given assert rq.headers[b"Accept"] == "text/zinc" # Make a grid to respond with expected = hszinc.Grid() expected.column["id"] = {} expected.column["dis"] = {} expected.extend([ { "id": hszinc.Ref("my.entity.id1"), "dis": "my entity 1" }, { "id": hszinc.Ref("my.entity.id2"), "dis": "my entity 2" }, { "id": hszinc.Ref("my.entity.id3"), "dis": "my entity 3" }, ]) rq.respond( status=200, headers={b"Content-Type": "text/zinc"}, content=hszinc.dump(expected, mode=hszinc.MODE_ZINC), ) # State machine should now be done assert op.is_done actual = op.result grid_cmp(expected, actual)
def try_dump_parse(): # Generate a randomised grid of values and try parsing it back. ref_grid = hszinc.Grid() ref_grid.metadata.extend(gen_random_meta()) # Randomised columns for n in range(0, random.randint(1,5)): col_name = gen_random_name(existing=ref_grid.column) if random.choice([True,False]): ref_grid.column[col_name] = gen_random_meta() else: ref_grid.column[col_name] = {} # Randomised rows for n in range(0, random.randint(0,20)): row = {} for c in ref_grid.column.keys(): if random.choice([True,False]): row[c] = gen_random_scalar() ref_grid.append(row) try: # Dump the randomised grid to a string grid_str = hszinc.dump(ref_grid) except: # Dump some detail about the grid print ('Failed to dump grid.') dump_grid(ref_grid) raise # Parse the grid string try: grid_list = hszinc.parse(grid_str) except: print ('Failed to parse dumped grid') dump_grid(ref_grid) print ('--- Parsed string ---') print (grid_str) raise assert len(grid_list) == 1 parsed_grid = grid_list.pop(0) # Check metadata matches try: assert list(ref_grid.metadata.keys()) \ == list(parsed_grid.metadata.keys()) for key in ref_grid.metadata.keys(): approx_check(ref_grid.metadata[key], parsed_grid.metadata[key]) except: print ('Mismatch in metadata') print ('Reference grid') dump_grid(ref_grid) print ('Parsed grid') dump_grid(parsed_grid) raise try: # Check column matches assert list(ref_grid.column.keys()) \ == list(parsed_grid.column.keys()) except: print ('Mismatch in column') print ('Reference grid') dump_grid(ref_grid) print ('Parsed grid') dump_grid(parsed_grid) raise for col in ref_grid.column.keys(): try: for key in ref_grid.column[col].keys(): approx_check(ref_grid.column[col][key], \ parsed_grid.column[col][key]) except: print ('Mismatch in metadata for column %s' % col) print ('Reference: %r' % ref_grid.column[col]) print ('Parsed: %r' % parsed_grid.column[col]) raise try: # Check row matches assert len(ref_grid) == len(parsed_grid) except: print ('Mismatch in row count') print ('Reference grid') dump_grid(ref_grid) print ('Parsed grid') dump_grid(parsed_grid) for (ref_row, parsed_row) in zip(ref_grid, parsed_grid): try: for col in ref_grid.column.keys(): approx_check(ref_row.get(col), parsed_row.get(col)) except: print ('Mismatch in row') print ('Reference:') print (ref_row) print ('Parsed:') print (parsed_row) raise
def test_about(self, server_session): (server, session) = server_session op = session.about() # The operation should still be in progress assert not op.is_done # There shall be one request assert server.requests() == 1 rq = server.next_request() # Request shall be a GET assert rq.method == "GET", "Expecting GET, got %s" % rq # Request shall be for base + 'api/about' assert rq.uri == BASE_URI + "api/about" # Accept header shall be given assert rq.headers[b"Accept"] == "text/zinc" # Make a grid to respond with expected = hszinc.Grid() expected.column["haystackVersion"] = {} expected.column["tz"] = {} expected.column["serverName"] = {} expected.column["serverTime"] = {} expected.column["serverBootTime"] = {} expected.column["productName"] = {} expected.column["productUri"] = {} expected.column["productVersion"] = {} expected.column["moduleName"] = {} expected.column["moduleVersion"] = {} expected.append({ "haystackVersion": "2.0", "tz": "UTC", "serverName": "pyhaystack dummy server", "serverTime": datetime.datetime.now(tz=pytz.UTC), "serverBootTime": datetime.datetime.now(tz=pytz.UTC), "productName": "pyhaystack dummy server", "productVersion": "0.0.1", "productUri": hszinc.Uri("http://pyhaystack.readthedocs.io"), "moduleName": "tests.client.base", "moduleVersion": "0.0.1", }) rq.respond( status=200, headers={b"Content-Type": "text/zinc"}, content=hszinc.dump(expected, mode=hszinc.MODE_ZINC), ) # State machine should now be done assert op.is_done actual = op.result grid_cmp(expected, actual)
def test_get_multi_entity(self, server_session): (server, session) = server_session # Try retrieving existing multiple entities op = session.get_entity(["my.entity.id1", "my.entity.id2"], single=False) # The operation should still be in progress assert not op.is_done # There shall be one request assert server.requests() == 1 rq = server.next_request() # Request shall be a POST assert rq.method == "POST", "Expecting POST, got %s" % rq # Request shall be for base + 'api/[email protected]' assert rq.uri == BASE_URI + "api/read" # Accept header shall be given assert rq.headers[b"Accept"] == "text/zinc" assert rq.headers[b"Content-Type"] == "text/zinc" # Body shall be a single grid: rq_grid = hszinc.parse(rq.body.decode("utf-8"), mode=hszinc.MODE_ZINC, single=True) # It shall have one column; id assert set(rq_grid.column.keys()) == set(["id"]) # It shall have 2 rows assert len(rq_grid) == 2 # Each row should only have 'id' values assert all([(set(r.keys()) == set(["id"])) for r in rq_grid]) # The rows' 'id' column should *only* contain Refs. assert all([isinstance(r["id"], hszinc.Ref) for r in rq_grid]) # Both IDs shall be listed, we don't about order assert set([r["id"].name for r in rq_grid ]) == set(["my.entity.id1", "my.entity.id2"]) # Make a grid to respond with response = hszinc.Grid() response.column["id"] = {} response.column["dis"] = {} response.column["randomTag"] = {} response.extend([ { "id": hszinc.Ref("my.entity.id1", value="id1"), "dis": "A test entity #1", "randomTag": hszinc.MARKER, }, { "id": hszinc.Ref("my.entity.id2", value="id2"), "dis": "A test entity #2", "randomTag": hszinc.MARKER, }, ]) rq.respond( status=200, headers={b"Content-Type": "text/zinc"}, content=hszinc.dump(response, mode=hszinc.MODE_ZINC), ) # State machine should now be done assert op.is_done entities = op.result # Response should be a dict assert isinstance(entities, dict), "%r not a dict" % entities # Response should have these keys assert set(entities.keys()) == set(["my.entity.id1", "my.entity.id2"]) entity = entities.pop("my.entity.id1") assert isinstance(entity, Entity), "%r not an entity" % entity # The tags should be passed through from the response assert entity.id.name == "my.entity.id1" assert entity.tags["dis"] == response[0]["dis"] assert entity.tags["randomTag"] == response[0]["randomTag"] entity = entities.pop("my.entity.id2") assert isinstance(entity, Entity), "%r not an entity" % entity # The tags should be passed through from the response assert entity.id.name == "my.entity.id2" assert entity.tags["dis"] == response[1]["dis"] assert entity.tags["randomTag"] == response[1]["randomTag"]
def test_simple_json(): grid = make_simple_grid() grid_json = json.loads(hszinc.dump(grid, mode=hszinc.MODE_JSON)) assert grid_json == SIMPLE_EXAMPLE_JSON
def test_metadata(): grid = make_metadata_grid() grid_str = hszinc.dump(grid) assert grid_str == METADATA_EXAMPLE
def test_multi_grid(): grids = [make_simple_grid(), make_metadata_grid()] grid_str = hszinc.dump(grids) assert grid_str == '\n'.join([SIMPLE_EXAMPLE, METADATA_EXAMPLE])
def test_col_meta(): grid_str = hszinc.dump(make_col_meta()) assert grid_str == '''ver:"2.0"
def test_data_types(): grid = hszinc.Grid() grid.column['comment'] = {} grid.column['value'] = {} grid.extend([ { 'comment': 'A null value', 'value': None, }, { 'comment': 'A marker', 'value': hszinc.MARKER, }, { 'comment': 'A "remove" object', 'value': hszinc.REMOVE, }, { 'comment': 'A boolean, indicating False', 'value': False, }, { 'comment': 'A boolean, indicating True', 'value': True, }, { 'comment': 'A reference, without value', 'value': hszinc.Ref('a-ref'), }, { 'comment': 'A reference, with value', 'value': hszinc.Ref('a-ref', 'a value'), }, { 'comment': 'A binary blob', 'value': hszinc.Bin('text/plain'), }, { 'comment': 'A quantity', 'value': hszinc.Quantity(500,'miles'), }, { 'comment': 'A quantity without unit', 'value': hszinc.Quantity(500,None), }, { 'comment': 'A coordinate', 'value': hszinc.Coordinate(-27.4725,153.003), }, { 'comment': 'A URI', 'value': hszinc.Uri(u'http://www.example.com#`unicode:\u1234\u5678`'), }, { 'comment': 'A string', 'value': u'This is a test\n'\ u'Line two of test\n'\ u'\tIndented with "quotes", \\backslashes\\ and '\ u'Unicode characters: \u1234\u5678 and a $ dollar sign', }, { 'comment': 'A date', 'value': datetime.date(2016,1,13), }, { 'comment': 'A time', 'value': datetime.time(7,51,43,microsecond=12345), }, { 'comment': 'A timestamp (non-UTC)', 'value': pytz.timezone('Europe/Berlin').localize(\ datetime.datetime(2016,1,13,7,51,42,12345)), }, { 'comment': 'A timestamp (UTC)', 'value': pytz.timezone('UTC').localize(\ datetime.datetime(2016,1,13,7,51,42,12345)), }, ]) grid_str = hszinc.dump(grid) ref_str = '''ver:"2.0" comment,value "A null value",N "A marker",M "A \\"remove\\" object",R "A boolean, indicating False",F "A boolean, indicating True",T "A reference, without value",@a-ref "A reference, with value",@a-ref "a value" "A binary blob",Bin(text/plain) "A quantity",500miles "A quantity without unit",500 "A coordinate",C(-27.472500,153.003000) "A URI",`http://www.example.com#\\`unicode:\\u1234\\u5678\\`` "A string","This is a test\\nLine two of test\\n\\tIndented with \\"quotes\\", \\\\backslashes\\\\ and Unicode characters: \\u1234\\u5678 and a \\$ dollar sign" "A date",2016-01-13 "A time",07:51:43.012345 "A timestamp (non-UTC)",2016-01-13T07:51:42.012345+01:00 Berlin "A timestamp (UTC)",2016-01-13T07:51:42.012345+00:00 UTC ''' assert grid_str == ref_str
def test_read_many_id(self, server_session): (server, session) = server_session op = session.read([ hszinc.Ref('my.entity.id1'), hszinc.Ref('my.entity.id2'), hszinc.Ref('my.entity.id3'), ]) # The operation should still be in progress assert not op.is_done # There shall be one request assert server.requests() == 1 rq = server.next_request() # Request shall be a GET assert rq.method == 'POST', 'Expecting POST, got %s' % rq # Request shall be for a specific URI assert rq.uri == BASE_URI + 'api/read' # Body shall be in ZINC assert rq.headers[b'Content-Type'] == 'text/zinc' # Body shall be a single valid grid of this form: expected = hszinc.Grid() expected.column['id'] = {} expected.extend([{ "id": hszinc.Ref('my.entity.id1'), }, { "id": hszinc.Ref('my.entity.id2'), }, { "id": hszinc.Ref('my.entity.id3'), }]) actual = hszinc.parse(rq.body.decode('utf-8'), mode=hszinc.MODE_ZINC) assert len(actual) == 1 grid_cmp(expected, actual[0]) # Accept header shall be given assert rq.headers[b'Accept'] == 'text/zinc' # Make a grid to respond with expected = hszinc.Grid() expected.column['id'] = {} expected.column['dis'] = {} expected.extend([{ "id": hszinc.Ref('my.entity.id1'), "dis": 'my entity 1' }, { "id": hszinc.Ref('my.entity.id2'), "dis": 'my entity 2' }, { "id": hszinc.Ref('my.entity.id3'), "dis": 'my entity 3' }]) rq.respond(status=200, headers={ b'Content-Type': 'text/zinc', }, content=hszinc.dump(expected, mode=hszinc.MODE_ZINC)) # State machine should now be done assert op.is_done actual = op.result grid_cmp(expected, actual)
def test_data_types_json(): grid = hszinc.Grid() grid.column['comment'] = {} grid.column['value'] = {} grid.extend([ { 'comment': 'A null value', 'value': None, }, { 'comment': 'A marker', 'value': hszinc.MARKER, }, { 'comment': 'A boolean, indicating False', 'value': False, }, { 'comment': 'A boolean, indicating True', 'value': True, }, { 'comment': 'A reference, without value', 'value': hszinc.Ref('a-ref'), }, { 'comment': 'A reference, with value', 'value': hszinc.Ref('a-ref', 'a value'), }, { 'comment': 'A binary blob', 'value': hszinc.Bin('text/plain'), }, { 'comment': 'A quantity', 'value': hszinc.Quantity(500,'miles'), }, { 'comment': 'A quantity without unit', 'value': hszinc.Quantity(500,None), }, { 'comment': 'A coordinate', 'value': hszinc.Coordinate(-27.4725,153.003), }, { 'comment': 'A URI', 'value': hszinc.Uri('http://www.example.com'), }, { 'comment': 'A string', 'value': 'This is a test\n'\ 'Line two of test\n'\ '\tIndented with "quotes" and \\backslashes\\', }, { 'comment': 'A date', 'value': datetime.date(2016,1,13), }, { 'comment': 'A time', 'value': datetime.time(7,51,43,microsecond=12345), }, { 'comment': 'A timestamp (non-UTC)', 'value': pytz.timezone('Europe/Berlin').localize(\ datetime.datetime(2016,1,13,7,51,42,12345)), }, { 'comment': 'A timestamp (UTC)', 'value': pytz.timezone('UTC').localize(\ datetime.datetime(2016,1,13,7,51,42,12345)), }, ]) grid_json = json.loads(hszinc.dump(grid, mode=hszinc.MODE_JSON)) assert grid_json == { 'meta': {'ver':'2.0'}, 'cols': [ {'name':'comment'}, {'name':'value'}, ], 'rows': [ { 'comment': 's:A null value', 'value': None}, { 'comment': 's:A marker', 'value': 'm:'}, { 'comment': 's:A boolean, indicating False', 'value': False}, { 'comment': 's:A boolean, indicating True', 'value': True}, { 'comment': 's:A reference, without value', 'value': 'r:a-ref'}, { 'comment': 's:A reference, with value', 'value': 'r:a-ref a value'}, { 'comment': 's:A binary blob', 'value': 'b:text/plain'}, { 'comment': 's:A quantity', 'value': 'n:500.000000 miles'}, { 'comment': 's:A quantity without unit', 'value': 'n:500.000000'}, { 'comment': 's:A coordinate', 'value': 'c:-27.472500,153.003000'}, { 'comment': 's:A URI', 'value': 'u:http://www.example.com'}, { 'comment': 's:A string', 'value': 's:This is a test\n'\ 'Line two of test\n'\ '\tIndented with \"quotes\" '\ 'and \\backslashes\\'}, { 'comment': 's:A date', 'value': 'd:2016-01-13'}, { 'comment': 's:A time', 'value': 'h:07:51:43.012345'}, { 'comment': 's:A timestamp (non-UTC)', 'value': 't:2016-01-13T07:51:42.012345+01:00 Berlin'}, { 'comment': 's:A timestamp (UTC)', 'value': 't:2016-01-13T07:51:42.012345+00:00 UTC'}, ], }
def test_ops(self, server_session): (server, session) = server_session op = session.ops() # The operation should still be in progress assert not op.is_done # There shall be one request assert server.requests() == 1 rq = server.next_request() # Request shall be a GET assert rq.method == 'GET', 'Expecting GET, got %s' % rq # Request shall be for base + 'api/ops' assert rq.uri == BASE_URI + 'api/ops' # Accept header shall be given assert rq.headers['Accept'] == 'text/zinc' # Make a grid to respond with expected = hszinc.Grid() expected.column['name'] = {} expected.column['summary'] = {} expected.extend([{ "name": "about", "summary": "Summary information for server" }, { "name": "ops", "summary": "Operations supported by this server" }, { "name": "formats", "summary": "Grid data formats supported by this server" }, { "name": "read", "summary": "Read records by id or filter" }, { "name": "hisRead", "summary": "Read historical records" }, { "name": "hisWrite", "summary": "Write historical records" }, { "name": "nav", "summary": "Navigate a project" }, { "name": "watchSub", "summary": "Subscribe to change notifications" }, { "name": "watchUnsub", "summary": "Unsubscribe from change notifications" }, { "name": "watchPoll", "summary": "Poll for changes in watched points" }, { "name": "pointWrite", "summary": "Write a real-time value to a point" }, { "name": "invokeAction", "summary": "Invoke an action on an entity" }]) rq.respond(status=200, headers={ 'Content-Type': 'text/zinc', }, content=hszinc.dump(expected, mode=hszinc.MODE_ZINC)) # State machine should now be done assert op.is_done actual = op.result grid_cmp(expected, actual)
def test_simple(): grid = make_simple_grid() grid_str = hszinc.dump(grid) assert grid_str == SIMPLE_EXAMPLE
def test_grid_meta(): grid_str = hszinc.dump(make_grid_meta()) assert grid_str == '''ver:"2.0" aString:"aValue" aNumber:3.14159 aNull:N aMarker aQuantity:123Hz
def test_get_multi_entity(self, server_session): (server, session) = server_session # Try retrieving existing multiple entities op = session.get_entity(['my.entity.id1', 'my.entity.id2'], single=False) # The operation should still be in progress assert not op.is_done # There shall be one request assert server.requests() == 1 rq = server.next_request() # Request shall be a POST assert rq.method == 'POST', 'Expecting POST, got %s' % rq # Request shall be for base + 'api/[email protected]' assert rq.uri == BASE_URI + 'api/read' # Accept header shall be given assert rq.headers[b'Accept'] == 'text/zinc' assert rq.headers[b'Content-Type'] == 'text/zinc' # Body shall be a single grid: rq_grid = hszinc.parse(rq.body.decode('utf-8'), mode=hszinc.MODE_ZINC) assert len(rq_grid) == 1 rq_grid = rq_grid[0] # It shall have one column; id assert set(rq_grid.column.keys()) == set(['id']) # It shall have 2 rows assert len(rq_grid) == 2 # Each row should only have 'id' values assert all([(set(r.keys()) == set(['id'])) for r in rq_grid]) # The rows' 'id' column should *only* contain Refs. assert all([isinstance(r['id'], hszinc.Ref) for r in rq_grid]) # Both IDs shall be listed, we don't about order assert set([r['id'].name for r in rq_grid]) \ == set(['my.entity.id1', 'my.entity.id2']) # Make a grid to respond with response = hszinc.Grid() response.column['id'] = {} response.column['dis'] = {} response.column['randomTag'] = {} response.extend([{ 'id': hszinc.Ref('my.entity.id1', value='id1'), 'dis': 'A test entity #1', 'randomTag': hszinc.MARKER },{ 'id': hszinc.Ref('my.entity.id2', value='id2'), 'dis': 'A test entity #2', 'randomTag': hszinc.MARKER }]) rq.respond(status=200, headers={ b'Content-Type': 'text/zinc', }, content=hszinc.dump(response, mode=hszinc.MODE_ZINC)) # State machine should now be done assert op.is_done entities = op.result # Response should be a dict assert isinstance(entities, dict), '%r not a dict' % entity # Response should have these keys assert set(entities.keys()) == set(['my.entity.id1','my.entity.id2']) entity = entities.pop('my.entity.id1') assert isinstance(entity, Entity), '%r not an entity' % entity # The tags should be passed through from the response assert entity.id.name == 'my.entity.id1' assert entity.tags['dis'] == response[0]['dis'] assert entity.tags['randomTag'] == response[0]['randomTag'] entity = entities.pop('my.entity.id2') assert isinstance(entity, Entity), '%r not an entity' % entity # The tags should be passed through from the response assert entity.id.name == 'my.entity.id2' assert entity.tags['dis'] == response[1]['dis'] assert entity.tags['randomTag'] == response[1]['randomTag']