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
Full layout metadata, layout named valueList, dateformats #63
Conversation
fmrest/server.py
Outdated
for use in a form SELECT (tuple) | ||
|
||
Example: | ||
values = fms.get_layout_valueList() |
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.
Missing name parameter in example: fms.get_layout_valueList('myListValue', 'myOptionalLayoutName')
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.
Thanks for the PR. I left some comments in the code for suggestions/questions/remarks. Let me know what you think.
fmrest/const.py
Outdated
@@ -10,6 +10,7 @@ | |||
TIMEOUT = int(os.environ.get('fmrest_timeout', 10)) | |||
|
|||
API_VERSIONS = ('v1', 'v2', 'vLatest') | |||
API_DATEFORMATS = ('0', '1', '2') |
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.
I'd prefer API_DATE_FORMATS
here to be in line with the snake_case
formatting.
Also, can't we use integers here? The API seems to be OK with sending the date format as integer (e.g. in the find
request's POST data). In the past some parameters caused issues when sent as integer (for example offset
and limit
weirdly always needed to be strings to be accepted). But I think they fixed this in the DAPI before introducing the dateformats
parameter.
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.
OK for snake_case
formatting.
I choose str to be in line with API format specified in apidoc:
https://you_host/fmi/data/apidoc/#tag/records/operation/getRecords
QUERY PARAMETERS
dateformats
string
The date format. Use 0 for US, 1 for file locale, or 2 for ISO8601. If not specified, the default value is 0.
fmrest/server.py
Outdated
@@ -371,7 +371,8 @@ def get_record(self, record_id: int, portals: Optional[List[Dict]] = None, | |||
scripts: Optional[Dict[str, List]] = None, | |||
layout: Optional[str] = None, | |||
request_layout: Optional[str] = None, | |||
response_layout: Optional[str] = None) -> Record: | |||
response_layout: Optional[str] = None, | |||
dateformats: Optional[str] = None) -> Record: |
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.
I would suggest to call the parameter date_format
(singular) to be in line with the other naming, even though it later gets translated to dateformats
(plural, one word) for the API. This would then need to be consistent for all the other methods as well.
Also, it would be nice if we could pass keywords instead of the numbers; maybe us
, file
, iso-8601
? Not passing a value or passing the default None
would default to whatever the default of the DAPI is.
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.
Ok to use date_format
parameter if you prefer. I just use same name to avoid typing error ;-)
If we use keywords, we also need to had logic to convert them.
Is this code a way to to that ?
API_DATE_FORMATS = [('us', '0'), ('file', '1'), ('iso-8601', '2')]
date_format = 'file'
matching_format = next((format[1] for format in API_DATE_FORMATS if format[0] == date_format), None)
if matching_format:
params['dateformats'] = matching_format
fmrest/server.py
Outdated
@@ -413,6 +418,9 @@ def get_record(self, record_id: int, portals: Optional[List[Dict]] = None, | |||
|
|||
params = build_portal_params(portals, True) if portals else {} | |||
|
|||
# Date format: 0-US (MM/DD/YYYY, default), 1-file locale, 2-ISO8601 (YYYY-MM-DD) | |||
params['dateformats'] = dateformats if dateformats in API_DATEFORMATS else '0' |
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.
Since the default format could change for the DAPI in a different API version, I wonder if it might be better to not specify any date format when the user doesn't explicitly passes one to this method. Otherwise, we might hardcode the 0 = US
value, even if a v3
of the DAPI might switch to a default value of iso-8601
.
What do you think?
Preferably, the default for the library would be the ISO format, but I guess that's too late and wouldn't be backward compatible :-)
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.
I also think it would make more sense to have the ISO format by default, but Claris has chosen the 0-US format.
Is it a good idea to change the default behavior of FMS?
And it may indeed be a little late to change the default value for those already using the library.
fmrest/server.py
Outdated
@@ -797,11 +820,13 @@ def get_scripts(self) -> Dict: | |||
|
|||
@_with_auto_relogin | |||
def get_layout(self, layout: Optional[str] = None) -> Dict: | |||
"""Fetches layout metadata and returns Dict instance | |||
"""Fetches layout metadata and returns "fieldMetaData" Dict instance |
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.
You're right that it's not optimal that get_layout
only returns the fieldMetaData
. However, instead of creating a new function that does the same except for its return value, I would suggest we give get_layout
a new parameter to control what is returned (and default to the current case to remain backward compatible). For example: metadata=fields
in the function signature, and then have options for portals
, value_lists
, or all
.
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.
Like this ?
def get_layout(self, layout: Optional[str] = None, metadata: Optional[str] = None) -> Dict:
"""Fetches layout metadata and returns Dict instance of metadata parameters
Parameters
-----------
layout : str, optional
Sets the layout name for this request. This takes precedence over
the value stored in the Server instance's layout attribute
metadata : str, optional
Options to get all or specifics metadatas.
Choices are 'fields', 'portals', 'value_lists' or 'all'.
Default is 'fields'
"""
target_layout = layout if layout else self.layout
path = self._get_api_path('meta.layouts') + f'/{target_layout}'
response = self._call_filemaker('GET', path)
if metadata == 'all':
return response
elif metadata == 'portals':
return response.get('portalMetaData', None)
elif metadata == 'value_lists':
return response.get('valueLists', None)
else:
return response.get('fieldMetaData', None)
fmrest/server.py
Outdated
|
||
return response | ||
|
||
def get_layout_valueList(self, name: str, layout: Optional[str] = None): |
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.
I would suggest renaming get_layout_valueList
to get_value_list_values
such that it's clear that we don't fetch meta data for the value list (as we might want to do later on), but only the values itself.
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.
Ok
fmrest/server.py
Outdated
|
||
def get_layout_valueList(self, name: str, layout: Optional[str] = None): | ||
"""Retrieves layout metadata and returns a list of "name" values | ||
for use in a form SELECT (tuple) |
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.
You write "returns a list of..." but at the end return a tuple. I think returning a list of tuples would actually be a good approach (i.e. [("a", "A"), ("b", "B"), ...]
) such that users could still modify the returned list without converting it back list
again.
Or was there a specific reason for using a tuple of tuples vs. a list of tuples?
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.
I agree with having a list of tuples as the default output. Therefore, I suggest adding a parameter with the options 'list' (default) and 'tuple' to choose the output format.
In my case, I use it with Django for the CHOICES of a form SELECT, as in a FileMaker Pop-up. For this usage, I specifically need a tuple of tuples.
fmrest/server.py
Outdated
name : str | ||
The list name to retreive values | ||
fms : object | ||
fmrest instance to query |
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.
Outdated comment for fms
?
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.
Correct, because I now use 'self' instead.
fmrest/server.py
Outdated
""" | ||
target_layout = layout if layout else self.layout | ||
metadata = self.get_layout_metadata(target_layout) | ||
valueLists = metadata.get('valueLists', None) |
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.
Please use snake case for the variable name, i.e. value_lists
.
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.
Ok
fmrest/server.py
Outdated
for value in evaluation_values: | ||
actual_value = value['value'] | ||
display_value = value['displayValue'] | ||
options.append((actual_value, display_value)) |
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.
I think we could speed up the loop where you identify the right value list with an early return
or break
. Something like this (you don't have to use a list comprehension, only the break
really makes the difference):
for vlist in value_lists:
if vlist['name'] == name:
values += [(v['value'], v['displayValue']) for v in vlist['values']]
break
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.
yes, well done!
fmrest/server.py
Outdated
metadata = self.get_layout_metadata(target_layout) | ||
valueLists = metadata.get('valueLists', None) | ||
|
||
# Initializing the list for the Select form |
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.
I would prefer not to reference a potential use case (e.g. building the list for an HTML form, or such) in the method since this would be an implementation detail of the user. They might use it to populate a form, or do something completely different with the returned data.
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.
ok, got it
Thanks for your comments. See replies. |
Thanks for your work! Looks all good to me. Just two small things: 1.) Can you put the date keyword logic into its own method, such that we have this logic only at one place? Something like this:
2.) I would prefer not to have an output parameter for the Thanks again for your work. Happy to merge with these last changes. |
fix owner comments
I agree with your comments. Code committed. |
Merged, thanks! |
The current version of fmrest limits the data returned by the get_layout function to fieldMetaData, omitting PortalMetaData and valueLists.
=>
return response.get('fieldMetaData', None)
As I also needed access to the valueLists for my own development, I had to modify fmrest directly.