Skip to content

feat(API): Adding partial database API support#169

Merged
jashnani merged 1 commit intoni:masterfrom
jashnani:databaseAPI2
Aug 14, 2017
Merged

feat(API): Adding partial database API support#169
jashnani merged 1 commit intoni:masterfrom
jashnani:databaseAPI2

Conversation

@jashnani
Copy link

@jashnani jashnani commented Aug 2, 2017

  • This contribution adheres to CONTRIBUTING.md.
  • New tests have been created for any new features or regression tests for bugfixes.
  • tox successfully runs, including unit tests and style checks (see CONTRIBUTING.md).
  • Unit test example
  • Clean up commits
  • Clean up PR
  • Integration test
  • Include example in docs

What does this Pull Request accomplish?

Adds partial database API support.

Why should this Pull Request be merged?

Enhances the API with programmatic database functionality. This fixed #77.

What testing has been done?

The example runs as expected. Integration and unit tests run as expected.

Design Alternatives

Goals

  • Be pythonic (including using ABCs that make sense
  • Keep things simple to the user; minimizing opening handles unless needed and make it clear when its done

DatabaseAlias Mapping
(current design)

System.databases is a custom Mapping object with Alias objects that store the results. These objects could also have an "open database" call.

iter, keys, values, items, len, getitem, delitem all work as expected.

setitem could be implemented. There are a couple of annoyances

  • A DatabaseAlias object would need to be created by the user and passed in.
  • It could only succeed for database formats that have enough information to satisfy NI-XNET, like baud rate. We'd still need a custom "import" function that accepts additional information that is needed but not included in the database.

Alias/Path Mapping

System.databases is a custom Mapping object from alias to file path. Everything else is similar to DatabaseAlias Mapping.

Distinct Functions (no ABC)

Whether System functions or free functions (which would mean they could take the IPAddress for RT support).

Known Issues

System.databases property is missing a type check because flake8 is throwing an error when the return type is set to 'databases.Databases'. I suspect this might have to do with 'databases' being overused.

@jashnani jashnani requested review from bcornish, epage and lilesj August 2, 2017 22:33
@jashnani
Copy link
Author

jashnani commented Aug 2, 2017

Will be posting an update shortly with slight different design (using an alias object).

@coveralls
Copy link

coveralls commented Aug 2, 2017

Coverage Status

Coverage decreased (-0.5%) to 64.935% when pulling 5cb608d on jashnani:databaseAPI2 into 481a1fb on ni:master.

database_alias2 = 'custom_database2'
database_filepath2 = '../databases/custom_database2.dbc'
default_baud_rate2 = '250000'
my_system.aliases[database_alias2] = (database_filepath2, default_baud_rate2)
Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think adding through assignment will work for an API like this. That implies that you'll get the same object back on lookup but we can't implement that. My recommendation is an explicit add function

Copy link
Contributor

Choose a reason for hiding this comment

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

resolved. See also PR description.

database_alias3 = 'custom_database3'
database_filepath3 = '../databases/custom_database3.dbc'
default_baud_rate3 = '800000'
my_system.aliases[database_alias3] = (database_filepath3, default_baud_rate3)
Copy link
Contributor

Choose a reason for hiding this comment

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

aliases doesn't give context to the user of what this is meant to be. db_aliases or database_aliases would though.

Copy link
Contributor

Choose a reason for hiding this comment

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

resolved

output_session.frames.write([frame])
print('Sent frame with ID %s payload: %s' % (id, payload))

def main():
Copy link
Contributor

Choose a reason for hiding this comment

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

I'm assuming this is more so to showcase the API work you are doing rather than an example because I wouldn't expect an example to be creating multiple aliases.

Copy link
Author

Choose a reason for hiding this comment

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

No, I was expecting us to create multiple aliases and use them in the example. @lilesj what are your thoughts on this? are there other uses cases/workflows I should consider writing examples for?

Copy link
Contributor

Choose a reason for hiding this comment

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

Demonstrating how to parse a folder for DB files is an important use case. We might want to change one of the database file to a fibex file to show we can do it with multiple file types. My comment below mentions how we can reinforce the purpose of the example by reordering the operations a bit.

Copy link
Contributor

@epage epage Aug 14, 2017

Choose a reason for hiding this comment

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

resolved

elif terminated_cable.lower() == "n":
output_session.intf.can_term = constants.CanTerm.ON
else:
print("Unrecognised input ({}), assuming 'n'".format(terminated_cable))
Copy link
Contributor

Choose a reason for hiding this comment

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

If you're making this a function distinct function then it shouldn't take user input but should receive all parameters from the caller.

Copy link
Contributor

Choose a reason for hiding this comment

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

Resolved

@@ -0,0 +1,91 @@
from __future__ import absolute_import
Copy link
Contributor

Choose a reason for hiding this comment

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

programmatic_database_api.py

The name feels very awkward to me. Is there an equivalent C or LV example for what this is trying to accomplish? What is it named? What does it look like / do?

Copy link
Contributor

Choose a reason for hiding this comment

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

Agreed, the functionality is limited to adding and removing aliases for the moment so the name should probably reflect that in some way.

Copy link
Author

@jashnani jashnani Aug 3, 2017

Choose a reason for hiding this comment

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

I couldn't find an equivalent example for what I'm trying to do. There is a C example called XNET_Database_Browser but it doesn't leverage add or remove alias. It might be good for the get database list or get database list size functions that I'm writing.

I trying to demonstrate that the same read/write code can be reused with different database aliases (different cluster, baud rates, etc.). This sounded like a common use case for aliases based on my conversation with Jeff.

How about dynamic_database_usage.py or programmatic_database_usage.py?

Copy link
Contributor

Choose a reason for hiding this comment

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

This hasn't been renamed yet

@coveralls
Copy link

coveralls commented Aug 2, 2017

Coverage Status

Coverage decreased (-0.5%) to 64.891% when pulling 15978a2 on jashnani:databaseAPI2 into 481a1fb on ni:master.

2 similar comments
@coveralls
Copy link

Coverage Status

Coverage decreased (-0.5%) to 64.891% when pulling 15978a2 on jashnani:databaseAPI2 into 481a1fb on ni:master.

@coveralls
Copy link

Coverage Status

Coverage decreased (-0.5%) to 64.891% when pulling 15978a2 on jashnani:databaseAPI2 into 481a1fb on ni:master.

alias3 = Alias(database_alias3, database_filepath3, default_baud_rate3)
my_system.aliases[alias3.database_name] = alias3
print(list(my_system.aliases))

Copy link
Contributor

Choose a reason for hiding this comment

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

Since the intent of the example is to demonstrate the use of the database functions, can we call the alias functions first to show they are being created in preparation for the session objects.


def write_can_frame_using_alias(database_name):
def write_can_frame_using_alias(alias):
database_name = alias.database_name
Copy link
Contributor

Choose a reason for hiding this comment

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

What should the property be called?

It seems weird to mix alias / name terminology.

We could just implement __str__ and expect people to use that (and the session API can be updated to accept both str and Alias)

Copy link
Contributor

Choose a reason for hiding this comment

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

resolved

default_baud_rate1 = '500000'
my_system.aliases[database_alias1] = (database_filepath1, default_baud_rate1)
alias1 = Alias(database_alias1, database_filepath1, default_baud_rate1)
my_system.aliases[alias1.database_name] = alias1
Copy link
Contributor

Choose a reason for hiding this comment

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

Again, we have an asymmetry problem. We give NI-XNET more information than we can lookup.

I suspect it'll be cleaner and cause us fewer short term problems if we have an add function that takes the parameters and makes the call. Then when you do my_system.aliases["database_alias1"], you get back and Alias object that only has database_name (or whatever we call it) on it.

Copy link
Contributor

Choose a reason for hiding this comment

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

resolved.

@coveralls
Copy link

coveralls commented Aug 4, 2017

Coverage Status

Coverage decreased (-1.7%) to 63.741% when pulling f0b63da on jashnani:databaseAPI2 into 481a1fb on ni:master.

@coveralls
Copy link

coveralls commented Aug 4, 2017

Coverage Status

Coverage decreased (-1.7%) to 63.752% when pulling b1b47f8 on jashnani:databaseAPI2 into 481a1fb on ni:master.

2 similar comments
@coveralls
Copy link

Coverage Status

Coverage decreased (-1.7%) to 63.752% when pulling b1b47f8 on jashnani:databaseAPI2 into 481a1fb on ni:master.

@coveralls
Copy link

coveralls commented Aug 4, 2017

Coverage Status

Coverage decreased (-1.7%) to 63.752% when pulling b1b47f8 on jashnani:databaseAPI2 into 481a1fb on ni:master.

database_alias = 'custom_database'
database_filepath = '../databases/custom_database.dbc'
default_baud_rate = '500000'
my_system.databases.add(database_alias, database_filepath, default_baud_rate)
Copy link
Contributor

Choose a reason for hiding this comment

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

I thought we were leaning towards this being called add_alias?

Copy link
Author

Choose a reason for hiding this comment

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

I changed it in the implementation but forgot to update the example.

Copy link
Contributor

Choose a reason for hiding this comment

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

resolved.

elif terminated_cable.lower() == "n":
output_session.intf.can_term = constants.CanTerm.ON
else:
print("Unrecognised input ({}), assuming 'n'".format(terminated_cable))
Copy link
Contributor

Choose a reason for hiding this comment

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

Resolved


def main():
with system.System() as my_system:
print(list(my_system.databases))
Copy link
Contributor

Choose a reason for hiding this comment

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

Hmm, I'm torn.

Technically, when you iterate on a mapping, you get back the keys rather than the values.

DAQmx implemented a sequence that also accepts strings. Not sure how well that fits here.

I'd say carry on but this is something we need to eventually resolve.

Copy link
Contributor

Choose a reason for hiding this comment

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

Opened #173 about this.

The interesting aspects for this situation

  • Can we support integer indexes? This requires the database list returned by the driver to be a stable order
  • Is it ok if we still implement get/del? Since get is just a function, probably. del is fine; it works even on mutable sequences (granted, we won't have a fully mutable sequence).

Copy link
Contributor

Choose a reason for hiding this comment

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

resolved.

@jashnani
Copy link
Author

Shortly, I will post a separate commit with unit/system tests that should go along with this feature.

@coveralls
Copy link

coveralls commented Aug 11, 2017

Coverage Status

Coverage decreased (-0.7%) to 63.786% when pulling e131894 on jashnani:databaseAPI2 into 67976b6 on ni:master.

import collections
import itertools
import six
import typing # NOQA: F401
Copy link
Contributor

Choose a reason for hiding this comment

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

six is a third party module, please have it in a separate section

Copy link
Author

Choose a reason for hiding this comment

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

Fixed.

Copy link
Contributor

Choose a reason for hiding this comment

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

resolved.

nixnet/_funcs.py Outdated
_errors.check_for_error(result.value)
alias_list = alias_buffer_ctypes.value.decode("ascii").split(",")
filepath_list = filepath_buffer_ctypes.value.decode("ascii").split(",")
return list(zip(alias_list, filepath_list))
Copy link
Contributor

Choose a reason for hiding this comment

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

We've generally left post-processing like this to the clients of _funcs

Copy link
Author

Choose a reason for hiding this comment

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

I left the decoding of the strings logic in this layer thinking that this would need to be generated for all string output types anyway.

I've moved the 'split' and 'zip' logic to the layer above (databases) though.

Copy link
Contributor

Choose a reason for hiding this comment

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

Resolved.

nixnet/_funcs.py Outdated
):
# type: (...) -> typing.Tuple[List[typing.Text], List[typing.Text], int]
ip_address_ctypes = _ctypedefs.char_p(ip_address.encode('ascii'))
size_of_alias_buffer, size_of_filepath_buffer = nxdb_get_database_list_sizes(ip_address)
Copy link
Contributor

Choose a reason for hiding this comment

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

Generally we leave policy like this to the callers of _funcs. If, in the future, we add code-gen, it'll make life a lot easier

Copy link
Author

Choose a reason for hiding this comment

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

I've cleaned this up and move the call the sizes function one layer above in databases.py

Copy link
Contributor

Choose a reason for hiding this comment

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

Resolved.

return len(_funcs.nxdb_get_database_list(''))

def __iter__(self):
for alias, filepath in _funcs.nxdb_get_database_list(''):
Copy link
Contributor

Choose a reason for hiding this comment

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

for alias, _ in ...

_ is a generally recommended placeholder for "don't care" values

Copy link
Author

Choose a reason for hiding this comment

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

Fixed.

Copy link
Contributor

Choose a reason for hiding this comment

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

resolved.

else:
raise ValueError('Database alias %s not found in the system' % index)
else:
raise KeyError(index)
Copy link
Contributor

Choose a reason for hiding this comment

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

I might not have been clear before.

We should throw KeyError when the key isn't found.

As for what to throw on unsupported types, it looks like we've set the precedence of TypeError
https://github.com/ni/nixnet-python/blob/master/nixnet/_session/collection.py#L44

Copy link
Author

Choose a reason for hiding this comment

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

Fixed.

Copy link
Contributor

Choose a reason for hiding this comment

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

Resolved.

database_filepath,
):
self._handle = handle
self._index = index
Copy link
Contributor

Choose a reason for hiding this comment

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

Why does this take a handle or index?

Copy link
Author

Choose a reason for hiding this comment

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

Got rid of them, don't need them anymore.

Copy link
Contributor

Choose a reason for hiding this comment

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

Resolved.

self._handle = handle
self._index = index
self._database_alias = database_alias
self._database_filepath = database_filepath
Copy link
Contributor

Choose a reason for hiding this comment

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

Don't we need to define a property to expose filepath?

Copy link
Author

Choose a reason for hiding this comment

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

Yes, I've a added a property for this. Just the get function for now.

Copy link
Contributor

Choose a reason for hiding this comment

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

Resolved.

@@ -0,0 +1,91 @@
from __future__ import absolute_import
Copy link
Contributor

Choose a reason for hiding this comment

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

This hasn't been renamed yet

print(list(my_system.databases.keys()))
print(list(my_system.databases.values()))
print(list(my_system.databases.items()))
print(len(my_system.databases))
Copy link
Contributor

Choose a reason for hiding this comment

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

Didn't we discuss removing all these prints? This is an example and not a test

Copy link
Author

Choose a reason for hiding this comment

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

Yes, I just forgot to take them out. Good catch.

Copy link
Contributor

Choose a reason for hiding this comment

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

Resolved.

print(list(my_system.databases.items()))
print(len(my_system.databases))
database_alias = 'custom_database'
database_filepath = 'databases/custom_database.dbc'
Copy link
Contributor

Choose a reason for hiding this comment

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

Probably want to prefix this with basename(__file__) or whatever the call would be so they can run this from other locations

Copy link
Contributor

Choose a reason for hiding this comment

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

This doesn't look like its resolved.

Copy link
Contributor

Choose a reason for hiding this comment

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

Oops, I posted another comment on this.

@coveralls
Copy link

coveralls commented Aug 11, 2017

Coverage Status

Coverage decreased (-0.1%) to 64.387% when pulling cd964f2 on jashnani:databaseAPI2 into 67976b6 on ni:master.

@coveralls
Copy link

coveralls commented Aug 11, 2017

Coverage Status

Coverage decreased (-0.6%) to 64.394% when pulling 3cf21ff on jashnani:databaseAPI2 into e25b721 on ni:master.

@jashnani jashnani force-pushed the databaseAPI2 branch 6 times, most recently from 376b3ff to 28ffe2a Compare August 14, 2017 15:33
@epage epage changed the title Programmatic database API example feat(API): Adding partial database API support Aug 14, 2017
@coveralls
Copy link

coveralls commented Aug 14, 2017

Coverage Status

Coverage increased (+0.4%) to 65.548% when pulling 2eddf48 on jashnani:databaseAPI2 into df28f54 on ni:master.

@coveralls
Copy link

coveralls commented Aug 14, 2017

Coverage Status

Coverage increased (+0.4%) to 65.548% when pulling 465698b on jashnani:databaseAPI2 into df28f54 on ni:master.

@coveralls
Copy link

coveralls commented Aug 14, 2017

Coverage Status

Coverage increased (+0.4%) to 65.548% when pulling 4198345 on jashnani:databaseAPI2 into df28f54 on ni:master.

2 similar comments
@coveralls
Copy link

Coverage Status

Coverage increased (+0.4%) to 65.548% when pulling 4198345 on jashnani:databaseAPI2 into df28f54 on ni:master.

@coveralls
Copy link

Coverage Status

Coverage increased (+0.4%) to 65.548% when pulling 4198345 on jashnani:databaseAPI2 into df28f54 on ni:master.

@jashnani jashnani force-pushed the databaseAPI2 branch 2 times, most recently from 22e290d to cb7e7f8 Compare August 14, 2017 21:13
@coveralls
Copy link

coveralls commented Aug 14, 2017

Coverage Status

Coverage increased (+0.4%) to 65.548% when pulling cb7e7f8 on jashnani:databaseAPI2 into 12a4cfe on ni:master.

@coveralls
Copy link

coveralls commented Aug 14, 2017

Coverage Status

Coverage increased (+0.4%) to 65.548% when pulling 2690d37 on jashnani:databaseAPI2 into 12a4cfe on ni:master.

from nixnet import _funcs


class Databases(collections.Mapping):
Copy link
Contributor

Choose a reason for hiding this comment

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

Resolved

print(list(sys.databases))
num_databases = len(sys.databases)
database_alias = 'test_database'
database_filepath = os.path.join(os.path.dirname(__file__), '..', 'nixnet_examples\databases\custom_database.dbc')
Copy link
Contributor

Choose a reason for hiding this comment

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

For clean up after this, we should have another dirname rather than ..

@coveralls
Copy link

coveralls commented Aug 14, 2017

Coverage Status

Coverage increased (+0.4%) to 65.548% when pulling 16b3bc4 on jashnani:databaseAPI2 into 12a4cfe on ni:master.

@jashnani jashnani force-pushed the databaseAPI2 branch 2 times, most recently from 4540bca to c323383 Compare August 14, 2017 22:00
@coveralls
Copy link

coveralls commented Aug 14, 2017

Coverage Status

Coverage increased (+0.4%) to 65.548% when pulling 4540bca on jashnani:databaseAPI2 into 12a4cfe on ni:master.

@coveralls
Copy link

coveralls commented Aug 14, 2017

Coverage Status

Coverage increased (+0.4%) to 65.548% when pulling c323383 on jashnani:databaseAPI2 into 12a4cfe on ni:master.

@coveralls
Copy link

Coverage Status

Coverage increased (+0.4%) to 65.548% when pulling eb7f960 on jashnani:databaseAPI2 into 12a4cfe on ni:master.

1 similar comment
@coveralls
Copy link

coveralls commented Aug 14, 2017

Coverage Status

Coverage increased (+0.4%) to 65.548% when pulling eb7f960 on jashnani:databaseAPI2 into 12a4cfe on ni:master.

@jashnani jashnani merged commit ae0a33c into ni:master Aug 14, 2017
@jashnani jashnani deleted the databaseAPI2 branch August 14, 2017 22:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Implement minimal Database API functionality

4 participants