Skip to content
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

Merged
merged 12 commits into from
Jan 18, 2024
1 change: 1 addition & 0 deletions fmrest/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
TIMEOUT = int(os.environ.get('fmrest_timeout', 10))

API_VERSIONS = ('v1', 'v2', 'vLatest')
API_DATE_FORMATS = [('us', '0'), ('file', '1'), ('iso-8601', '2')]
API_PATH_PREFIX = '/fmi/data/{version}'
API_PATH: Dict[str, Any] = {
'meta': {
Expand Down
86 changes: 78 additions & 8 deletions fmrest/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import requests
from .utils import (request, build_portal_params, build_script_params,
filename_from_url, PlaceholderDict)
from .const import (PORTAL_PREFIX, FMSErrorCode, API_VERSIONS, API_PATH_PREFIX,
from .const import (PORTAL_PREFIX, FMSErrorCode, API_VERSIONS, API_DATE_FORMATS, API_PATH_PREFIX,
API_PATH)
from .exceptions import BadJSON, FileMakerError, RecordError
from .record import Record
Expand Down Expand Up @@ -155,6 +155,9 @@ def _get_api_path(self, resource: str,
path.format_map(PlaceholderDict(database=self.database,
layout=request_layout)))

def _date_format_for_keyword(self, keyword: str) -> Optional[str]:
return next((format[1] for format in API_DATE_FORMATS if format[0] == keyword), None)

def _with_auto_relogin(f):
@wraps(f)
def wrapper(self, *args, **kwargs):
Expand Down Expand Up @@ -371,7 +374,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,
date_format: Optional[str] = None) -> Record:
"""Fetches record with given ID and returns Record instance

Parameters
Expand Down Expand Up @@ -403,6 +407,13 @@ def get_record(self, record_id: int, portals: Optional[List[Dict]] = None,
Set the response layout. This is helpful, for example, if you
want to limit the number of fields/portals being returned and have
a dedicated response layout.
date_format : str, optional
The date format. Choices are:
'us' for US format MM/DD/YYYY,
'file' for file locale format,
'iso-8601' for ISO format YYYY-MM-DD.
If not specified, the default value is 'us'.
Note that dates should always be sent in the US format when creating or editing a record.
"""
if layout is not None:
warnings.warn('layout parameter is deprecated and will be removed '
Expand All @@ -413,6 +424,8 @@ def get_record(self, record_id: int, portals: Optional[List[Dict]] = None,

params = build_portal_params(portals, True) if portals else {}

params['dateformats'] = self._date_format_for_keyword(date_format)

# set response layout; layout param is only handled for backward-
# compatibility
params['layout.response'] = layout if layout else response_layout
Expand Down Expand Up @@ -496,7 +509,8 @@ def get_records(self, offset: int = 1, limit: int = 100,
scripts: Optional[Dict[str, List]] = None,
layout: Optional[str] = None,
request_layout: Optional[str] = None,
response_layout: Optional[str] = None) -> Foundset:
response_layout: Optional[str] = None,
date_format: Optional[str] = None) -> Foundset:
"""Requests all records with given offset and limit and returns result as
(sorted) Foundset instance.

Expand Down Expand Up @@ -528,6 +542,13 @@ def get_records(self, offset: int = 1, limit: int = 100,
Set the response layout. This is helpful, for example, if you
want to limit the number of fields/portals being returned and have
a dedicated response layout.
date_format : str, optional
The date format. Choices are:
'us' for US format MM/DD/YYYY,
'file' for file locale format,
'iso-8601' for ISO format YYYY-MM-DD.
If not specified, the default value is 'us'.
Note that dates should always be sent in the US format when creating or editing a record.
"""
if layout is not None:
warnings.warn('layout parameter is deprecated and will be removed '
Expand All @@ -539,6 +560,8 @@ def get_records(self, offset: int = 1, limit: int = 100,
params['_offset'] = offset
params['_limit'] = limit

params['dateformats'] = self._date_format_for_keyword(date_format)

# set response layout; layout param is only handled for backward-
# compatibility
params['layout.response'] = layout if layout else response_layout
Expand All @@ -564,7 +587,8 @@ def find(self, query: List[Dict[str, Any]],
scripts: Optional[Dict[str, List]] = None,
layout: Optional[str] = None,
request_layout: Optional[str] = None,
response_layout: Optional[str] = None) -> Foundset:
response_layout: Optional[str] = None,
date_format: Optional[str] = None) -> Foundset:
"""Finds all records matching query and returns result as a Foundset instance.

Parameters
Expand Down Expand Up @@ -605,6 +629,13 @@ def find(self, query: List[Dict[str, Any]],
Set the response layout. This is helpful, for example, if you
want to limit the number of fields/portals being returned and have
a dedicated response layout.
date_format : str, optional
The date format. Choices are:
'us' for US format MM/DD/YYYY,
'file' for file locale format,
'iso-8601' for ISO format YYYY-MM-DD.
If not specified, the default value is 'us'.
Note that dates should always be sent in the US format when creating or editing a record.
"""
if layout is not None:
warnings.warn('layout parameter is deprecated and will be removed '
Expand All @@ -617,6 +648,7 @@ def find(self, query: List[Dict[str, Any]],
'sort': sort,
'limit': str(limit),
'offset': str(offset),
'dateformats': self._date_format_for_keyword(date_format),
# "layout" param is only handled for backwards-compatibility
'layout.response': layout if layout else response_layout
}
Expand Down Expand Up @@ -796,19 +828,57 @@ def get_scripts(self) -> Dict:
return response.get('scripts', None)

@_with_auto_relogin
def get_layout(self, layout: Optional[str] = None) -> Dict:
"""Fetches layout metadata and returns Dict instance
def get_layout(self, layout: Optional[str] = None, metadata: Optional[str] = None) -> Dict:
"""Fetches layout metadata and returns Dict instance of metadata parameter

Parameters
-----------
none
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)

return response.get('fieldMetaData', None)
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)

@_with_auto_relogin
def get_value_list_values(self, name: str, layout: Optional[str] = None) -> List[Tuple[str, str]]:
"""Retrieves layout metadata and returns a list of tuple of (value, display value) of a named FileMaker value list
in the format [('a','A'), ('b','B'), ('c','C')]

Parameters
-----------
name : str
The list name to retreive values
layout : str, optional
Sets the layout name for this request. This takes precedence over
the value stored in the Server instance's layout attribute
"""
target_layout = layout if layout else self.layout
value_lists = self.get_layout(layout=target_layout, metadata='value_lists')

values = []

for vlist in value_lists:
if vlist['name'] == name:
values += [(v['value'], v['displayValue']) for v in vlist['values']]
break

return values

def _call_filemaker(self, method: str, path: str,
data: Optional[Dict] = None,
Expand Down