-
Notifications
You must be signed in to change notification settings - Fork 448
External Content Support (Databases and Tables) #445
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
19fd25f
2239612
28a66d0
751901b
0670e42
9641298
c2e7ac9
bb84698
6cc5567
ae27f28
b1f629e
0ca12d9
f729296
cbccf3f
a4abfb1
326f224
ac59af7
38a2700
79b611e
e751068
9f10736
062fa85
a8b1878
d1d0b62
08739ee
bfbd864
69f13b3
2fda34b
a1c68e5
81df01f
333685d
0e36d76
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,69 @@ | ||
| import xml.etree.ElementTree as ET | ||
|
|
||
| from .property_decorators import property_is_enum, property_not_empty | ||
| from .exceptions import UnpopulatedPropertyError | ||
|
|
||
|
|
||
| class ColumnItem(object): | ||
| def __init__(self, name, description=None): | ||
| self._id = None | ||
| self.description = description | ||
| self.name = name | ||
|
|
||
| @property | ||
| def id(self): | ||
| return self._id | ||
|
|
||
| @property | ||
| def name(self): | ||
| return self._name | ||
|
|
||
| @name.setter | ||
| @property_not_empty | ||
| def name(self, value): | ||
| self._name = value | ||
|
|
||
| @property | ||
| def description(self): | ||
| return self._description | ||
|
|
||
| @description.setter | ||
| def description(self, value): | ||
| self._description = value | ||
|
|
||
| @property | ||
| def remote_type(self): | ||
| return self._remote_type | ||
|
|
||
| def _set_values(self, id, name, description, remote_type): | ||
| if id is not None: | ||
| self._id = id | ||
| if name: | ||
| self._name = name | ||
| if description: | ||
| self.description = description | ||
| if remote_type: | ||
| self._remote_type = remote_type | ||
|
|
||
| @classmethod | ||
| def from_response(cls, resp, ns): | ||
| all_column_items = list() | ||
| parsed_response = ET.fromstring(resp) | ||
| all_column_xml = parsed_response.findall('.//t:column', namespaces=ns) | ||
|
|
||
| for column_xml in all_column_xml: | ||
| (id, name, description, remote_type) = cls._parse_element(column_xml, ns) | ||
| column_item = cls(name) | ||
| column_item._set_values(id, name, description, remote_type) | ||
| all_column_items.append(column_item) | ||
|
|
||
| return all_column_items | ||
|
|
||
| @staticmethod | ||
| def _parse_element(column_xml, ns): | ||
| id = column_xml.get('id', None) | ||
| name = column_xml.get('name', None) | ||
| description = column_xml.get('description', None) | ||
| remote_type = column_xml.get('remoteType', None) | ||
|
|
||
| return id, name, description, remote_type | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,260 @@ | ||
| import xml.etree.ElementTree as ET | ||
|
|
||
| from .permissions_item import Permission | ||
|
|
||
| from .property_decorators import property_is_enum, property_not_empty, property_is_boolean | ||
| from .exceptions import UnpopulatedPropertyError | ||
|
|
||
|
|
||
| class DatabaseItem(object): | ||
| class ContentPermissions: | ||
| LockedToProject = 'LockedToDatabase' | ||
| ManagedByOwner = 'ManagedByOwner' | ||
|
|
||
| def __init__(self, name, description=None, content_permissions=None): | ||
| self._id = None | ||
| self.name = name | ||
| self.description = description | ||
| self.content_permissions = content_permissions | ||
| self._certified = None | ||
| self._certification_note = None | ||
| self._contact_id = None | ||
|
|
||
| self._connector_url = None | ||
| self._connection_type = None | ||
| self._embedded = None | ||
| self._file_extension = None | ||
| self._file_id = None | ||
| self._file_path = None | ||
| self._host_name = None | ||
| self._metadata_type = None | ||
| self._mime_type = None | ||
| self._port = None | ||
| self._provider = None | ||
| self._request_url = None | ||
|
|
||
| self._permissions = None | ||
| self._default_table_permissions = None | ||
|
|
||
| self._tables = None # Not implemented yet | ||
|
|
||
| @property | ||
| def content_permissions(self): | ||
| return self._content_permissions | ||
|
|
||
| @property | ||
| def permissions(self): | ||
| if self._permissions is None: | ||
| error = "Project item must be populated with permissions first." | ||
| raise UnpopulatedPropertyError(error) | ||
| return self._permissions() | ||
|
|
||
| @property | ||
| def default_table_permissions(self): | ||
| if self._default_table_permissions is None: | ||
| error = "Project item must be populated with permissions first." | ||
| raise UnpopulatedPropertyError(error) | ||
| return self._default_table_permissions() | ||
|
|
||
| @content_permissions.setter | ||
| @property_is_enum(ContentPermissions) | ||
| def content_permissions(self, value): | ||
| self._content_permissions = value | ||
|
|
||
| @property | ||
| def id(self): | ||
| return self._id | ||
|
|
||
| @property | ||
| def name(self): | ||
| return self._name | ||
|
|
||
| @name.setter | ||
| @property_not_empty | ||
| def name(self, value): | ||
| self._name = value | ||
|
|
||
| @property | ||
| def description(self): | ||
| return self._description | ||
|
|
||
| @description.setter | ||
| def description(self, value): | ||
| self._description = value | ||
|
|
||
| @property | ||
| def embedded(self): | ||
| return self._embedded | ||
|
|
||
| @property | ||
| def certified(self): | ||
| return self._certified | ||
|
|
||
| @certified.setter | ||
| @property_is_boolean | ||
| def certified(self, value): | ||
| self._certified = value | ||
|
|
||
| @property | ||
| def certification_note(self): | ||
| return self._certification_note | ||
|
|
||
| @certification_note.setter | ||
| def certification_note(self, value): | ||
| self._certification_note = value | ||
|
|
||
| @property | ||
| def metadata_type(self): | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This one and a few below are not defined in the constructor. Also for port, should it be an integer property?
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Not all of these properties will exist for all types (though Right now it lazily adds them based on whatever we pass to Happy to do it if you think we should. It raises the question again about what makes it into constructor vs what doesn't.
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In my opinion, I think it's good practice to have all properties defined in the constructor. That way we can easily see all the properties that the class can have, and avoids the possibility of an error from accessing it before assigned from the response. |
||
| return self._metadata_type | ||
|
|
||
| @property | ||
| def host_name(self): | ||
| return self._host_name | ||
|
|
||
| @property | ||
| def port(self): | ||
| return self._port | ||
|
|
||
| @property | ||
| def file_path(self): | ||
| return self._file_path | ||
|
|
||
| @property | ||
| def provider(self): | ||
| return self._provider | ||
|
|
||
| @property | ||
| def mime_type(self): | ||
| return self._mime_type | ||
|
|
||
| @property | ||
| def connector_url(self): | ||
| return self._connector_url | ||
|
|
||
| @property | ||
| def connection_type(self): | ||
| return self._connection_type | ||
|
|
||
| @property | ||
| def request_url(self): | ||
| return self._request_url | ||
|
|
||
| @property | ||
| def file_extension(self): | ||
| return self._file_extension | ||
|
|
||
| @property | ||
| def file_id(self): | ||
| return self._file_id | ||
|
|
||
| @property | ||
| def contact_id(self): | ||
| return self._contact_id | ||
|
|
||
| @contact_id.setter | ||
| def contact_id(self, value): | ||
| self._contact_id = value | ||
|
|
||
| @property | ||
| def tables(self): | ||
| if self._tables is None: | ||
| error = "Database must be populated with tables first." | ||
| raise UnpopulatedPropertyError(error) | ||
| # Each call to `.tables` should create a new pager, this just runs the callable | ||
| return self._tables() | ||
|
|
||
| def _set_values(self, database_values): | ||
| # ID & Settable | ||
| if 'id' in database_values: | ||
| self._id = database_values['id'] | ||
|
|
||
| if 'contact' in database_values: | ||
| self._contact_id = database_values['contact']['id'] | ||
|
|
||
| if 'name' in database_values: | ||
| self._name = database_values['name'] | ||
|
|
||
| if 'description' in database_values: | ||
| self._description = database_values['description'] | ||
|
|
||
| if 'isCertified' in database_values: | ||
| self._certified = string_to_bool(database_values['isCertified']) | ||
|
|
||
| if 'certificationNote' in database_values: | ||
| self._certification_note = database_values['certificationNote'] | ||
|
|
||
| # Not settable, alphabetical | ||
|
|
||
| if 'connectionType' in database_values: | ||
| self._connection_type = database_values['connectionType'] | ||
|
|
||
| if 'connectorUrl' in database_values: | ||
| self._connector_url = database_values['connectorUrl'] | ||
|
|
||
| if 'contentPermissions' in database_values: | ||
| self._content_permissions = database_values['contentPermissions'] | ||
|
|
||
| if 'embedded' in database_values: | ||
| self._embedded = string_to_bool(database_values['embedded']) | ||
|
|
||
| if 'fileExtension' in database_values: | ||
| self._file_extension = database_values['fileExtension'] | ||
|
|
||
| if 'fileId' in database_values: | ||
| self._file_id = database_values['fileId'] | ||
|
|
||
| if 'filePath' in database_values: | ||
| self._file_path = database_values['filePath'] | ||
|
|
||
| if 'hostName' in database_values: | ||
| self._host_name = database_values['hostName'] | ||
|
|
||
| if 'mimeType' in database_values: | ||
| self._mime_type = database_values['mimeType'] | ||
|
|
||
| if 'port' in database_values: | ||
| self._port = int(database_values['port']) | ||
|
|
||
| if 'provider' in database_values: | ||
| self._provider = database_values['provider'] | ||
|
|
||
| if 'requestUrl' in database_values: | ||
| self._request_url = database_values['requestUrl'] | ||
|
|
||
| if 'type' in database_values: | ||
| self._metadata_type = database_values['type'] | ||
|
|
||
| def _set_permissions(self, permissions): | ||
| self._permissions = permissions | ||
|
|
||
| def _set_tables(self, tables): | ||
| self._tables = tables | ||
|
|
||
| def _set_default_permissions(self, permissions, content_type): | ||
| setattr(self, "_default_{content}_permissions".format(content=content_type), permissions) | ||
|
|
||
| @classmethod | ||
| def from_response(cls, resp, ns): | ||
| all_database_items = list() | ||
| parsed_response = ET.fromstring(resp) | ||
| all_database_xml = parsed_response.findall('.//t:database', namespaces=ns) | ||
|
|
||
| for database_xml in all_database_xml: | ||
| parsed_database = cls._parse_element(database_xml, ns) | ||
| database_item = cls(parsed_database['name']) | ||
| database_item._set_values(parsed_database) | ||
| all_database_items.append(database_item) | ||
| return all_database_items | ||
|
|
||
| @staticmethod | ||
| def _parse_element(database_xml, ns): | ||
| database_values = database_xml.attrib.copy() | ||
| contact = database_xml.find('.//t:contact', namespaces=ns) | ||
| if contact is not None: | ||
| database_values['contact'] = contact.attrib.copy() | ||
| return database_values | ||
|
|
||
|
|
||
| # Used to convert string represented boolean to a boolean type | ||
| def string_to_bool(s): | ||
| return s.lower() == 'true' | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This field should also be defined in the constructor to keep it consistent.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
That's a good point -- what is the pattern here?
That's not a user-editable attribute, and it's something we set purely from the XML response, sometimes we put these in constructors, sometimes we don't.
I can't add it here, just wondering.