hdx-python-api 6.6.4__py3-none-any.whl → 6.6.6__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
hdx/api/_version.py CHANGED
@@ -1,5 +1,6 @@
1
- # file generated by setuptools-scm
1
+ # file generated by vcs-versioning
2
2
  # don't change, don't track in version control
3
+ from __future__ import annotations
3
4
 
4
5
  __all__ = [
5
6
  "__version__",
@@ -10,25 +11,14 @@ __all__ = [
10
11
  "commit_id",
11
12
  ]
12
13
 
13
- TYPE_CHECKING = False
14
- if TYPE_CHECKING:
15
- from typing import Tuple
16
- from typing import Union
17
-
18
- VERSION_TUPLE = Tuple[Union[int, str], ...]
19
- COMMIT_ID = Union[str, None]
20
- else:
21
- VERSION_TUPLE = object
22
- COMMIT_ID = object
23
-
24
14
  version: str
25
15
  __version__: str
26
- __version_tuple__: VERSION_TUPLE
27
- version_tuple: VERSION_TUPLE
28
- commit_id: COMMIT_ID
29
- __commit_id__: COMMIT_ID
16
+ __version_tuple__: tuple[int | str, ...]
17
+ version_tuple: tuple[int | str, ...]
18
+ commit_id: str | None
19
+ __commit_id__: str | None
30
20
 
31
- __version__ = version = '6.6.4'
32
- __version_tuple__ = version_tuple = (6, 6, 4)
21
+ __version__ = version = '6.6.6'
22
+ __version_tuple__ = version_tuple = (6, 6, 6)
33
23
 
34
24
  __commit_id__ = commit_id = None
hdx/data/dataset.py CHANGED
@@ -3,8 +3,7 @@
3
3
  import json
4
4
  import logging
5
5
  import sys
6
- import warnings
7
- from collections.abc import Callable, Iterable, Iterator, Mapping, Sequence
6
+ from collections.abc import Callable, Iterable, Mapping, Sequence
8
7
  from copy import deepcopy
9
8
  from datetime import datetime
10
9
  from pathlib import Path
@@ -26,7 +25,6 @@ from hdx.utilities.dateparse import (
26
25
  parse_date_range,
27
26
  )
28
27
  from hdx.utilities.dictandlist import merge_two_dictionaries
29
- from hdx.utilities.downloader import Download
30
28
  from hdx.utilities.loader import load_json
31
29
  from hdx.utilities.saver import save_iterable, save_json
32
30
  from hdx.utilities.uuid import is_valid_uuid
@@ -40,7 +38,6 @@ import hdx.data.user as user
40
38
  import hdx.data.vocabulary as vocabulary
41
39
  from hdx.api.configuration import Configuration
42
40
  from hdx.api.locations import Locations
43
- from hdx.api.utilities.dataset_title_helper import DatasetTitleHelper
44
41
  from hdx.api.utilities.date_helper import DateHelper
45
42
  from hdx.api.utilities.filestore_helper import FilestoreHelper
46
43
  from hdx.data.hdxobject import HDXError, HDXObject
@@ -203,7 +200,7 @@ class Dataset(HDXObject):
203
200
  session: Session | None = None,
204
201
  ) -> None:
205
202
  """Save dataset to JSON. If follow_urls is True, resource urls that point to
206
- datasets, HXL proxy urls etc. are followed to retrieve final urls.
203
+ datasets are followed to retrieve final urls.
207
204
 
208
205
  Args:
209
206
  path: Path to save dataset
@@ -397,35 +394,6 @@ class Dataset(HDXObject):
397
394
  """
398
395
  return len(self._resources)
399
396
 
400
- def reorder_resources(
401
- self, resource_ids: Sequence[str], hxl_update: bool = True
402
- ) -> None:
403
- """Reorder resources in dataset according to provided list. Resources are
404
- updated in the dataset object to match new order. However, the dataset is not
405
- refreshed by rereading from HDX. If only some resource ids are supplied then
406
- these are assumed to be first and the other resources will stay in their
407
- original order.
408
-
409
- Args:
410
- resource_ids: List of resource ids
411
-
412
- Returns:
413
- None
414
- """
415
- dataset_id = self.data.get("id")
416
- if not dataset_id:
417
- raise HDXError(
418
- "Dataset has no id! It must be read, created or updated first."
419
- )
420
- data = {"id": dataset_id, "order": resource_ids}
421
- results = self._write_to_hdx("reorder", data)
422
- ordered_ids = results["order"]
423
- reordered_resources = []
424
- for resource_id in ordered_ids:
425
- resource = next(x for x in self._resources if x["id"] == resource_id)
426
- reordered_resources.append(resource)
427
- self._resources = reordered_resources
428
-
429
397
  def move_resource(
430
398
  self,
431
399
  resource_name: str,
@@ -519,6 +487,33 @@ class Dataset(HDXObject):
519
487
  self.init_resources()
520
488
  self.separate_resources()
521
489
 
490
+ def reorder_resources(self, resource_ids: Sequence[str]) -> None:
491
+ """Reorder resources in dataset according to provided list. Resources are
492
+ updated in the dataset object to match new order. However, the dataset is not
493
+ refreshed by rereading from HDX. If only some resource ids are supplied then
494
+ these are assumed to be first and the other resources will stay in their
495
+ original order.
496
+
497
+ Args:
498
+ resource_ids: List of resource ids
499
+
500
+ Returns:
501
+ None
502
+ """
503
+ dataset_id = self.data.get("id")
504
+ if not dataset_id:
505
+ raise HDXError(
506
+ "Dataset has no id! It must be read, created or updated first."
507
+ )
508
+ data = {"id": dataset_id, "order": resource_ids}
509
+ results = self._write_to_hdx("reorder", data)
510
+ ordered_ids = results["order"]
511
+ reordered_resources = []
512
+ for resource_id in ordered_ids:
513
+ resource = next(x for x in self._resources if x["id"] == resource_id)
514
+ reordered_resources.append(resource)
515
+ self._resources = reordered_resources
516
+
522
517
  def _dataset_load_from_hdx(self, id_or_name: str) -> bool:
523
518
  """Loads the dataset given by either id or name from HDX
524
519
 
@@ -1080,7 +1075,6 @@ class Dataset(HDXObject):
1080
1075
  remove_additional_resources: bool = False,
1081
1076
  match_resource_order: bool = False,
1082
1077
  create_default_views: bool = True,
1083
- hxl_update: bool = True,
1084
1078
  **kwargs: Any,
1085
1079
  ) -> dict:
1086
1080
  """Check if dataset exists in HDX and if so, update it. match_resources_by_metadata uses ids if they are
@@ -1146,7 +1140,6 @@ class Dataset(HDXObject):
1146
1140
  remove_additional_resources: bool = False,
1147
1141
  match_resource_order: bool = False,
1148
1142
  create_default_views: bool = True,
1149
- hxl_update: bool = True,
1150
1143
  **kwargs: Any,
1151
1144
  ) -> dict:
1152
1145
  """Check if dataset exists in HDX and if so, update it, otherwise create it. match_resources_by_metadata uses
@@ -1475,17 +1468,6 @@ class Dataset(HDXObject):
1475
1468
  self.data.get("dataset_date"), date_format, today
1476
1469
  )
1477
1470
 
1478
- def get_reference_period(
1479
- self,
1480
- date_format: str | None = None,
1481
- today: datetime = now_utc(),
1482
- ) -> dict:
1483
- warnings.warn(
1484
- "get_reference_period() is deprecated, use get_time_period() instead",
1485
- DeprecationWarning,
1486
- )
1487
- return self.get_time_period(date_format, today)
1488
-
1489
1471
  def set_time_period(
1490
1472
  self,
1491
1473
  startdate: datetime | str,
@@ -1512,19 +1494,6 @@ class Dataset(HDXObject):
1512
1494
  startdate, enddate, ongoing, ignore_timeinfo
1513
1495
  )
1514
1496
 
1515
- def set_reference_period(
1516
- self,
1517
- startdate: datetime | str,
1518
- enddate: datetime | str | None = None,
1519
- ongoing: bool = False,
1520
- ignore_timeinfo: bool = True,
1521
- ) -> None:
1522
- warnings.warn(
1523
- "set_reference_period() is deprecated, use set_time_period() instead",
1524
- DeprecationWarning,
1525
- )
1526
- self.set_time_period(startdate, enddate, ongoing, ignore_timeinfo)
1527
-
1528
1497
  def set_time_period_year_range(
1529
1498
  self,
1530
1499
  dataset_year: str | int | Iterable,
@@ -1545,17 +1514,6 @@ class Dataset(HDXObject):
1545
1514
  ) = DateHelper.get_hdx_time_period_from_years(dataset_year, dataset_end_year)
1546
1515
  return retval
1547
1516
 
1548
- def set_reference_period_year_range(
1549
- self,
1550
- dataset_year: str | int | Iterable,
1551
- dataset_end_year: str | int | None = None,
1552
- ) -> list[int]:
1553
- warnings.warn(
1554
- "set_reference_period_year_range() is deprecated, use set_time_period_year_range() instead",
1555
- DeprecationWarning,
1556
- )
1557
- return self.set_time_period_year_range(dataset_year, dataset_end_year)
1558
-
1559
1517
  @classmethod
1560
1518
  def list_valid_update_frequencies(cls) -> list[str]:
1561
1519
  """List of valid update frequency values
@@ -2266,15 +2224,6 @@ class Dataset(HDXObject):
2266
2224
  dataset_resource.disable_dataset_preview()
2267
2225
  return preview_resource
2268
2226
 
2269
- def set_quickchart_resource(
2270
- self, resource: Union["Resource", dict, str, int]
2271
- ) -> "Resource":
2272
- warnings.warn(
2273
- "set_quickchart_resource() is deprecated, use set_preview_resource() instead",
2274
- DeprecationWarning,
2275
- )
2276
- return self.set_preview_resource(resource)
2277
-
2278
2227
  def create_default_views(self, create_datastore_views: bool = False) -> None:
2279
2228
  """Create default resource views for all resources in dataset
2280
2229
 
@@ -2305,28 +2254,6 @@ class Dataset(HDXObject):
2305
2254
  self._preview_resourceview = None
2306
2255
  break
2307
2256
 
2308
- def generate_quickcharts(
2309
- self,
2310
- resource: Union["Resource", dict, str, int] = 0,
2311
- path: Path | str | None = None,
2312
- bites_disabled: Sequence[bool] | None = None,
2313
- indicators: Sequence[dict] | None = None,
2314
- findreplace: dict | None = None,
2315
- ) -> None:
2316
- """To be removed
2317
-
2318
- Args:
2319
- resource: Either resource id or name, resource metadata from a Resource object or a dictionary or position. Defaults to 0.
2320
- path: Path to YAML resource view metadata. Defaults to None (config/hdx_resource_view_static.yaml or internal template).
2321
- bites_disabled: Which QC bites should be disabled. Defaults to None (all bites enabled).
2322
- indicators: Indicator codes, QC titles and units for resource view template. Defaults to None (don't use template).
2323
- findreplace: Replacements for anything else in resource view. Defaults to None.
2324
-
2325
- Returns:
2326
- None
2327
- """
2328
- return None
2329
-
2330
2257
  def get_name_or_id(self, prefer_name: bool = True) -> str | None:
2331
2258
  """Get dataset name or id eg. for use in urls. If prefer_name is True,
2332
2259
  name is preferred over id if available, otherwise id is preferred over
@@ -2379,33 +2306,6 @@ class Dataset(HDXObject):
2379
2306
  return None
2380
2307
  return f"{self.configuration.get_hdx_site_url()}/api/3/action/package_show?id={name_or_id}"
2381
2308
 
2382
- def remove_dates_from_title(
2383
- self, change_title: bool = True, set_time_period: bool = False
2384
- ) -> list[tuple[datetime, datetime]]:
2385
- """Remove dates from dataset title returning sorted the dates that were found in
2386
- title. The title in the dataset metadata will be changed by default. The
2387
- dataset's metadata field time period will not be changed by default, but if
2388
- set_time_period is True, then the range with the lowest start date will be used
2389
- to set the time period field.
2390
-
2391
- Args:
2392
- change_title: Whether to change the dataset title. Defaults to True.
2393
- set_time_period: Whether to set time period from date or range in title. Defaults to False.
2394
-
2395
- Returns:
2396
- Date ranges found in title
2397
- """
2398
- if "title" not in self.data:
2399
- raise HDXError("Dataset has no title!")
2400
- title = self.data["title"]
2401
- newtitle, ranges = DatasetTitleHelper.get_dates_from_title(title)
2402
- if change_title:
2403
- self.data["title"] = newtitle
2404
- if set_time_period and len(ranges) != 0:
2405
- startdate, enddate = ranges[0]
2406
- self.set_time_period(startdate, enddate)
2407
- return ranges
2408
-
2409
2309
  def generate_resource(
2410
2310
  self,
2411
2311
  folder: Path | str,
@@ -2541,193 +2441,6 @@ class Dataset(HDXObject):
2541
2441
  retdict["rows"] = rows
2542
2442
  return True, retdict
2543
2443
 
2544
- def generate_resource_from_rows(
2545
- self,
2546
- folder: Path | str,
2547
- filename: str,
2548
- rows: Iterable[Sequence | Mapping],
2549
- resourcedata: dict,
2550
- headers: Sequence[str] | None = None,
2551
- encoding: str | None = None,
2552
- ) -> Optional["Resource"]:
2553
- """Write rows to csv and create resource, adding it to the dataset.
2554
- The headers argument is either a row number (rows start counting at
2555
- 1), or the actual headers defined as a list of strings. If not set, all
2556
- rows will be treated as containing values.
2557
-
2558
- Args:
2559
- folder: Folder to which to write file containing rows
2560
- filename: Filename of file to write rows
2561
- rows: List of rows in dict or list form
2562
- resourcedata: Resource data
2563
- headers: List of headers. Defaults to None.
2564
- encoding: Encoding to use. Defaults to None (infer encoding).
2565
-
2566
- Returns:
2567
- The created resource or None if not created
2568
- """
2569
- warnings.warn(
2570
- "generate_resource_from_rows() is deprecated, use generate_resource() instead",
2571
- DeprecationWarning,
2572
- )
2573
- res, retdict = self.generate_resource(
2574
- folder, filename, rows, resourcedata, headers, headers, "csv", encoding
2575
- )
2576
- return retdict["resource"] if res else None
2577
-
2578
- def generate_resource_from_iterable(
2579
- self,
2580
- headers: Sequence[str],
2581
- iterable: Iterable[Sequence | dict],
2582
- hxltags: dict[str, str],
2583
- folder: Path | str,
2584
- filename: str,
2585
- resourcedata: dict,
2586
- datecol: int | str | None = None,
2587
- yearcol: int | str | None = None,
2588
- date_function: Callable[[dict], dict | None] | None = None,
2589
- quickcharts: dict | None = None,
2590
- encoding: str | None = None,
2591
- ) -> tuple[bool, dict]:
2592
- """Given headers and an iterable, write rows to csv and create
2593
- resource, adding to it the dataset. The returned dictionary will
2594
- contain the resource in the key resource, headers in the key headers
2595
- and list of rows in the key rows.
2596
-
2597
- The time period can optionally be set by supplying a column in
2598
- which the date or year is to be looked up. Note that any timezone
2599
- information is ignored and UTC assumed. Alternatively, a function can
2600
- be supplied to handle any dates in a row. It should accept a row and
2601
- should return None to ignore the row or a dictionary which can either
2602
- be empty if there are no dates in the row or can be populated with
2603
- keys startdate and/or enddate which are of type timezone-aware
2604
- datetime. The lowest start date and highest end date are used to set
2605
- the time period and are returned in the results dictionary in keys
2606
- startdate and enddate.
2607
-
2608
- Args:
2609
- headers: Headers
2610
- iterable: Iterable returning rows
2611
- hxltags: Header to HXL hashtag mapping
2612
- folder: Folder to which to write file containing rows
2613
- filename: Filename of file to write rows
2614
- resourcedata: Resource data
2615
- datecol: Date column for setting time period. Defaults to None (don't set).
2616
- yearcol: Year column for setting dataset year range. Defaults to None (don't set).
2617
- date_function: Date function to call for each row. Defaults to None.
2618
- encoding: Encoding to use. Defaults to None (infer encoding).
2619
-
2620
- Returns:
2621
- (True if resource added, dictionary of results)
2622
- """
2623
- warnings.warn(
2624
- "generate_resource_from_iterable() is deprecated, use generate_resource() instead",
2625
- DeprecationWarning,
2626
- )
2627
- if [datecol, yearcol, date_function].count(None) < 2:
2628
- raise HDXError("Supply one of datecol, yearcol or date_function!")
2629
- retdict = {}
2630
- if headers is None:
2631
- return False, retdict
2632
- rows = [Download.hxl_row(headers, hxltags, dict_form=True)]
2633
- dates = [default_enddate, default_date]
2634
- if yearcol is not None:
2635
-
2636
- def yearcol_function(row):
2637
- result = {}
2638
- year = row[yearcol]
2639
- if year:
2640
- result["startdate"], result["enddate"] = parse_date_range(
2641
- year,
2642
- zero_time=True,
2643
- max_endtime=True,
2644
- )
2645
- return result
2646
-
2647
- date_function = yearcol_function
2648
- elif datecol is not None:
2649
-
2650
- def datecol_function(row):
2651
- result = {}
2652
- date = row[datecol]
2653
- if date:
2654
- date = parse_date(date)
2655
- result["startdate"] = date
2656
- result["enddate"] = date
2657
- return result
2658
-
2659
- date_function = datecol_function
2660
-
2661
- for row in iterable:
2662
- if date_function is not None:
2663
- result = date_function(row)
2664
- if result is None:
2665
- continue
2666
- startdate = result.get("startdate")
2667
- if startdate is not None:
2668
- if startdate < dates[0]:
2669
- dates[0] = startdate
2670
- enddate = result.get("enddate")
2671
- if enddate is not None:
2672
- if enddate > dates[1]:
2673
- dates[1] = enddate
2674
- rows.append(row)
2675
- if len(rows) == 1:
2676
- logger.error(f"No data rows in {filename}!")
2677
- return False, retdict
2678
- if yearcol is not None or date_function is not None:
2679
- if dates[0] == default_enddate or dates[1] == default_date:
2680
- logger.error(f"No dates in {filename}!")
2681
- return False, retdict
2682
- else:
2683
- retdict["startdate"] = dates[0]
2684
- retdict["enddate"] = dates[1]
2685
- self.set_time_period(dates[0], dates[1])
2686
- resource = self.generate_resource_from_rows(
2687
- folder,
2688
- filename,
2689
- rows,
2690
- resourcedata,
2691
- headers=headers,
2692
- encoding=encoding,
2693
- )
2694
- retdict["resource"] = resource
2695
- retdict["headers"] = headers
2696
- retdict["rows"] = rows
2697
- return True, retdict
2698
-
2699
- def generate_resource_from_iterator(
2700
- self,
2701
- headers: Sequence[str],
2702
- iterator: Iterator[Sequence | dict],
2703
- hxltags: dict[str, str],
2704
- folder: Path | str,
2705
- filename: str,
2706
- resourcedata: dict,
2707
- datecol: int | str | None = None,
2708
- yearcol: int | str | None = None,
2709
- date_function: Callable[[dict], dict | None] | None = None,
2710
- quickcharts: dict | None = None,
2711
- encoding: str | None = None,
2712
- ) -> tuple[bool, dict]:
2713
- warnings.warn(
2714
- "generate_resource_from_iterator() is deprecated, use generate_resource() instead",
2715
- DeprecationWarning,
2716
- )
2717
- return self.generate_resource_from_iterable(
2718
- headers,
2719
- iterator,
2720
- hxltags,
2721
- folder,
2722
- filename,
2723
- resourcedata,
2724
- datecol,
2725
- yearcol,
2726
- date_function,
2727
- quickcharts,
2728
- encoding,
2729
- )
2730
-
2731
2444
  def download_generate_resource(
2732
2445
  self,
2733
2446
  downloader: BaseDownload,
@@ -2773,7 +2486,6 @@ class Dataset(HDXObject):
2773
2486
  Args:
2774
2487
  downloader: A Download or Retrieve object
2775
2488
  url: URL to download
2776
- hxltags: Header to HXL hashtag mapping
2777
2489
  folder: Folder to which to write file containing rows
2778
2490
  filename: Filename of file to write rows
2779
2491
  resourcedata: Resource data
@@ -2814,89 +2526,6 @@ class Dataset(HDXObject):
2814
2526
  no_empty=no_empty,
2815
2527
  )
2816
2528
 
2817
- def download_and_generate_resource(
2818
- self,
2819
- downloader: BaseDownload,
2820
- url: str,
2821
- hxltags: dict[str, str],
2822
- folder: Path | str,
2823
- filename: str,
2824
- resourcedata: dict,
2825
- header_insertions: Sequence[tuple[int, str]] | None = None,
2826
- row_function: Callable[[list[str], dict], dict] | None = None,
2827
- datecol: str | None = None,
2828
- yearcol: str | None = None,
2829
- date_function: Callable[[dict], dict | None] | None = None,
2830
- quickcharts: dict | None = None,
2831
- **kwargs: Any,
2832
- ) -> tuple[bool, dict]:
2833
- """Download url, write rows to csv and create resource, adding to it
2834
- the dataset. The returned dictionary will contain the resource in the
2835
- key resource, headers in the key headers and list of rows in the key
2836
- rows.
2837
-
2838
- Optionally, headers can be inserted at specific positions. This is
2839
- achieved using the header_insertions argument. If supplied, it is a
2840
- list of tuples of the form (position, header) to be inserted. A
2841
- function is called for each row. If supplied, it takes as arguments:
2842
- headers (prior to any insertions) and row (which will be in dict or
2843
- list form depending upon the dict_rows argument) and outputs a modified
2844
- row.
2845
-
2846
- The time period can optionally be set by supplying a column in
2847
- which the date or year is to be looked up. Note that any timezone
2848
- information is ignored and UTC assumed. Alternatively, a function can
2849
- be supplied to handle any dates in a row. It should accept a row and
2850
- should return None to ignore the row or a dictionary which can either
2851
- be empty if there are no dates in the row or can be populated with
2852
- keys startdate and/or enddate which are of type timezone-aware
2853
- datetime. The lowest start date and highest end date are used to set
2854
- the time period and are returned in the results dictionary in keys
2855
- startdate and enddate.
2856
-
2857
- Args:
2858
- downloader: A Download or Retrieve object
2859
- url: URL to download
2860
- hxltags: Header to HXL hashtag mapping
2861
- folder: Folder to which to write file containing rows
2862
- filename: Filename of file to write rows
2863
- resourcedata: Resource data
2864
- header_insertions: List of (position, header) to insert. Defaults to None.
2865
- row_function: Function to call for each row. Defaults to None.
2866
- datecol: Date column for setting time period. Defaults to None (don't set).
2867
- yearcol: Year column for setting dataset year range. Defaults to None (don't set).
2868
- date_function: Date function to call for each row. Defaults to None.
2869
- **kwargs: Any additional args to pass to downloader.get_tabular_rows
2870
-
2871
- Returns:
2872
- (True if resource added, dictionary of results)
2873
- """
2874
- warnings.warn(
2875
- "download_and_generate_resource() is deprecated, use download_generate_resource() instead",
2876
- DeprecationWarning,
2877
- )
2878
- headers, iterator = downloader.get_tabular_rows(
2879
- url,
2880
- dict_form=True,
2881
- header_insertions=header_insertions,
2882
- row_function=row_function,
2883
- format="csv",
2884
- **kwargs,
2885
- )
2886
- return self.generate_resource_from_iterable(
2887
- headers,
2888
- iterator,
2889
- hxltags,
2890
- folder,
2891
- filename,
2892
- resourcedata,
2893
- datecol=datecol,
2894
- yearcol=yearcol,
2895
- date_function=date_function,
2896
- quickcharts=quickcharts,
2897
- encoding=kwargs.get("encoding", None),
2898
- )
2899
-
2900
2529
  def add_hapi_error(
2901
2530
  self,
2902
2531
  error_message: str,
hdx/data/resource.py CHANGED
@@ -1,7 +1,6 @@
1
1
  """Resource class containing all logic for creating, checking, and updating resources."""
2
2
 
3
3
  import logging
4
- import warnings
5
4
  from collections.abc import Sequence
6
5
  from datetime import datetime
7
6
  from pathlib import Path
@@ -244,13 +243,6 @@ class Resource(HDXObject):
244
243
  file_format = file_format.lower()
245
244
  return file_format
246
245
 
247
- def get_file_type(self) -> str | None:
248
- warnings.warn(
249
- "get_file_type() is deprecated, use get_format() instead",
250
- DeprecationWarning,
251
- )
252
- return self.get_format()
253
-
254
246
  def set_format(self, format: str) -> str:
255
247
  """Set the resource's file type
256
248
 
@@ -268,13 +260,6 @@ class Resource(HDXObject):
268
260
  self.data["format"] = file_format
269
261
  return file_format
270
262
 
271
- def set_file_type(self, file_type: str) -> str:
272
- warnings.warn(
273
- "set_file_type() is deprecated, use set_format() instead",
274
- DeprecationWarning,
275
- )
276
- return self.set_format(file_type)
277
-
278
263
  def clean_format(self) -> str:
279
264
  """Clean the resource's format, setting it to None if it is invalid and
280
265
  cannot be mapped
@@ -284,13 +269,6 @@ class Resource(HDXObject):
284
269
  """
285
270
  return self.set_format(self.data.get("format"))
286
271
 
287
- def clean_file_type(self) -> str:
288
- warnings.warn(
289
- "clean_file_type() is deprecated, use clean_format() instead",
290
- DeprecationWarning,
291
- )
292
- return self.set_format(self.data.get("format"))
293
-
294
272
  def get_file_to_upload(self) -> str | None:
295
273
  """Get the file uploaded
296
274
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: hdx-python-api
3
- Version: 6.6.4
3
+ Version: 6.6.6
4
4
  Summary: HDX Python API for interacting with the Humanitarian Data Exchange
5
5
  Project-URL: Homepage, https://github.com/OCHA-DAP/hdx-python-api
6
6
  Author: Michael Rans
@@ -28,10 +28,9 @@ Requires-Python: >=3.10
28
28
  Requires-Dist: ckanapi>=4.8
29
29
  Requires-Dist: defopt>=7.0.0
30
30
  Requires-Dist: email-validator
31
- Requires-Dist: hdx-python-country>=4.0.1
32
- Requires-Dist: hdx-python-utilities>=4.0.4
31
+ Requires-Dist: hdx-python-country>=4.1.1
32
+ Requires-Dist: hdx-python-utilities>=4.0.8
33
33
  Requires-Dist: makefun
34
- Requires-Dist: quantulum3
35
34
  Requires-Dist: requests
36
35
  Provides-Extra: docs
37
36
  Requires-Dist: mkapi; extra == 'docs'
@@ -53,3 +52,87 @@ For more information, please read the
53
52
  This library is part of the [Humanitarian Data Exchange](https://data.humdata.org/)
54
53
  (HDX) project. If you have humanitarian related data, please upload your datasets to
55
54
  HDX.
55
+
56
+ # Development
57
+
58
+ ## Environment
59
+
60
+ Development is currently done using Python 3.13. The environment can be created with:
61
+
62
+ ```shell
63
+ uv sync
64
+ ```
65
+
66
+ This creates a .venv folder with the versions specified in the project's uv.lock file.
67
+
68
+ ### Pre-commit
69
+
70
+ pre-commit will be installed when syncing uv. It is run every time you make a git
71
+ commit if you call it like this:
72
+
73
+ ```shell
74
+ pre-commit install
75
+ ```
76
+
77
+ With pre-commit, all code is formatted according to
78
+ [ruff](https://docs.astral.sh/ruff/) guidelines.
79
+
80
+ To check if your changes pass pre-commit without committing, run:
81
+
82
+ ```shell
83
+ pre-commit run --all-files
84
+ ```
85
+
86
+ ## Packages
87
+
88
+ [uv](https://github.com/astral-sh/uv) is used for package management. If
89
+ you’ve introduced a new package to the source code (i.e. anywhere in `src/`),
90
+ please add it to the `project.dependencies` section of `pyproject.toml` with
91
+ any known version constraints.
92
+
93
+ To add packages required only for testing, add them to the
94
+ `[dependency-groups]`.
95
+
96
+ Any changes to the dependencies will be automatically reflected in
97
+ `uv.lock` with `pre-commit`, but you can re-generate the files without committing by
98
+ executing:
99
+
100
+ ```shell
101
+ uv lock --upgrade
102
+ ```
103
+
104
+ ## Project
105
+
106
+ [uv](https://github.com/astral-sh/uv) is used for project management. The project can be
107
+ built using:
108
+
109
+ ```shell
110
+ uv build
111
+ ```
112
+
113
+ Linting and syntax checking can be run with:
114
+
115
+ ```shell
116
+ uv run ruff check
117
+ ```
118
+
119
+ To run the tests and view coverage, execute:
120
+
121
+ ```shell
122
+ uv run pytest
123
+ ```
124
+
125
+ For the `test_ckan.py` test to run successfully, some configuration is required:
126
+
127
+ 1. The environment variable HDX_KEY_TEST needs to contain a valid key from the HDX demo
128
+ server at https://demo.data-humdata-org.ahconu.org/
129
+ 1. Authentication details for Google Sheets need to be obtained and either saved in a
130
+ file named `.gsheet_auth.json` in the home directory (~) or placed in an environment
131
+ variable `GSHEET_AUTH`. The file is preferred for Windows systems since adding such a
132
+ long text string to an environment variable in Windows is challenging.
133
+
134
+ ## Documentation
135
+
136
+ The documentation, including API documentation, is generated using ReadtheDocs and
137
+ MkDocs with Material. As you change the source code, remember to update the
138
+ documentation at `documentation/index.md`.
@@ -1,23 +1,20 @@
1
1
  hdx/api/__init__.py,sha256=11ozJKiUsqDCZ3_mcAHhGYUyGK_Unl54djVSBBExFB4,59
2
- hdx/api/_version.py,sha256=UEE9I6WlhPAe2Jt5wF9PG4WaLKPa_1VgRmxjpizBjCA,704
2
+ hdx/api/_version.py,sha256=ExjJQQs0P3ByYbVeKqojfK1IwhKHopRm142FJqldcBU,520
3
3
  hdx/api/configuration.py,sha256=9WvuVI-3th2Dv70_t_4VoyrlJwB_9qUSN5_zUKbp4sQ,23805
4
4
  hdx/api/hdx_base_configuration.yaml,sha256=9yJYroZwoyMQo6tcxGfWHWvYahnTma7Szx0J5Z378z4,2173
5
5
  hdx/api/locations.py,sha256=blnqOjEASvh0mWgmo2iEE7ES_xOuRwQo3fOMqTPV_P0,4139
6
6
  hdx/api/remotehdx.py,sha256=IjKwP3jutlkrjvWCJPP7YM-xzg3pP_QVmiJfblllBY0,2079
7
7
  hdx/api/utilities/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
8
- hdx/api/utilities/dataset_title_helper.py,sha256=b3Q-ycVC6dtspgJDUqhY7GK7XpXPVeFtlzg_T5gz4Ts,9110
9
8
  hdx/api/utilities/date_helper.py,sha256=sXxjRtXz-2rpBVZBbJAk80gfjpYKl_AtHplWr-rniIk,6396
10
9
  hdx/api/utilities/filestore_helper.py,sha256=0FcUpGHpYlAzlNDpDxdXbSqV6VHFfGCLakCmK37hieU,6917
11
10
  hdx/api/utilities/hdx_error_handler.py,sha256=x1rb2qBO0C6CJjqToSFj_XB1F3pf3nHpA8otxXfTFzA,8711
12
11
  hdx/api/utilities/hdx_state.py,sha256=KzrK0A-H5R6cRPqwF4d23_S_zfIa1ow4K5u46FesGmY,2442
13
12
  hdx/api/utilities/url_utils.py,sha256=Pb4r-BAqIHYmeoB_i7QjutDp6nOAUh_OHvCtiNLda3Y,4534
14
13
  hdx/data/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
15
- hdx/data/dataset.py,sha256=vmqCUgpyx-Dm7oORZu5BBI4gZ5yNbtGBXgRnKOW6DMU,114531
16
- hdx/data/hdx_datasource_topline.yaml,sha256=kBsb7UPl9RGkPbXfJ-OBQBzIse57-o0T41sbOCd2KVg,366
14
+ hdx/data/dataset.py,sha256=0gS7tQSswTgG0g8YeXg0FL8pFVkocBQSRPFpQAurN00,99455
17
15
  hdx/data/hdxobject.py,sha256=_jlwYjb_CcjaXsTXObknMfq-OiNOcpJfLJ9b1vKV1Zo,27494
18
- hdx/data/indicator_resource_view_template.yaml,sha256=iww2EjGSJdUpN3vhsUEva6ohrXjS-tjR56K5Aly0Xo4,2915
19
16
  hdx/data/organization.py,sha256=JHlCl50g1nh65DzN75OgMdvEcT_wfSIPLkhI5Ms-u8g,10588
20
- hdx/data/resource.py,sha256=URzTM0PJC1rNMNAVhYtom-0wo26twqRnPnYk0EbDjpo,34722
17
+ hdx/data/resource.py,sha256=5j6QK5_J-UFKCT3jZVUZ66VgGsOcCssmGy4jpqdb1N4,34027
21
18
  hdx/data/resource_matcher.py,sha256=Yzk9pWa4zk1p09m6zcZMj0XZf5Y16Pa3KToOdUt7rP8,4247
22
19
  hdx/data/resource_view.py,sha256=GcYkh0iDAe6_m2ErY_A6i3Z7ZFX949eGt1TLnLZv_8s,7346
23
20
  hdx/data/showcase.py,sha256=vnRZ45xd9Ct65eFQkWZtMIJPFifZfKOrH4obO5D5Pvc,14062
@@ -27,7 +24,7 @@ hdx/facades/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
27
24
  hdx/facades/infer_arguments.py,sha256=DvyDuozfOPmoaV7HjhQySojGIrMWW9nos31auQBVVl8,4032
28
25
  hdx/facades/keyword_arguments.py,sha256=aVapHCfqBO6H7xxBgTdK9ISdgrBFsNypPDWihfQ3edU,1125
29
26
  hdx/facades/simple.py,sha256=QWsBkaHG_UM6X3Zu2u3LWVOQzeR24q-EUClrfsL_mkM,1064
30
- hdx_python_api-6.6.4.dist-info/METADATA,sha256=7mTxQ0xIXxI-E3lFkZJ89oGrcte0I7pHTJLiNBnXK7o,2723
31
- hdx_python_api-6.6.4.dist-info/WHEEL,sha256=WLgqFyCfm_KASv4WHyYy0P3pM_m7J5L9k2skdKLirC8,87
32
- hdx_python_api-6.6.4.dist-info/licenses/LICENSE,sha256=wc-4GpMn-ODs-U_bTe1YCiPVgvcjzrpYOx2wPuyAeII,1079
33
- hdx_python_api-6.6.4.dist-info/RECORD,,
27
+ hdx_python_api-6.6.6.dist-info/METADATA,sha256=o4V01ehlDyD2YzWP0EbQ_hRuMnOoyXuW_etzGWA-kTk,4945
28
+ hdx_python_api-6.6.6.dist-info/WHEEL,sha256=QccIxa26bgl1E6uMy58deGWi-0aeIkkangHcxk2kWfw,87
29
+ hdx_python_api-6.6.6.dist-info/licenses/LICENSE,sha256=wc-4GpMn-ODs-U_bTe1YCiPVgvcjzrpYOx2wPuyAeII,1079
30
+ hdx_python_api-6.6.6.dist-info/RECORD,,
@@ -1,4 +1,4 @@
1
1
  Wheel-Version: 1.0
2
- Generator: hatchling 1.28.0
2
+ Generator: hatchling 1.29.0
3
3
  Root-Is-Purelib: true
4
4
  Tag: py3-none-any
@@ -1,254 +0,0 @@
1
- """Helper to the Dataset class for handling processing dataset titles."""
2
-
3
- import logging
4
- import re
5
- from datetime import datetime, timedelta
6
- from re import Match
7
- from string import punctuation, whitespace
8
-
9
- from dateutil.parser import ParserError
10
- from hdx.utilities.dateparse import parse_date, parse_date_range
11
- from hdx.utilities.text import (
12
- PUNCTUATION_MINUS_BRACKETS,
13
- remove_end_characters,
14
- remove_from_end,
15
- remove_string,
16
- )
17
- from quantulum3 import parser
18
-
19
- logger = logging.getLogger(__name__)
20
-
21
-
22
- class DatasetTitleHelper:
23
- YEAR_PATTERN = re.compile(r"([12]\d\d\d)")
24
- VERSION_PATTERN = re.compile(r"(\d)?(\d)?\d")
25
- YEAR_RANGE_PATTERN = re.compile(
26
- r"([12]\d\d\d)(/(\d{1,2}))?(-| & | and )([12]\d\d\d)"
27
- )
28
- YEAR_RANGE_PATTERN2 = re.compile(r"([12]\d\d\d)([/-])(\d{1,2})")
29
- PUNCTUATION_PATTERN = re.compile(rf"[{punctuation}]")
30
- EMPTY_BRACKET_PATTERN = re.compile(r"(\s?\(\s*\)\s?)")
31
- WORD_RIGHT_BRACKET_PATTERN = re.compile(r"\b(\s*)(\w{2,})\b\)")
32
- DATE_INTRO_WORDS = ["on", "at", "for", "of", "in"]
33
-
34
- @classmethod
35
- def fuzzy_match_dates_in_title(
36
- cls,
37
- title: str,
38
- ranges: list[tuple[datetime, datetime]],
39
- ignore_wrong_years: list[int],
40
- ) -> str:
41
- """
42
- Fuzzy match dates in title appending to ranges
43
-
44
- Args:
45
- title: Title to parse
46
- ranges: List of date ranges found so far
47
- ignore_wrong_years: Numbers identified as years that probably are not years
48
-
49
- Returns:
50
- Title with dates removed
51
-
52
- """
53
- for quant in parser.parse(title):
54
- if quant.unit.name == "dimensionless":
55
- continue
56
- ignore_wrong_years.append(int(quant.value))
57
- for match in cls.YEAR_PATTERN.finditer(title):
58
- year = match.group(0)
59
- if int(year) in ignore_wrong_years:
60
- continue
61
- start = match.start()
62
- end = match.end()
63
- stringlr = title[max(start - 13, 0) : end]
64
- fuzzylr = {}
65
- startdatelr = None
66
- enddatelr = None
67
- deltalr = timedelta(days=1000)
68
- try:
69
- startdatelr, enddatelr = parse_date_range(
70
- stringlr,
71
- fuzzy=fuzzylr,
72
- zero_time=True,
73
- max_endtime=True,
74
- )
75
- if startdatelr and enddatelr:
76
- deltalr = enddatelr - startdatelr
77
- except ParserError:
78
- pass
79
- fuzzyrl = {}
80
- stringrl = title[start : min(end + 13, len(title))]
81
- startdaterl = None
82
- enddaterl = None
83
- deltarl = timedelta(days=1000)
84
- try:
85
- startdaterl, enddaterl = parse_date_range(
86
- stringrl,
87
- fuzzy=fuzzyrl,
88
- zero_time=True,
89
- max_endtime=True,
90
- )
91
- if startdaterl and enddaterl:
92
- deltarl = enddaterl - startdaterl
93
- except ParserError:
94
- pass
95
- if startdatelr and deltalr <= deltarl:
96
- date_components = fuzzylr["date"]
97
- ranges.append((startdatelr, enddatelr))
98
- elif startdaterl:
99
- date_components = fuzzyrl["date"]
100
- ranges.append((startdaterl, enddaterl))
101
- else:
102
- date_components = year
103
- ranges.append(parse_date_range(year, zero_time=True, max_endtime=True))
104
- newtitle = title
105
- for date_component in date_components:
106
- newtitle = remove_string(
107
- newtitle, date_component, PUNCTUATION_MINUS_BRACKETS
108
- )
109
- logger.info(f"Removing date from title: {title} -> {newtitle}")
110
- title = newtitle
111
- try:
112
- fuzzy = {}
113
- startdate, enddate = parse_date_range(
114
- title,
115
- fuzzy=fuzzy,
116
- zero_time=True,
117
- max_endtime=True,
118
- )
119
- datestrs = fuzzy["date"]
120
- if (
121
- startdate.date() == enddate.date() and len(datestrs) == 1
122
- ): # only accept dates where day, month and year are
123
- # all together not split throughout the string and where the date is a precise day not a range
124
- date_component = datestrs[0]
125
- index = title.find(date_component) - 1
126
- if index >= 0 and title[index].lower() == "v":
127
- return title
128
- ranges.append((startdate, enddate))
129
- newtitle = remove_string(
130
- title, date_component, PUNCTUATION_MINUS_BRACKETS
131
- )
132
- logger.info(f"Removing date from title: {title} -> {newtitle}")
133
- title = newtitle
134
- except (ParserError, OverflowError):
135
- pass
136
-
137
- return title
138
-
139
- @classmethod
140
- def get_month_year_in_slash_range(
141
- cls, match: Match, ignore_wrong_years: list[int]
142
- ) -> tuple[int | None, int | None, int | None]:
143
- """
144
- Get year(s) and month from slash range eg. 2007/08. If second value is between 1 and 12, take it as a month.
145
-
146
- Args:
147
- match (Match): Match object
148
- ignore_wrong_years: Numbers identified as years that probably are not years
149
-
150
- Returns:
151
- First year, first month, second year
152
-
153
- """
154
- first_year_str = match.group(1)
155
- first_year = int(first_year_str)
156
- if first_year in ignore_wrong_years:
157
- return None, None, None
158
- two_digits_str = match.group(3)
159
- if two_digits_str:
160
- two_digits = int(two_digits_str)
161
- if 1 <= two_digits <= 12:
162
- return first_year, two_digits, None
163
- else:
164
- second_year = int(f"{first_year_str[:2]}{two_digits_str}")
165
- if second_year > first_year:
166
- return first_year, None, second_year
167
- else:
168
- ignore_wrong_years.append(first_year)
169
- return None, None, None
170
- else:
171
- return first_year, None, None
172
-
173
- @classmethod
174
- def get_dates_from_title(
175
- cls, title: str
176
- ) -> tuple[str, list[tuple[datetime, datetime]]]:
177
- """
178
- Get dataset dates (start and end dates in a list) from title and clean title of dates
179
-
180
- Args:
181
- title: Title to get date from and clean
182
-
183
- Returns:
184
- Cleaned title, list of start and end dates
185
-
186
- """
187
- ranges = []
188
- ignore_wrong_years = []
189
- for match in cls.YEAR_RANGE_PATTERN.finditer(title):
190
- (
191
- first_year,
192
- first_month,
193
- second_year,
194
- ) = cls.get_month_year_in_slash_range(match, ignore_wrong_years)
195
- if first_year is None:
196
- continue
197
- if first_month is None:
198
- first_month = 1
199
- startdate = parse_date(
200
- f"{first_year}-{first_month}-01",
201
- "%Y-%m-%d",
202
- zero_time=True,
203
- )
204
- enddate = parse_date(
205
- f"{match.group(5)}-12-31",
206
- "%Y-%m-%d",
207
- max_time=True,
208
- )
209
- ranges.append((startdate, enddate))
210
- newtitle = remove_string(title, match.group(0))
211
- logger.info(f"Removing date range from title: {title} -> {newtitle}")
212
- title = newtitle
213
-
214
- for match in cls.YEAR_RANGE_PATTERN2.finditer(title):
215
- (
216
- first_year,
217
- first_month,
218
- second_year,
219
- ) = cls.get_month_year_in_slash_range(match, ignore_wrong_years)
220
- if first_year is None or second_year is None:
221
- continue
222
- startdate = parse_date(
223
- f"{first_year}-01-01",
224
- "%Y-%m-%d",
225
- zero_time=True,
226
- )
227
- enddate = parse_date(
228
- f"{second_year}-12-31",
229
- "%Y-%m-%d",
230
- max_time=True,
231
- )
232
- ranges.append((startdate, enddate))
233
- newtitle = remove_string(title, match.group(0))
234
- logger.info(f"Removing date range from title: {title} -> {newtitle}")
235
- title = newtitle
236
-
237
- title = cls.fuzzy_match_dates_in_title(title, ranges, ignore_wrong_years)
238
-
239
- for match in cls.WORD_RIGHT_BRACKET_PATTERN.finditer(title):
240
- word = match.group(2)
241
- if word in cls.DATE_INTRO_WORDS:
242
- title = title.replace(match.group(0), ")")
243
-
244
- for match in cls.EMPTY_BRACKET_PATTERN.finditer(title):
245
- title = title.replace(match.group(0), " ")
246
- title = remove_end_characters(
247
- title, f"{PUNCTUATION_MINUS_BRACKETS}{whitespace}"
248
- )
249
- title = remove_from_end(
250
- title,
251
- ["as of"] + cls.DATE_INTRO_WORDS,
252
- "Removing - from title: %s -> %s",
253
- )
254
- return title, sorted(ranges)
@@ -1,30 +0,0 @@
1
- #Topline configuration
2
- schema:
3
- -
4
- id: code
5
- type: text
6
- -
7
- id: title
8
- type: text
9
- -
10
- id: value
11
- type: float
12
- -
13
- id: latest_date
14
- type: timestamp
15
- -
16
- id: source
17
- type: text
18
- -
19
- id: source_link
20
- type: text
21
- -
22
- id: notes
23
- type: text
24
- -
25
- id: explore
26
- type: text
27
- -
28
- id: units
29
- type: text
30
- primary_key: code
@@ -1,4 +0,0 @@
1
- description: ""
2
- title: "Quick Charts"
3
- view_type: "hdx_hxl_preview"
4
- hxl_preview_config: '{"configVersion":5,"bites":[{"tempShowSaveCancelButtons":false,"ingredient":{"aggregateColumn":"AGGREGATE_COL_1","valueColumn":"VALUE_COL_1","aggregateFunction":"sum","dateColumn":"DATE_COL_1","comparisonValueColumn":null,"comparisonOperator":null,"filters":{"filterWith":[{"CODE_COL_1":"CODE_VALUE_1"}]},"description":"DESCRIPTION_VALUE_1"},"type":"timeseries","errorMsg":null,"computedProperties":{"explainedFiltersMap":{"remove empty valued rows":{"filterWith":[{"VALUE_COL_1":"is not empty"}],"filterWithout":[]}},"pieChart":false,"title":"Sum of Value by Year","dataTitle":"UNIT_VALUE_1"},"uiProperties":{"swapAxis":true,"showGrid":true,"color":"#0077ce","sortingByValue1":"DESC","sortingByCategory1":null,"showPoints":true,"internalColorPattern":["#1ebfb3","#0077ce","#f2645a","#9C27B0"],"title":"TITLE_VALUE_1","dateFormat":"DATE_FORMAT_1"},"dataProperties":{},"displayCategory":"Timeseries","hashCode":498306664},{"tempShowSaveCancelButtons":false,"ingredient":{"aggregateColumn":"AGGREGATE_COL_2","valueColumn":"VALUE_COL_2","aggregateFunction":"sum","dateColumn":"DATE_COL_2","comparisonValueColumn":null,"comparisonOperator":null,"filters":{"filterWith":[{"CODE_COL_2":"CODE_VALUE_2"}]},"description":"DESCRIPTION_VALUE_2"},"type":"timeseries","errorMsg":null,"computedProperties":{"explainedFiltersMap":{"remove empty valued rows":{"filterWith":[{"VALUE_COL_2":"is not empty"}],"filterWithout":[]}},"pieChart":false,"title":"Sum of Value by Year","dataTitle":"UNIT_VALUE_2"},"uiProperties":{"swapAxis":true,"showGrid":true,"color":"#0077ce","sortingByValue1":"DESC","sortingByCategory1":null,"showPoints":true,"internalColorPattern":["#1ebfb3","#0077ce","#f2645a","#9C27B0"],"title":"TITLE_VALUE_2","dateFormat":"DATE_FORMAT_2"},"dataProperties":{},"displayCategory":"Timeseries","hashCode":498306664},{"tempShowSaveCancelButtons":false,"ingredient":{"aggregateColumn":"AGGREGATE_COL_3","valueColumn":"VALUE_COL_3","aggregateFunction":"sum","dateColumn":"DATE_COL_3","comparisonValueColumn":null,"comparisonOperator":null,"filters":{"filterWith":[{"CODE_COL_3":"CODE_VALUE_3"}]},"description":"DESCRIPTION_VALUE_3"},"type":"timeseries","errorMsg":null,"computedProperties":{"explainedFiltersMap":{"remove empty valued rows":{"filterWith":[{"VALUE_COL_3":"is not empty"}],"filterWithout":[]}},"pieChart":false,"title":"Sum of Value by Year","dataTitle":"UNIT_VALUE_3"},"uiProperties":{"swapAxis":true,"showGrid":true,"color":"#0077ce","sortingByValue1":"DESC","sortingByCategory1":null,"showPoints":true,"internalColorPattern":["#1ebfb3","#0077ce","#f2645a","#9C27B0"],"title":"TITLE_VALUE_3","dateFormat":"DATE_FORMAT_3"},"dataProperties":{},"displayCategory":"Timeseries","hashCode":498306664}],"recipeUrl":"https://raw.githubusercontent.com/mcarans/hxl-recipes/dev/recipes/historic-values-filtered/recipe.json"}'