8000 Added the ability to create and modify fields, including aliases and … by KernpunktAnalytics · Pull Request #111 · tableau/document-api-python · GitHub
[go: up one dir, main page]

Skip to content

Added the ability to create and modify fields, including aliases 8000 and … #111

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

Merged

Conversation

KernpunktAnalytics
Copy link
Contributor

This PR fixes #103

Adds the ability to:

  • Modify attributes of a field
  • Add aliases to a field
  • Modify and add calculations to a field
  • Create and add new fields
  • Remove fields
  • Create calculations

Includes tests.

@t8y8
Copy link
Contributor
t8y8 commented Dec 9, 2016

Confirmed we've got the CLA and all is in order!

Copy link
Contributor
@graysonarts graysonarts left a comment

Choose a reason for hiding this comment

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

Awesome work! Thank you so much for all of the contributions you've been making while I was away!

I had a few small things that should probably be changed before we merge this in. It's mostly around decomposing some of the functions into smaller chunks (my personal preference)

"""
# TODO: A better approach would be to create an empty column and then
# use the input validation from its "Field"-object-representation to set values.
# However, creating an empty column causes errors :(
Copy link
Contributor

Choose a reason for hiding this comment

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

What error is it causing?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The Field objects init() expects some sort of XML as input. Currently, it's not possible to initialize an "empty" Field and fill it with values later on.

name: Name of the new Field. String.
datatype: Datatype of the new field. String.
role: Role of the new field. String.
type: Type of the new field. String.
Copy link
Contributor

Choose a reason for hiding this comment

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

type is a reserved word. use either field_type (preferred) or type_

caption = name.replace('[', '').replace(']', '').title()

# Create the elements
column = ET.Element('column')
Copy link
Contributor

Choose a reason for hiding this comment

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

Could you extract the xml manipulation into a separate function that is a column tag factory? So it read more like this:

column = xml_factory.create_column(caption, datatype, role, type_, name)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done.

def calculations(self):
""" Returns all calculated fields.
"""
# TODO: There is a default [Number of Records] calculation.
Copy link
Contributor

Choose a reason for hiding this comment

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

I think as long as we don't allow people to edit [Number of Records] it's okay to return it.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Great, I've removed the TODO.

The new calculated field that was created. Field.
"""
# Dynamically create the name of the field
name = '[Calculation_{}]'.format(str(uuid4().int)[:18])
Copy link
Contributor

Choose a reason for hiding this comment

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

I think we have a function called make_unique or something that does this. I'll take a look to see if I can find it after doing the review.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

There is one in datasource.py (base36encode), but it's used for a different purpose and the value it returns has a different format than the one required here.

Sidenote: you could replace
ALPHABET = ....
with
ALPHABET = string.digits + string.ascii_lowercase

Copy link
Contributor

Choose a reason for hiding this comment

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

Yeah, I left it the simple way so the function is easy to grok :)

(And I copy pasted from SO)

@@ -24,6 +24,21 @@
]


def argument_is_one_of(*allowed_values):
Copy link
Contributor

Choose a reason for hiding this comment

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

I think (again, I need to go check after the review, I might be confusing this with TSC) that we have a module specifically for property decorators. This should probably be in there. If we don't have one, let's start one :)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's TSC ;)
I've created a new module and put the decorator functions in there.

@@ -43,14 +58,18 @@ def __init__(self, column_xml=None, metadata_xml=None):
setattr(self, '_{}'.format(attrib), None)
self._worksheets = set()

self._xml = None
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this line needed?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Actually, I have no idea why this is even here. Removed.

self._xml.set('role', role)

@property
def type(self):
Copy link
Contributor

Choose a reason for hiding this comment

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

type is a reserved word. Per pep8, it should be type_, or something more specific like column_type or field_type

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I've removed all 'free floating' variables that are named 'type'.
I didn't change the properties that are named 'type', since they correspond to a XML attribute named 'type' and changing it will throw off anyone using the API. The way it's working, using type() should not be affected by this.

"""
return hash(ET.tostring(self.tds._datasourceTree.getroot()))

def test_change_values(self):
Copy link
Contributor

Choose a reason for hiding this comment

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

There are many similarities between the code in this test and the next one. Is there a way to extract the mostly common code and replace the differences with arguments to the new function?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Extracted code into a new function.

self.tds.remove_field(field)
self.assertNotEqual(state, self.current_hash())

def test_change_aliases(self):
Copy link
Contributor

Choose a reason for hiding this comment

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

This one is also very similar to the two above that I previously commented on.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

See above.

@KernpunktAnalytics
Copy link
Contributor Author

Hey Russel,

thanks for the feedback. I'll try to find some time for a rework either this or next week.

Cheers
Felix

@KernpunktAnalytics
Copy link
Contributor Author

I've addressed your feedback and updated the PR.

Cheers

@@ -70,6 +70,16 @@ def get_xml_from_archive(filename):
return xml_tree


def create_column(caption, datatype, role, field_type, name):
Copy link
Contributor

Choose a reason for hiding this comment

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

This looks fine, but xfile is an odd place, I say toss it back in fields.py -- did @RussTheAerialist recommend it go here?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@RussTheAerialist wanted this as a column tag factory so it would read

column = xml_factory.create_column(caption, datatype, role, type_, name)

So I put it into xfile, since it's the closest thing to a dedicated XML class that I could find in this project. I'll put it back into fields.py

self._xml.append(aliases)

# find out if an alias with this key already exists and use it
existing_alias = [tag for tag in aliases.findall('alias') if tag.get('key') == key]
Copy link
Contributor

Choose a reason for hiding this comment

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

A slightly more idiomatic way to write this is:

existing_alias = next((tag for tag in aliases.findall('alias') if tag.get('key') == key), None)
alias = existing_alias or ET.Element('alias')

Copy link
Contributor Author

Choose a reason for hiding this comment

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

That's neat, haven't seen this pattern before :)

Copy link
Contributor
@t8y8 t8y8 left a comment

Choose a reason for hiding this comment

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

🚀

After moving the function out of xfile I think I'm ready to give this a shipit!

If we do it early in a release cycle there's room to change it if we encounter anything, and I'd like to start playing with some of what it offers

@KernpunktAnalytics
Copy link
Contributor Author
KernpunktAnalytics commented Feb 4, 2017

Hey Tyler, I've moved the function into fields.py as requested.

Currently, I think there is no place this function can go without feeling misplaced.
IMHO, the whole Fields constructor should be refactored to work independent of any XML. The code of the current constructor looks like it should rather be a "from_column_xml" classmethod. Anyways, that would require some more consideration, and I feel this is out of the scope of this PR.

Cheers
Felix

@t8y8
Copy link
Contributor
t8y8 commented Feb 4, 2017

🇩🇪 🚀 🌔

@t8y8 t8y8 merged commit 562a54e into tableau:development Feb 16, 2017
jacalata pushed a commit that referenced this pull request May 29, 2021
#111)

* Added the ability to create and modify fields, including aliases and calculated fields
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.

4 participants
0