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.
- the_datagarden-1.2.1/PKG-INFO +137 -0
- the_datagarden-1.2.1/README.rst +98 -0
- the_datagarden-1.2.1/pyproject.toml +84 -0
- the_datagarden-1.2.1/src/the_datagarden/__init__.py +8 -0
- the_datagarden-1.2.1/src/the_datagarden/abc/__init__.py +3 -0
- the_datagarden-1.2.1/src/the_datagarden/abc/api.py +19 -0
- the_datagarden-1.2.1/src/the_datagarden/abc/authentication.py +42 -0
- the_datagarden-1.2.1/src/the_datagarden/api/__init__.py +5 -0
- the_datagarden-1.2.1/src/the_datagarden/api/authentication/__init__.py +112 -0
- the_datagarden-1.2.1/src/the_datagarden/api/authentication/credentials/__init__.py +120 -0
- the_datagarden-1.2.1/src/the_datagarden/api/authentication/environment/__init__.py +13 -0
- the_datagarden-1.2.1/src/the_datagarden/api/authentication/settings.py +54 -0
- the_datagarden-1.2.1/src/the_datagarden/api/base/__init__.py +215 -0
- the_datagarden-1.2.1/src/the_datagarden/api/regions/__init__.py +4 -0
- the_datagarden-1.2.1/src/the_datagarden/api/regions/base/__init__.py +108 -0
- the_datagarden-1.2.1/src/the_datagarden/api/regions/base/settings.py +19 -0
- the_datagarden-1.2.1/src/the_datagarden/api/regions/continent.py +9 -0
- the_datagarden-1.2.1/src/the_datagarden/api/regions/country.py +9 -0
- the_datagarden-1.2.1/src/the_datagarden/models/__init__.py +9 -0
- the_datagarden-1.2.1/src/the_datagarden/models/geojson.py +179 -0
- the_datagarden-1.2.1/src/the_datagarden/models/regional_data.py +411 -0
- the_datagarden-1.2.1/src/the_datagarden/version.py +1 -0
- the_datagarden-1.2.1/src/the_datagarden.egg-info/PKG-INFO +137 -0
- the_datagarden-1.2.1/src/the_datagarden.egg-info/SOURCES.txt +30 -0
- the_datagarden-1.2.1/src/the_datagarden.egg-info/requires.txt +8 -0
- the_datagarden-0.1.0/PKG-INFO +0 -17
- the_datagarden-0.1.0/README.md +0 -0
- the_datagarden-0.1.0/pyproject.toml +0 -70
- the_datagarden-0.1.0/src/the_datagarden/__init__.py +0 -0
- the_datagarden-0.1.0/src/the_datagarden.egg-info/PKG-INFO +0 -17
- the_datagarden-0.1.0/src/the_datagarden.egg-info/SOURCES.txt +0 -12
- the_datagarden-0.1.0/src/the_datagarden.egg-info/requires.txt +0 -2
- {the_datagarden-0.1.0 → the_datagarden-1.2.1}/setup.cfg +0 -0
- {the_datagarden-0.1.0 → the_datagarden-1.2.1}/src/the_datagarden/cli.py +0 -0
- {the_datagarden-0.1.0 → the_datagarden-1.2.1}/src/the_datagarden.egg-info/dependency_links.txt +0 -0
- {the_datagarden-0.1.0 → the_datagarden-1.2.1}/src/the_datagarden.egg-info/entry_points.txt +0 -0
- {the_datagarden-0.1.0 → the_datagarden-1.2.1}/src/the_datagarden.egg-info/top_level.txt +0 -0
- {the_datagarden-0.1.0 → the_datagarden-1.2.1}/tests/test_cli.py +0 -0
- {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,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,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"
|