the-datagarden 0.1.0__tar.gz → 1.2.1__tar.gz

Sign up to get free protection for your applications and to get access to all the features.
Files changed (39) hide show
  1. the_datagarden-1.2.1/PKG-INFO +137 -0
  2. the_datagarden-1.2.1/README.rst +98 -0
  3. the_datagarden-1.2.1/pyproject.toml +84 -0
  4. the_datagarden-1.2.1/src/the_datagarden/__init__.py +8 -0
  5. the_datagarden-1.2.1/src/the_datagarden/abc/__init__.py +3 -0
  6. the_datagarden-1.2.1/src/the_datagarden/abc/api.py +19 -0
  7. the_datagarden-1.2.1/src/the_datagarden/abc/authentication.py +42 -0
  8. the_datagarden-1.2.1/src/the_datagarden/api/__init__.py +5 -0
  9. the_datagarden-1.2.1/src/the_datagarden/api/authentication/__init__.py +112 -0
  10. the_datagarden-1.2.1/src/the_datagarden/api/authentication/credentials/__init__.py +120 -0
  11. the_datagarden-1.2.1/src/the_datagarden/api/authentication/environment/__init__.py +13 -0
  12. the_datagarden-1.2.1/src/the_datagarden/api/authentication/settings.py +54 -0
  13. the_datagarden-1.2.1/src/the_datagarden/api/base/__init__.py +215 -0
  14. the_datagarden-1.2.1/src/the_datagarden/api/regions/__init__.py +4 -0
  15. the_datagarden-1.2.1/src/the_datagarden/api/regions/base/__init__.py +108 -0
  16. the_datagarden-1.2.1/src/the_datagarden/api/regions/base/settings.py +19 -0
  17. the_datagarden-1.2.1/src/the_datagarden/api/regions/continent.py +9 -0
  18. the_datagarden-1.2.1/src/the_datagarden/api/regions/country.py +9 -0
  19. the_datagarden-1.2.1/src/the_datagarden/models/__init__.py +9 -0
  20. the_datagarden-1.2.1/src/the_datagarden/models/geojson.py +179 -0
  21. the_datagarden-1.2.1/src/the_datagarden/models/regional_data.py +411 -0
  22. the_datagarden-1.2.1/src/the_datagarden/version.py +1 -0
  23. the_datagarden-1.2.1/src/the_datagarden.egg-info/PKG-INFO +137 -0
  24. the_datagarden-1.2.1/src/the_datagarden.egg-info/SOURCES.txt +30 -0
  25. the_datagarden-1.2.1/src/the_datagarden.egg-info/requires.txt +8 -0
  26. the_datagarden-0.1.0/PKG-INFO +0 -17
  27. the_datagarden-0.1.0/README.md +0 -0
  28. the_datagarden-0.1.0/pyproject.toml +0 -70
  29. the_datagarden-0.1.0/src/the_datagarden/__init__.py +0 -0
  30. the_datagarden-0.1.0/src/the_datagarden.egg-info/PKG-INFO +0 -17
  31. the_datagarden-0.1.0/src/the_datagarden.egg-info/SOURCES.txt +0 -12
  32. the_datagarden-0.1.0/src/the_datagarden.egg-info/requires.txt +0 -2
  33. {the_datagarden-0.1.0 → the_datagarden-1.2.1}/setup.cfg +0 -0
  34. {the_datagarden-0.1.0 → the_datagarden-1.2.1}/src/the_datagarden/cli.py +0 -0
  35. {the_datagarden-0.1.0 → the_datagarden-1.2.1}/src/the_datagarden.egg-info/dependency_links.txt +0 -0
  36. {the_datagarden-0.1.0 → the_datagarden-1.2.1}/src/the_datagarden.egg-info/entry_points.txt +0 -0
  37. {the_datagarden-0.1.0 → the_datagarden-1.2.1}/src/the_datagarden.egg-info/top_level.txt +0 -0
  38. {the_datagarden-0.1.0 → the_datagarden-1.2.1}/tests/test_cli.py +0 -0
  39. {the_datagarden-0.1.0 → the_datagarden-1.2.1}/tests/test_the_datagarden_imports.py +0 -0
@@ -0,0 +1,137 @@
1
+ Metadata-Version: 2.1
2
+ Name: the-datagarden
3
+ Version: 1.2.1
4
+ Summary: Public data made easy.
5
+ Author-email: Maarten de Ruyter <info@the-datagarden.io>
6
+ License: MIT
7
+ Project-URL: Read the Docs, https://the-datagarden.readthedocs.io/
8
+ Project-URL: The-DataGarden, https://www.the-datagarden.io/
9
+ Project-URL: API documentation, https://api.the-datagarden.io/api-docs
10
+ Project-URL: Source, https://github.com/the-datagarden/the-datagarden
11
+ Classifier: Programming Language :: Python :: 3
12
+ Classifier: Programming Language :: Python :: 3.10
13
+ Classifier: Programming Language :: Python :: 3.11
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Classifier: Programming Language :: Python :: 3.13
16
+ Classifier: License :: OSI Approved :: MIT License
17
+ Classifier: Operating System :: OS Independent
18
+ Classifier: Development Status :: 4 - Beta
19
+ Classifier: Intended Audience :: Developers
20
+ Classifier: Intended Audience :: Financial and Insurance Industry
21
+ Classifier: Intended Audience :: Science/Research
22
+ Classifier: Intended Audience :: Healthcare Industry
23
+ Classifier: Topic :: Scientific/Engineering :: GIS
24
+ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
25
+ Classifier: Topic :: Scientific/Engineering :: Information Analysis
26
+ Classifier: Topic :: Scientific/Engineering :: Visualization
27
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
28
+ Classifier: Topic :: Utilities
29
+ Requires-Python: >=3.10
30
+ Description-Content-Type: text/x-rst
31
+ Requires-Dist: click>=8.1.7
32
+ Requires-Dist: pandas>=2.2.3
33
+ Requires-Dist: polars>=1.15.0
34
+ Requires-Dist: pydantic>=2.9.2
35
+ Requires-Dist: pyjwt>=2.10.0
36
+ Requires-Dist: python-decouple>=3.8
37
+ Requires-Dist: requests>=2.32.3
38
+ Requires-Dist: the-datagarden-models>=1.6.3
39
+
40
+ ==================
41
+ the-datagarden SDK
42
+ ==================
43
+
44
+ The-datagarden package is a Python SDK built on top of The-DataGarden API. It provides easy access to continent and country regional hierarchies, as well as public data related to these regions. Additionally, you can retrieve regional GeoJSONs using the SDK. It simplifies the process of converting regional data into DataFrames and/or GeoJSON Feature collections, enabling developers to build upon this data effortlessly.
45
+
46
+ A quick example
47
+ ---------------
48
+ If you have a user account at the-datagarden.io, you can start using the SDK right away:
49
+
50
+ .. code-block:: python
51
+
52
+ # Retrieve a country object from the datagarden API
53
+ >>> from the-datagarden import TheDataGardenAPI
54
+ >>> the_datagarden_api = TheDataGardenAPI(email='your-email@example.com', password='your-password')
55
+ >>> nl = the_datagarden_api.netherlands()
56
+ >>> nl_demographics = nl.demographics(from_period="2010-01-01", source="united nations")
57
+ >>> nl_demographics
58
+ TheDataGardenRegionalDataModel : Demographics : (count=15)
59
+
60
+ this returns a `TheDataGardenRegionalDataModel` containimg the demographics data in this case 15 records.
61
+ Each of those records will contain a Demographics object for the region for the specified period.
62
+
63
+ To work with this data, you can convert it to a pandas or polars dataframe and select the data from the demographics
64
+ data model you need.
65
+
66
+ .. code-block:: python
67
+
68
+ >>> df = nl_demographics.to_polars({"pop_count": "population.total"}) # or to_pandas(...)
69
+ >>> df["name", "source_name", "period", "data_model_name", "total"]
70
+ ┌─────────────┬────────────────┬─────────────────┬─────────────────┬─────────────┐
71
+ │ name ┆ source_name ┆ period ┆ data_model_name ┆ pop_count │
72
+ │ --- ┆ --- ┆ --- ┆ --- ┆ --- │
73
+ │ str ┆ str ┆ str ┆ str ┆ f64 │
74
+ ╞═════════════╪════════════════╪═════════════════╪═════════════════╪═════════════╡
75
+ │ Netherlands ┆ United Nations ┆ 2010-01-010:00Z ┆ Demographics ┆ 1.6729801e7 │
76
+ │ Netherlands ┆ United Nations ┆ 2011-01-010:00Z ┆ Demographics ┆ 1.6812669e7 │
77
+ │ Netherlands ┆ United Nations ┆ 2012-01-010:00Z ┆ Demographics ┆ 1.6889445e7 │
78
+ │ Netherlands ┆ United Nations ┆ 2013-01-010:00Z ┆ Demographics ┆ 1.6940942e7 │
79
+ │ Netherlands ┆ United Nations ┆ 2014-01-010:00Z ┆ Demographics ┆ 1.6993184e7 │
80
+ │ … ┆ … ┆ … ┆ … ┆ … │
81
+ │ Netherlands ┆ United Nations ┆ 2020-01-010:00Z ┆ Demographics ┆ 1.7601682e7 │
82
+ │ Netherlands ┆ United Nations ┆ 2021-01-010:00Z ┆ Demographics ┆ 1.767178e7 │
83
+ │ Netherlands ┆ United Nations ┆ 2022-01-010:00Z ┆ Demographics ┆ 1.7789347e7 │
84
+ │ Netherlands ┆ United Nations ┆ 2023-01-010:00Z ┆ Demographics ┆ 1.8019495e7 │
85
+ │ Netherlands ┆ United Nations ┆ 2024-01-010:00Z ┆ Demographics ┆ null │
86
+ └─────────────┴────────────────┴─────────────────┴─────────────────┴─────────────┘
87
+
88
+
89
+ Retrieving the GeoJSON for the Netherlands and its provinces is straightforward as well:
90
+
91
+ .. code-block:: python
92
+
93
+ >>> nl_geojson = nl.geojsons()
94
+ >>> nl_geojson
95
+ TheDataGardenRegionGeoJSONModel : GeoJSON : (count=1)
96
+ >>> nl_geojson(region_level=2) # Retrieve GeoJSON for 2nd regional level (provinces)
97
+ TheDataGardenRegionGeoJSONModel : GeoJSON : (count=13) # 12 provinces + 1 country
98
+ >>> df = nl_geojson.to_polars()
99
+ >>> df["name", "region_type", "local_region_code", "region_level", "feature"]
100
+ ┌───────────────┬─────────────┬───────────────┬──────────────┬────────────────────────┐
101
+ │ name ┆ region_type ┆ local_region_c┆ region_level ┆ feature │
102
+ │ --- ┆ --- ┆ --- ┆ --- ┆ --- │
103
+ │ str ┆ str ┆ str ┆ i64 ┆ struct[3] │
104
+ ╞═══════════════╪═════════════╪═══════════════╪══════════════╪════════════════════════╡
105
+ │ Netherlands ┆ country ┆ 528 ┆ 0 ┆ {"Feature",{"Netherland│
106
+ │ Drenthe ┆ province ┆ NL13 ┆ 2 ┆ {"Feature",{"Drenthe",2│
107
+ │ Flevoland ┆ province ┆ NL23 ┆ 2 ┆ {"Feature",{"Flevoland"│
108
+ │ Friesland ┆ province ┆ NL12 ┆ 2 ┆ {"Feature",{"Friesland"│
109
+ │ Gelderland ┆ province ┆ NL22 ┆ 2 ┆ {"Feature",{"Gelderland│
110
+ │ … ┆ … ┆ … ┆ … ┆ … │
111
+ │ Noord-Holland ┆ province ┆ NL32 ┆ 2 ┆ {"Feature",{"Noord-Holl│
112
+ │ Overijssel ┆ province ┆ NL21 ┆ 2 ┆ {"Feature",{"Overijssel│
113
+ │ Utrecht ┆ province ┆ NL31 ┆ 2 ┆ {"Feature",{"Utrecht",2│
114
+ │ Zeeland ┆ province ┆ NL34 ┆ 2 ┆ {"Feature",{"Zeeland",2│
115
+ │ Zuid-Holland ┆ province ┆ NL33 ┆ 2 ┆ {"Feature",{"Zuid-Holla│
116
+ └───────────────┴─────────────┴───────────────┴──────────────┴────────────────────────┘
117
+
118
+ For readability, the output only a limited number of dataframe columns are displayed.
119
+ Attributes in both the demographics and geojson dataframes are available to connect the geojson to
120
+ the demographics data. This allows you quickly make data sets that contain both demographics and geojson data
121
+ for further analysis or visualisation in map applications.
122
+
123
+
124
+ Read more
125
+ ---------
126
+
127
+ * `The DataGarden Website <https://www.the-datagarden.io>`_
128
+ * `API Documentation <https://www.the-datagarden.io/api-docs>`_
129
+ * `The Datagarden Models <https://www.the-datagarden.io/data-docs>`_
130
+ * `GitHub Repository <https://github.com/MaartendeRuyter/dg-the-datagarden>`_
131
+
132
+ Access to The DataGarden API
133
+ ----------------------------
134
+ To use the DataGarden SDK, you need access to the The DataGarden API. Simply register for free at https://www.the-datagarden.io
135
+ and you will have an inital free access account to the API with access to country and continent data.
136
+
137
+ Visit https://www.the-datagarden.io for to register for free.
@@ -0,0 +1,98 @@
1
+ ==================
2
+ the-datagarden SDK
3
+ ==================
4
+
5
+ The-datagarden package is a Python SDK built on top of The-DataGarden API. It provides easy access to continent and country regional hierarchies, as well as public data related to these regions. Additionally, you can retrieve regional GeoJSONs using the SDK. It simplifies the process of converting regional data into DataFrames and/or GeoJSON Feature collections, enabling developers to build upon this data effortlessly.
6
+
7
+ A quick example
8
+ ---------------
9
+ If you have a user account at the-datagarden.io, you can start using the SDK right away:
10
+
11
+ .. code-block:: python
12
+
13
+ # Retrieve a country object from the datagarden API
14
+ >>> from the-datagarden import TheDataGardenAPI
15
+ >>> the_datagarden_api = TheDataGardenAPI(email='your-email@example.com', password='your-password')
16
+ >>> nl = the_datagarden_api.netherlands()
17
+ >>> nl_demographics = nl.demographics(from_period="2010-01-01", source="united nations")
18
+ >>> nl_demographics
19
+ TheDataGardenRegionalDataModel : Demographics : (count=15)
20
+
21
+ this returns a `TheDataGardenRegionalDataModel` containimg the demographics data in this case 15 records.
22
+ Each of those records will contain a Demographics object for the region for the specified period.
23
+
24
+ To work with this data, you can convert it to a pandas or polars dataframe and select the data from the demographics
25
+ data model you need.
26
+
27
+ .. code-block:: python
28
+
29
+ >>> df = nl_demographics.to_polars({"pop_count": "population.total"}) # or to_pandas(...)
30
+ >>> df["name", "source_name", "period", "data_model_name", "total"]
31
+ ┌─────────────┬────────────────┬─────────────────┬─────────────────┬─────────────┐
32
+ │ name ┆ source_name ┆ period ┆ data_model_name ┆ pop_count │
33
+ │ --- ┆ --- ┆ --- ┆ --- ┆ --- │
34
+ │ str ┆ str ┆ str ┆ str ┆ f64 │
35
+ ╞═════════════╪════════════════╪═════════════════╪═════════════════╪═════════════╡
36
+ │ Netherlands ┆ United Nations ┆ 2010-01-010:00Z ┆ Demographics ┆ 1.6729801e7 │
37
+ │ Netherlands ┆ United Nations ┆ 2011-01-010:00Z ┆ Demographics ┆ 1.6812669e7 │
38
+ │ Netherlands ┆ United Nations ┆ 2012-01-010:00Z ┆ Demographics ┆ 1.6889445e7 │
39
+ │ Netherlands ┆ United Nations ┆ 2013-01-010:00Z ┆ Demographics ┆ 1.6940942e7 │
40
+ │ Netherlands ┆ United Nations ┆ 2014-01-010:00Z ┆ Demographics ┆ 1.6993184e7 │
41
+ │ … ┆ … ┆ … ┆ … ┆ … │
42
+ │ Netherlands ┆ United Nations ┆ 2020-01-010:00Z ┆ Demographics ┆ 1.7601682e7 │
43
+ │ Netherlands ┆ United Nations ┆ 2021-01-010:00Z ┆ Demographics ┆ 1.767178e7 │
44
+ │ Netherlands ┆ United Nations ┆ 2022-01-010:00Z ┆ Demographics ┆ 1.7789347e7 │
45
+ │ Netherlands ┆ United Nations ┆ 2023-01-010:00Z ┆ Demographics ┆ 1.8019495e7 │
46
+ │ Netherlands ┆ United Nations ┆ 2024-01-010:00Z ┆ Demographics ┆ null │
47
+ └─────────────┴────────────────┴─────────────────┴─────────────────┴─────────────┘
48
+
49
+
50
+ Retrieving the GeoJSON for the Netherlands and its provinces is straightforward as well:
51
+
52
+ .. code-block:: python
53
+
54
+ >>> nl_geojson = nl.geojsons()
55
+ >>> nl_geojson
56
+ TheDataGardenRegionGeoJSONModel : GeoJSON : (count=1)
57
+ >>> nl_geojson(region_level=2) # Retrieve GeoJSON for 2nd regional level (provinces)
58
+ TheDataGardenRegionGeoJSONModel : GeoJSON : (count=13) # 12 provinces + 1 country
59
+ >>> df = nl_geojson.to_polars()
60
+ >>> df["name", "region_type", "local_region_code", "region_level", "feature"]
61
+ ┌───────────────┬─────────────┬───────────────┬──────────────┬────────────────────────┐
62
+ │ name ┆ region_type ┆ local_region_c┆ region_level ┆ feature │
63
+ │ --- ┆ --- ┆ --- ┆ --- ┆ --- │
64
+ │ str ┆ str ┆ str ┆ i64 ┆ struct[3] │
65
+ ╞═══════════════╪═════════════╪═══════════════╪══════════════╪════════════════════════╡
66
+ │ Netherlands ┆ country ┆ 528 ┆ 0 ┆ {"Feature",{"Netherland│
67
+ │ Drenthe ┆ province ┆ NL13 ┆ 2 ┆ {"Feature",{"Drenthe",2│
68
+ │ Flevoland ┆ province ┆ NL23 ┆ 2 ┆ {"Feature",{"Flevoland"│
69
+ │ Friesland ┆ province ┆ NL12 ┆ 2 ┆ {"Feature",{"Friesland"│
70
+ │ Gelderland ┆ province ┆ NL22 ┆ 2 ┆ {"Feature",{"Gelderland│
71
+ │ … ┆ … ┆ … ┆ … ┆ … │
72
+ │ Noord-Holland ┆ province ┆ NL32 ┆ 2 ┆ {"Feature",{"Noord-Holl│
73
+ │ Overijssel ┆ province ┆ NL21 ┆ 2 ┆ {"Feature",{"Overijssel│
74
+ │ Utrecht ┆ province ┆ NL31 ┆ 2 ┆ {"Feature",{"Utrecht",2│
75
+ │ Zeeland ┆ province ┆ NL34 ┆ 2 ┆ {"Feature",{"Zeeland",2│
76
+ │ Zuid-Holland ┆ province ┆ NL33 ┆ 2 ┆ {"Feature",{"Zuid-Holla│
77
+ └───────────────┴─────────────┴───────────────┴──────────────┴────────────────────────┘
78
+
79
+ For readability, the output only a limited number of dataframe columns are displayed.
80
+ Attributes in both the demographics and geojson dataframes are available to connect the geojson to
81
+ the demographics data. This allows you quickly make data sets that contain both demographics and geojson data
82
+ for further analysis or visualisation in map applications.
83
+
84
+
85
+ Read more
86
+ ---------
87
+
88
+ * `The DataGarden Website <https://www.the-datagarden.io>`_
89
+ * `API Documentation <https://www.the-datagarden.io/api-docs>`_
90
+ * `The Datagarden Models <https://www.the-datagarden.io/data-docs>`_
91
+ * `GitHub Repository <https://github.com/MaartendeRuyter/dg-the-datagarden>`_
92
+
93
+ Access to The DataGarden API
94
+ ----------------------------
95
+ To use the DataGarden SDK, you need access to the The DataGarden API. Simply register for free at https://www.the-datagarden.io
96
+ and you will have an inital free access account to the API with access to country and continent data.
97
+
98
+ Visit https://www.the-datagarden.io for to register for free.
@@ -0,0 +1,84 @@
1
+ [project]
2
+ name = "the-datagarden"
3
+ version = "1.2.1"
4
+ description = "Public data made easy."
5
+ readme = "README.rst"
6
+ requires-python = ">=3.10"
7
+ license = { text = "MIT" } # Specifies the MIT license
8
+ authors = [
9
+ { name = "Maarten de Ruyter", email = "info@the-datagarden.io" }
10
+ ]
11
+ dependencies = [
12
+ "click>=8.1.7",
13
+ "pandas>=2.2.3",
14
+ "polars>=1.15.0",
15
+ "pydantic>=2.9.2",
16
+ "pyjwt>=2.10.0",
17
+ "python-decouple>=3.8",
18
+ "requests>=2.32.3",
19
+ "the-datagarden-models>=1.6.3",
20
+ ]
21
+ classifiers = [
22
+ "Programming Language :: Python :: 3",
23
+ "Programming Language :: Python :: 3.10",
24
+ "Programming Language :: Python :: 3.11",
25
+ "Programming Language :: Python :: 3.12",
26
+ "Programming Language :: Python :: 3.13",
27
+ "License :: OSI Approved :: MIT License",
28
+ "Operating System :: OS Independent",
29
+ "Development Status :: 4 - Beta",
30
+ "Intended Audience :: Developers",
31
+ "Intended Audience :: Financial and Insurance Industry",
32
+ "Intended Audience :: Science/Research",
33
+ "Intended Audience :: Healthcare Industry",
34
+ "Topic :: Scientific/Engineering :: GIS",
35
+ "Topic :: Scientific/Engineering :: Artificial Intelligence",
36
+ "Topic :: Scientific/Engineering :: Information Analysis",
37
+ "Topic :: Scientific/Engineering :: Visualization",
38
+ "Topic :: Software Development :: Libraries :: Python Modules",
39
+ "Topic :: Utilities",
40
+
41
+ ]
42
+
43
+ [dependency-groups]
44
+ dev = [
45
+ "mypy>=1.13.0",
46
+ "pre-commit>=4.0.1",
47
+ "pytest>=8.3.3",
48
+ "ruff>=0.7.4",
49
+ "sphinx>=8.1.3",
50
+ "types-requests>=2.32.0.20241016",
51
+ ]
52
+
53
+ [project.scripts]
54
+ the-datagarden = "the_datagarden.cli:main"
55
+
56
+ [project.urls]
57
+ "Read the Docs" = "https://the-datagarden.readthedocs.io/"
58
+ "The-DataGarden" = "https://www.the-datagarden.io/"
59
+ "API documentation" = "https://api.the-datagarden.io/api-docs"
60
+ "Source" = "https://github.com/the-datagarden/the-datagarden"
61
+
62
+
63
+ [tool.ruff]
64
+ line-length = 110
65
+
66
+ [tool.ruff.lint]
67
+ select = [
68
+ "C", # mccabe rules
69
+ "F", # pyflakes rules
70
+ "E", # pycodestyle error rules
71
+ "W", # pycodestyle warning rules
72
+ "B", # flake8-bugbear rules
73
+ "I", # isort rules
74
+ ]
75
+ ignore = [
76
+ "C901", # max-complexity-10
77
+ ]
78
+
79
+ [tool.ruff.format]
80
+ indent-style = "space"
81
+ quote-style = "double"
82
+
83
+ [tool.mypy]
84
+ ignore_missing_imports = true
@@ -0,0 +1,8 @@
1
+ from .api.authentication.environment import TheDatagardenLocalEnvironment
2
+ from .api.base import TheDataGardenAPI, TheDatagardenProductionEnvironment
3
+
4
+ __all__ = [
5
+ "TheDataGardenAPI",
6
+ "TheDatagardenProductionEnvironment",
7
+ "TheDatagardenLocalEnvironment",
8
+ ]
@@ -0,0 +1,3 @@
1
+ from .authentication import BaseDataGardenCredentials, DatagardenEnvironment
2
+
3
+ __all__ = ["DatagardenEnvironment", "BaseDataGardenCredentials"]
@@ -0,0 +1,19 @@
1
+ from abc import ABC, abstractmethod
2
+
3
+ from requests import Response
4
+
5
+ from the_datagarden.abc.authentication import DatagardenEnvironment
6
+
7
+
8
+ class BaseApi(ABC):
9
+ @abstractmethod
10
+ def __init__(self, environment: type[DatagardenEnvironment] | None = None): ...
11
+
12
+ @abstractmethod
13
+ def retrieve_from_api(
14
+ self,
15
+ url_extension: str,
16
+ method: str = "GET",
17
+ payload: dict | None = None,
18
+ params: dict | None = None,
19
+ ) -> Response | None: ...
@@ -0,0 +1,42 @@
1
+ from abc import ABC, abstractmethod
2
+ from typing import TypedDict
3
+
4
+
5
+ class TheDatagardenCredentialsDict(TypedDict):
6
+ email: str
7
+ password: str
8
+
9
+
10
+ class BaseDataGardenCredentials(ABC):
11
+ """Protocol for the datagarden credentials"""
12
+
13
+ @classmethod
14
+ @abstractmethod
15
+ def credentials(
16
+ cls, the_datagarden_api_url: str, email: str | None = None, password: str | None = None
17
+ ) -> TheDatagardenCredentialsDict: ...
18
+
19
+
20
+ class DatagardenEnvironment(ABC):
21
+ """Protocol for the datagarden environment"""
22
+
23
+ CREDENTIALS: type[BaseDataGardenCredentials]
24
+ THE_DATAGARDEN_URL: str
25
+ ECHO_INIT: bool = True
26
+
27
+ def __init__(self):
28
+ if self.ECHO_INIT:
29
+ print("Initializing :", self.__class__.__name__)
30
+ print("At :", self.the_datagarden_url)
31
+
32
+ def credentials(
33
+ self, email: str | None = None, password: str | None = None
34
+ ) -> TheDatagardenCredentialsDict:
35
+ return self.CREDENTIALS.credentials(self.the_datagarden_url, email, password)
36
+
37
+ @property
38
+ def the_datagarden_url(self) -> str:
39
+ url = self.THE_DATAGARDEN_URL
40
+ if url[-1] != "/":
41
+ return url + "/"
42
+ return url
@@ -0,0 +1,5 @@
1
+ from .authentication import AccessToken
2
+ from .authentication.environment import TheDatagardenProductionEnvironment
3
+ from .base import TheDataGardenAPI
4
+
5
+ __all__ = ["AccessToken", "TheDatagardenProductionEnvironment", "TheDataGardenAPI"]
@@ -0,0 +1,112 @@
1
+ import json
2
+ from datetime import UTC, datetime, timedelta
3
+
4
+ import jwt
5
+ import requests
6
+ from requests import Response
7
+
8
+ from ...abc.authentication import DatagardenEnvironment
9
+ from .settings import (
10
+ BEARER_KEY,
11
+ DEFAULT_HEADER,
12
+ REFRESH_TOKEN_URL_EXTENSION,
13
+ REQ_TOKEN_URL_EXTENSION,
14
+ )
15
+
16
+
17
+ class AccessToken:
18
+ _instance = None
19
+ _tokens: dict = {}
20
+ TOKEN_LIFE_TIME_MARGIN: int = 20
21
+ ACCESS_TOKEN_KEY = "access"
22
+ REFRESH_TOKEN_KEY = "refresh"
23
+
24
+ def __new__(cls, *args, **kwargs):
25
+ if cls._instance is None:
26
+ cls._instance = super().__new__(cls)
27
+ return cls._instance
28
+
29
+ def __init__(
30
+ self,
31
+ environment: type[DatagardenEnvironment],
32
+ email: str | None = None,
33
+ password: str | None = None,
34
+ ) -> None:
35
+ self._environment = environment()
36
+ self._token_payload = self._environment.credentials(email, password)
37
+ self._token_header = DEFAULT_HEADER.copy()
38
+
39
+ @property
40
+ def _token_url(self) -> str:
41
+ return self._the_datagarden_url + REQ_TOKEN_URL_EXTENSION
42
+
43
+ @property
44
+ def _refresh_token_url(self) -> str:
45
+ return self._the_datagarden_url + REFRESH_TOKEN_URL_EXTENSION
46
+
47
+ @property
48
+ def _the_datagarden_url(self) -> str:
49
+ return self._environment.the_datagarden_url
50
+
51
+ def _access_token_expired(self) -> bool:
52
+ if self._token_expiry_time:
53
+ return datetime.now(tz=UTC) + timedelta(seconds=5) > self._token_expiry_time
54
+ return True
55
+
56
+ @property
57
+ def _access_token(self) -> str:
58
+ if not self._tokens:
59
+ self._request_tokens()
60
+ elif self._access_token_expired():
61
+ self._get_refresh_token()
62
+ return self._tokens.get(self.ACCESS_TOKEN_KEY, "")
63
+
64
+ @property
65
+ def header_with_access_token(self) -> dict[str, str]:
66
+ header = DEFAULT_HEADER.copy()
67
+ header["Authorization"] = BEARER_KEY + self._access_token
68
+ return header
69
+
70
+ def _request_tokens(self):
71
+ response = requests.request(
72
+ method="POST",
73
+ url=self._token_url,
74
+ headers=self._token_header,
75
+ data=json.dumps(self._token_payload),
76
+ )
77
+ if not response.status_code == 200:
78
+ print("Token request failed and returned error(s): ")
79
+ print(" Errorr : ", response.json().get("detail", "No error details provided"))
80
+ quit()
81
+
82
+ self._tokens = self._get_response_data(response)
83
+ self._set_token_expiry_time()
84
+
85
+ def _get_refresh_token(self):
86
+ response = requests.request(
87
+ method="POST",
88
+ url=self._refresh_token_url,
89
+ headers=self._token_header,
90
+ data=json.dumps(self._refresh_payload()),
91
+ )
92
+ if not response.status_code == 200:
93
+ raise ValueError("Token request failed and returned error: " f"{response.text}")
94
+ self._tokens = self._get_response_data(response)
95
+ self._set_token_expiry_time()
96
+
97
+ def _refresh_payload(self) -> dict:
98
+ refresh_token = self._tokens.get(self.REFRESH_TOKEN_KEY, "")
99
+ return {"refresh": refresh_token}
100
+
101
+ def _set_token_expiry_time(self):
102
+ if not self._tokens:
103
+ return
104
+ access_token = self._tokens.get(self.ACCESS_TOKEN_KEY, "")
105
+ if not access_token:
106
+ raise ValueError("Access token not found in response")
107
+ decoded_token = jwt.decode(access_token, options={"verify_signature": False})
108
+ exp_time_stamp = decoded_token["exp"]
109
+ self._token_expiry_time = datetime.fromtimestamp(timestamp=exp_time_stamp, tz=UTC)
110
+
111
+ def _get_response_data(self, response: Response) -> dict[str, str]:
112
+ return json.loads(response.text)
@@ -0,0 +1,120 @@
1
+ import requests
2
+ from decouple import config
3
+
4
+ from the_datagarden.abc.authentication import BaseDataGardenCredentials, TheDatagardenCredentialsDict
5
+ from the_datagarden.api.authentication.settings import REGISTRATION_URL_EXTENSION
6
+
7
+
8
+ class CredentialsFromUserInput:
9
+ def get_missing_credentials(self, the_datagarden_api_url: str) -> TheDatagardenCredentialsDict:
10
+ print()
11
+ print("Welcome to The Data Garden API.")
12
+ print()
13
+ print(" You can start using the API with an account from The-Datagarden.io.")
14
+ print(" Please provide your credentials or create a new account.")
15
+ print(" Check www.the-datagarden.io for more information.")
16
+ print()
17
+ choice = input(
18
+ "Do you want to (1) create a new account or (2) provide existing credentials? " "Enter 1 or 2: "
19
+ )
20
+
21
+ if choice == "1":
22
+ credentials = self.enroll_to_api(the_datagarden_api_url)
23
+ if not credentials:
24
+ quit()
25
+ return credentials
26
+
27
+ elif choice == "2":
28
+ return self.provide_existing_credentials()
29
+ else:
30
+ print("Invalid choice. Please enter 1 or 2.")
31
+ return self.get_missing_credentials(the_datagarden_api_url)
32
+
33
+ def enroll_to_api(self, the_datagarden_api_url: str) -> TheDatagardenCredentialsDict | None:
34
+ print("Enrolling in The Data Garden API...")
35
+ print()
36
+ email = input(" Enter your email: ")
37
+ password = self.get_confirmed_password()
38
+ print()
39
+
40
+ data = {"email": email, "password": password}
41
+ registration_url = the_datagarden_api_url + REGISTRATION_URL_EXTENSION
42
+ response = requests.post(registration_url, data=data)
43
+ if response.status_code == 201:
44
+ print("Successfully enrolled in The Data Garden API.")
45
+ return TheDatagardenCredentialsDict(
46
+ email=email,
47
+ password=password,
48
+ )
49
+ else:
50
+ print("Enrollment failed with error(s):")
51
+ for field, message_list in response.json().items():
52
+ print(f" {field}: {message_list[0]}")
53
+
54
+ return None
55
+
56
+ def get_confirmed_password(self) -> str:
57
+ while True:
58
+ password = input(" Enter your password: ")
59
+ confirm_password = input(" Confirm your password: ")
60
+
61
+ if password == confirm_password:
62
+ return password
63
+ else:
64
+ print("Passwords do not match. Please try again.")
65
+
66
+ def provide_existing_credentials(self) -> TheDatagardenCredentialsDict:
67
+ print("Please provide your existing credentials...")
68
+ print()
69
+ email = input(" Enter your email: ")
70
+ password = input(" Enter your password: ")
71
+ return TheDatagardenCredentialsDict(
72
+ email=email,
73
+ password=password,
74
+ )
75
+
76
+ def credentials(self, the_datagarden_api_url: str) -> TheDatagardenCredentialsDict:
77
+ return self.get_missing_credentials(the_datagarden_api_url)
78
+
79
+
80
+ class TheDataGardenCredentials(BaseDataGardenCredentials):
81
+ """
82
+ Manages credentials for The Data Garden API in a production environment.
83
+
84
+ This class handles the retrieval of user credentials (email and password) for
85
+ authenticating with The Data Garden API. It first attempts to fetch credentials
86
+ from environment variables. If not found, it prompts the user for input.
87
+
88
+ Attributes:
89
+ ENV_EMAIL_KEY (str): Environment variable key for the user's email.
90
+ ENV_PASSWORD_KEY (str): Environment variable key for the user's password.
91
+ CREDENTIALS_FROM_USER_INPUT (CredentialsFromUserInput):
92
+ Instance to handle user input for credentials.
93
+
94
+ Methods:
95
+ credentials(): Retrieves and returns the user's credentials.
96
+ """
97
+
98
+ ENV_EMAIL_KEY: str = "THE_DATAGARDEN_USER_EMAIL"
99
+ ENV_PASSWORD_KEY: str = "THE_DATAGARDEN_USER_PASSWORD"
100
+ CREDENTIALS_FROM_USER_INPUT = CredentialsFromUserInput()
101
+
102
+ @classmethod
103
+ def credentials(
104
+ cls, the_datagarden_api_url: str, email: str | None = None, password: str | None = None
105
+ ) -> TheDatagardenCredentialsDict:
106
+ if email and password:
107
+ return TheDatagardenCredentialsDict(
108
+ email=email,
109
+ password=password,
110
+ )
111
+
112
+ datagarden_user_email = str(config(cls.ENV_EMAIL_KEY, default="", cast=str))
113
+ datagarden_user_password = str(config(cls.ENV_PASSWORD_KEY, default="", cast=str))
114
+ if datagarden_user_email and datagarden_user_password:
115
+ return TheDatagardenCredentialsDict(
116
+ email=datagarden_user_email,
117
+ password=datagarden_user_password,
118
+ )
119
+
120
+ return cls.CREDENTIALS_FROM_USER_INPUT.credentials(the_datagarden_api_url)
@@ -0,0 +1,13 @@
1
+ from the_datagarden.abc import DatagardenEnvironment
2
+
3
+ from ..credentials import TheDataGardenCredentials
4
+
5
+
6
+ class TheDatagardenProductionEnvironment(DatagardenEnvironment):
7
+ CREDENTIALS = TheDataGardenCredentials
8
+ THE_DATAGARDEN_URL = "https://api.the-datagarden.io"
9
+
10
+
11
+ class TheDatagardenLocalEnvironment(DatagardenEnvironment):
12
+ CREDENTIALS = TheDataGardenCredentials
13
+ THE_DATAGARDEN_URL = "http://127.0.0.1:8000"