cardo-python-utils 0.4.2__tar.gz → 0.5.dev0__tar.gz
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.
- {cardo_python_utils-0.4.2/cardo_python_utils.egg-info → cardo_python_utils-0.5.dev0}/PKG-INFO +29 -22
- {cardo_python_utils-0.4.2 → cardo_python_utils-0.5.dev0}/README.rst +2 -4
- {cardo_python_utils-0.4.2 → cardo_python_utils-0.5.dev0/cardo_python_utils.egg-info}/PKG-INFO +29 -22
- {cardo_python_utils-0.4.2 → cardo_python_utils-0.5.dev0}/cardo_python_utils.egg-info/SOURCES.txt +7 -6
- cardo_python_utils-0.5.dev0/cardo_python_utils.egg-info/requires.txt +26 -0
- {cardo_python_utils-0.4.2 → cardo_python_utils-0.5.dev0}/cardo_python_utils.egg-info/top_level.txt +1 -0
- cardo_python_utils-0.5.dev0/pyproject.toml +81 -0
- {cardo_python_utils-0.4.2 → cardo_python_utils-0.5.dev0}/python_utils/choices.py +1 -1
- cardo_python_utils-0.5.dev0/python_utils/django/auth/__init__.py +0 -0
- cardo_python_utils-0.5.dev0/python_utils/django/auth/admin.py +49 -0
- cardo_python_utils-0.5.dev0/python_utils/django/auth/drf.py +117 -0
- cardo_python_utils-0.5.dev0/python_utils/django/auth/ninja.py +128 -0
- cardo_python_utils-0.4.2/python_utils/django_utils.py → cardo_python_utils-0.5.dev0/python_utils/django/utils.py +0 -38
- {cardo_python_utils-0.4.2 → cardo_python_utils-0.5.dev0}/python_utils/text.py +5 -4
- cardo_python_utils-0.5.dev0/setup.cfg +4 -0
- cardo_python_utils-0.4.2/cardo_python_utils.egg-info/requires.txt +0 -18
- cardo_python_utils-0.4.2/python_utils/pandas_utils.py +0 -143
- cardo_python_utils-0.4.2/python_utils/rest.py +0 -37
- cardo_python_utils-0.4.2/setup.cfg +0 -44
- cardo_python_utils-0.4.2/setup.py +0 -7
- {cardo_python_utils-0.4.2 → cardo_python_utils-0.5.dev0}/LICENSE +0 -0
- {cardo_python_utils-0.4.2 → cardo_python_utils-0.5.dev0}/MANIFEST.in +0 -0
- {cardo_python_utils-0.4.2 → cardo_python_utils-0.5.dev0}/cardo_python_utils.egg-info/dependency_links.txt +0 -0
- {cardo_python_utils-0.4.2 → cardo_python_utils-0.5.dev0}/python_utils/__init__.py +0 -0
- {cardo_python_utils-0.4.2 → cardo_python_utils-0.5.dev0}/python_utils/data_structures.py +0 -0
- {cardo_python_utils-0.4.2 → cardo_python_utils-0.5.dev0}/python_utils/db.py +0 -0
- {cardo_python_utils-0.4.2 → cardo_python_utils-0.5.dev0}/python_utils/esma_choices.py +0 -0
- {cardo_python_utils-0.4.2 → cardo_python_utils-0.5.dev0}/python_utils/exceptions.py +0 -0
- {cardo_python_utils-0.4.2 → cardo_python_utils-0.5.dev0}/python_utils/imports.py +0 -0
- {cardo_python_utils-0.4.2 → cardo_python_utils-0.5.dev0}/python_utils/math.py +0 -0
- {cardo_python_utils-0.4.2 → cardo_python_utils-0.5.dev0}/python_utils/time.py +0 -0
- {cardo_python_utils-0.4.2 → cardo_python_utils-0.5.dev0}/python_utils/types_hinting.py +0 -0
{cardo_python_utils-0.4.2/cardo_python_utils.egg-info → cardo_python_utils-0.5.dev0}/PKG-INFO
RENAMED
|
@@ -1,40 +1,49 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cardo-python-utils
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.dev0
|
|
4
4
|
Summary: Python library enhanced with a wide range of functions for different scenarios.
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
Author-email: Kristi Kotini <hello@cardoai.com>, Klajdi Çaushi <hello@cardoai.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/CardoAI/cardo-python-utils
|
|
8
|
+
Project-URL: Repository, https://github.com/CardoAI/cardo-python-utils.git
|
|
9
|
+
Project-URL: Issues, https://github.com/CardoAI/cardo-python-utils/issues
|
|
10
|
+
Keywords: utilities,helpers,django
|
|
9
11
|
Classifier: Environment :: Web Environment
|
|
10
12
|
Classifier: Framework :: Django
|
|
11
13
|
Classifier: Intended Audience :: Developers
|
|
12
|
-
Classifier: License :: OSI Approved ::
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
15
|
Classifier: Operating System :: OS Independent
|
|
14
16
|
Classifier: Programming Language :: Python
|
|
15
17
|
Classifier: Programming Language :: Python :: 3
|
|
16
18
|
Classifier: Programming Language :: Python :: 3 :: Only
|
|
17
|
-
Classifier: Programming Language :: Python :: 3.8
|
|
18
|
-
Classifier: Programming Language :: Python :: 3.9
|
|
19
19
|
Classifier: Programming Language :: Python :: 3.10
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
22
|
Classifier: Topic :: Internet :: WWW/HTTP
|
|
21
23
|
Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
|
|
22
24
|
Requires-Python: >=3.8
|
|
25
|
+
Description-Content-Type: text/x-rst
|
|
23
26
|
License-File: LICENSE
|
|
24
|
-
Provides-Extra: pandas
|
|
25
|
-
Requires-Dist: pandas>=1.4.0; extra == "pandas"
|
|
26
27
|
Provides-Extra: django
|
|
27
28
|
Requires-Dist: Django; extra == "django"
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
Requires-Dist:
|
|
31
|
-
Requires-Dist:
|
|
29
|
+
Provides-Extra: django-ninja
|
|
30
|
+
Requires-Dist: Django; extra == "django-ninja"
|
|
31
|
+
Requires-Dist: django-ninja; extra == "django-ninja"
|
|
32
|
+
Requires-Dist: PyJWT; extra == "django-ninja"
|
|
33
|
+
Provides-Extra: drf
|
|
34
|
+
Requires-Dist: Django; extra == "drf"
|
|
35
|
+
Requires-Dist: djangorestframework; extra == "drf"
|
|
36
|
+
Requires-Dist: PyJWT; extra == "drf"
|
|
37
|
+
Provides-Extra: django-admin-auth
|
|
38
|
+
Requires-Dist: Django; extra == "django-admin-auth"
|
|
39
|
+
Requires-Dist: mozilla-django-oidc>=4.0.1; extra == "django-admin-auth"
|
|
32
40
|
Provides-Extra: all
|
|
33
41
|
Requires-Dist: Django; extra == "all"
|
|
34
|
-
|
|
35
|
-
Requires-Dist:
|
|
36
|
-
Requires-Dist:
|
|
37
|
-
Requires-Dist:
|
|
42
|
+
Provides-Extra: dev
|
|
43
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
44
|
+
Requires-Dist: pytest-django>=4.5; extra == "dev"
|
|
45
|
+
Requires-Dist: coverage>=6.0; extra == "dev"
|
|
46
|
+
Requires-Dist: tox>=3.25; extra == "dev"
|
|
38
47
|
Dynamic: license-file
|
|
39
48
|
|
|
40
49
|
============================
|
|
@@ -50,9 +59,7 @@ Main utils:
|
|
|
50
59
|
* data_structures
|
|
51
60
|
* db
|
|
52
61
|
* django
|
|
53
|
-
* django_rest
|
|
54
62
|
* math
|
|
55
|
-
* pandas
|
|
56
63
|
* exception
|
|
57
64
|
* choices
|
|
58
65
|
|
|
@@ -64,7 +71,7 @@ Quick start
|
|
|
64
71
|
from python_utils.time import date_range
|
|
65
72
|
date_range(start_date, end_date)
|
|
66
73
|
|
|
67
|
-
Although the library provides some utility functions related to other libraries like django
|
|
74
|
+
Although the library provides some utility functions related to other libraries like django, it does not install any dependencies automatically.
|
|
68
75
|
This means, you can install the library even if you do not use these libraries, but keep in mind that in this case you cannot use the
|
|
69
76
|
functions that depend on them.
|
|
70
77
|
|
|
@@ -74,7 +81,7 @@ You can also chose to install the dependencies alongside the library, including
|
|
|
74
81
|
|
|
75
82
|
Tests
|
|
76
83
|
-----
|
|
77
|
-
The library has a
|
|
84
|
+
The library has a high coverage by tests. If you want to see tests in action:
|
|
78
85
|
|
|
79
86
|
1. Inside venv, run ``pip install -r tests/requirements.txt``
|
|
80
87
|
|
|
@@ -11,9 +11,7 @@ Main utils:
|
|
|
11
11
|
* data_structures
|
|
12
12
|
* db
|
|
13
13
|
* django
|
|
14
|
-
* django_rest
|
|
15
14
|
* math
|
|
16
|
-
* pandas
|
|
17
15
|
* exception
|
|
18
16
|
* choices
|
|
19
17
|
|
|
@@ -25,7 +23,7 @@ Quick start
|
|
|
25
23
|
from python_utils.time import date_range
|
|
26
24
|
date_range(start_date, end_date)
|
|
27
25
|
|
|
28
|
-
Although the library provides some utility functions related to other libraries like django
|
|
26
|
+
Although the library provides some utility functions related to other libraries like django, it does not install any dependencies automatically.
|
|
29
27
|
This means, you can install the library even if you do not use these libraries, but keep in mind that in this case you cannot use the
|
|
30
28
|
functions that depend on them.
|
|
31
29
|
|
|
@@ -35,7 +33,7 @@ You can also chose to install the dependencies alongside the library, including
|
|
|
35
33
|
|
|
36
34
|
Tests
|
|
37
35
|
-----
|
|
38
|
-
The library has a
|
|
36
|
+
The library has a high coverage by tests. If you want to see tests in action:
|
|
39
37
|
|
|
40
38
|
1. Inside venv, run ``pip install -r tests/requirements.txt``
|
|
41
39
|
|
{cardo_python_utils-0.4.2 → cardo_python_utils-0.5.dev0/cardo_python_utils.egg-info}/PKG-INFO
RENAMED
|
@@ -1,40 +1,49 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: cardo-python-utils
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.5.dev0
|
|
4
4
|
Summary: Python library enhanced with a wide range of functions for different scenarios.
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
5
|
+
Author-email: Kristi Kotini <hello@cardoai.com>, Klajdi Çaushi <hello@cardoai.com>
|
|
6
|
+
License: MIT
|
|
7
|
+
Project-URL: Homepage, https://github.com/CardoAI/cardo-python-utils
|
|
8
|
+
Project-URL: Repository, https://github.com/CardoAI/cardo-python-utils.git
|
|
9
|
+
Project-URL: Issues, https://github.com/CardoAI/cardo-python-utils/issues
|
|
10
|
+
Keywords: utilities,helpers,django
|
|
9
11
|
Classifier: Environment :: Web Environment
|
|
10
12
|
Classifier: Framework :: Django
|
|
11
13
|
Classifier: Intended Audience :: Developers
|
|
12
|
-
Classifier: License :: OSI Approved ::
|
|
14
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
13
15
|
Classifier: Operating System :: OS Independent
|
|
14
16
|
Classifier: Programming Language :: Python
|
|
15
17
|
Classifier: Programming Language :: Python :: 3
|
|
16
18
|
Classifier: Programming Language :: Python :: 3 :: Only
|
|
17
|
-
Classifier: Programming Language :: Python :: 3.8
|
|
18
|
-
Classifier: Programming Language :: Python :: 3.9
|
|
19
19
|
Classifier: Programming Language :: Python :: 3.10
|
|
20
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
21
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
20
22
|
Classifier: Topic :: Internet :: WWW/HTTP
|
|
21
23
|
Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
|
|
22
24
|
Requires-Python: >=3.8
|
|
25
|
+
Description-Content-Type: text/x-rst
|
|
23
26
|
License-File: LICENSE
|
|
24
|
-
Provides-Extra: pandas
|
|
25
|
-
Requires-Dist: pandas>=1.4.0; extra == "pandas"
|
|
26
27
|
Provides-Extra: django
|
|
27
28
|
Requires-Dist: Django; extra == "django"
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
Requires-Dist:
|
|
31
|
-
Requires-Dist:
|
|
29
|
+
Provides-Extra: django-ninja
|
|
30
|
+
Requires-Dist: Django; extra == "django-ninja"
|
|
31
|
+
Requires-Dist: django-ninja; extra == "django-ninja"
|
|
32
|
+
Requires-Dist: PyJWT; extra == "django-ninja"
|
|
33
|
+
Provides-Extra: drf
|
|
34
|
+
Requires-Dist: Django; extra == "drf"
|
|
35
|
+
Requires-Dist: djangorestframework; extra == "drf"
|
|
36
|
+
Requires-Dist: PyJWT; extra == "drf"
|
|
37
|
+
Provides-Extra: django-admin-auth
|
|
38
|
+
Requires-Dist: Django; extra == "django-admin-auth"
|
|
39
|
+
Requires-Dist: mozilla-django-oidc>=4.0.1; extra == "django-admin-auth"
|
|
32
40
|
Provides-Extra: all
|
|
33
41
|
Requires-Dist: Django; extra == "all"
|
|
34
|
-
|
|
35
|
-
Requires-Dist:
|
|
36
|
-
Requires-Dist:
|
|
37
|
-
Requires-Dist:
|
|
42
|
+
Provides-Extra: dev
|
|
43
|
+
Requires-Dist: pytest>=7.0; extra == "dev"
|
|
44
|
+
Requires-Dist: pytest-django>=4.5; extra == "dev"
|
|
45
|
+
Requires-Dist: coverage>=6.0; extra == "dev"
|
|
46
|
+
Requires-Dist: tox>=3.25; extra == "dev"
|
|
38
47
|
Dynamic: license-file
|
|
39
48
|
|
|
40
49
|
============================
|
|
@@ -50,9 +59,7 @@ Main utils:
|
|
|
50
59
|
* data_structures
|
|
51
60
|
* db
|
|
52
61
|
* django
|
|
53
|
-
* django_rest
|
|
54
62
|
* math
|
|
55
|
-
* pandas
|
|
56
63
|
* exception
|
|
57
64
|
* choices
|
|
58
65
|
|
|
@@ -64,7 +71,7 @@ Quick start
|
|
|
64
71
|
from python_utils.time import date_range
|
|
65
72
|
date_range(start_date, end_date)
|
|
66
73
|
|
|
67
|
-
Although the library provides some utility functions related to other libraries like django
|
|
74
|
+
Although the library provides some utility functions related to other libraries like django, it does not install any dependencies automatically.
|
|
68
75
|
This means, you can install the library even if you do not use these libraries, but keep in mind that in this case you cannot use the
|
|
69
76
|
functions that depend on them.
|
|
70
77
|
|
|
@@ -74,7 +81,7 @@ You can also chose to install the dependencies alongside the library, including
|
|
|
74
81
|
|
|
75
82
|
Tests
|
|
76
83
|
-----
|
|
77
|
-
The library has a
|
|
84
|
+
The library has a high coverage by tests. If you want to see tests in action:
|
|
78
85
|
|
|
79
86
|
1. Inside venv, run ``pip install -r tests/requirements.txt``
|
|
80
87
|
|
{cardo_python_utils-0.4.2 → cardo_python_utils-0.5.dev0}/cardo_python_utils.egg-info/SOURCES.txt
RENAMED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
LICENSE
|
|
2
2
|
MANIFEST.in
|
|
3
3
|
README.rst
|
|
4
|
-
|
|
5
|
-
setup.py
|
|
4
|
+
pyproject.toml
|
|
6
5
|
cardo_python_utils.egg-info/PKG-INFO
|
|
7
6
|
cardo_python_utils.egg-info/SOURCES.txt
|
|
8
7
|
cardo_python_utils.egg-info/dependency_links.txt
|
|
@@ -12,13 +11,15 @@ python_utils/__init__.py
|
|
|
12
11
|
python_utils/choices.py
|
|
13
12
|
python_utils/data_structures.py
|
|
14
13
|
python_utils/db.py
|
|
15
|
-
python_utils/django_utils.py
|
|
16
14
|
python_utils/esma_choices.py
|
|
17
15
|
python_utils/exceptions.py
|
|
18
16
|
python_utils/imports.py
|
|
19
17
|
python_utils/math.py
|
|
20
|
-
python_utils/pandas_utils.py
|
|
21
|
-
python_utils/rest.py
|
|
22
18
|
python_utils/text.py
|
|
23
19
|
python_utils/time.py
|
|
24
|
-
python_utils/types_hinting.py
|
|
20
|
+
python_utils/types_hinting.py
|
|
21
|
+
python_utils/django/utils.py
|
|
22
|
+
python_utils/django/auth/__init__.py
|
|
23
|
+
python_utils/django/auth/admin.py
|
|
24
|
+
python_utils/django/auth/drf.py
|
|
25
|
+
python_utils/django/auth/ninja.py
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
|
|
2
|
+
[all]
|
|
3
|
+
Django
|
|
4
|
+
|
|
5
|
+
[dev]
|
|
6
|
+
pytest>=7.0
|
|
7
|
+
pytest-django>=4.5
|
|
8
|
+
coverage>=6.0
|
|
9
|
+
tox>=3.25
|
|
10
|
+
|
|
11
|
+
[django]
|
|
12
|
+
Django
|
|
13
|
+
|
|
14
|
+
[django-admin-auth]
|
|
15
|
+
Django
|
|
16
|
+
mozilla-django-oidc>=4.0.1
|
|
17
|
+
|
|
18
|
+
[django-ninja]
|
|
19
|
+
Django
|
|
20
|
+
django-ninja
|
|
21
|
+
PyJWT
|
|
22
|
+
|
|
23
|
+
[drf]
|
|
24
|
+
Django
|
|
25
|
+
djangorestframework
|
|
26
|
+
PyJWT
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=61.0", "wheel"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "cardo-python-utils"
|
|
7
|
+
version = "0.5.dev0"
|
|
8
|
+
description = "Python library enhanced with a wide range of functions for different scenarios."
|
|
9
|
+
readme = "README.rst"
|
|
10
|
+
requires-python = ">=3.8"
|
|
11
|
+
license = {text = "MIT"}
|
|
12
|
+
authors = [
|
|
13
|
+
{name = "Kristi Kotini", email = "hello@cardoai.com"},
|
|
14
|
+
{name = "Klajdi Çaushi", email = "hello@cardoai.com"}
|
|
15
|
+
]
|
|
16
|
+
keywords = ["utilities", "helpers", "django"]
|
|
17
|
+
classifiers = [
|
|
18
|
+
"Environment :: Web Environment",
|
|
19
|
+
"Framework :: Django",
|
|
20
|
+
"Intended Audience :: Developers",
|
|
21
|
+
"License :: OSI Approved :: MIT License",
|
|
22
|
+
"Operating System :: OS Independent",
|
|
23
|
+
"Programming Language :: Python",
|
|
24
|
+
"Programming Language :: Python :: 3",
|
|
25
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
26
|
+
"Programming Language :: Python :: 3.10",
|
|
27
|
+
"Programming Language :: Python :: 3.11",
|
|
28
|
+
"Programming Language :: Python :: 3.12",
|
|
29
|
+
"Topic :: Internet :: WWW/HTTP",
|
|
30
|
+
"Topic :: Internet :: WWW/HTTP :: Dynamic Content",
|
|
31
|
+
]
|
|
32
|
+
dependencies = []
|
|
33
|
+
|
|
34
|
+
[project.optional-dependencies]
|
|
35
|
+
django = [
|
|
36
|
+
"Django",
|
|
37
|
+
]
|
|
38
|
+
django-ninja = [
|
|
39
|
+
"Django",
|
|
40
|
+
"django-ninja",
|
|
41
|
+
"PyJWT",
|
|
42
|
+
]
|
|
43
|
+
drf = [
|
|
44
|
+
"Django",
|
|
45
|
+
"djangorestframework",
|
|
46
|
+
"PyJWT",
|
|
47
|
+
]
|
|
48
|
+
django-admin-auth = [
|
|
49
|
+
"Django",
|
|
50
|
+
"mozilla-django-oidc>=4.0.1",
|
|
51
|
+
]
|
|
52
|
+
all = [
|
|
53
|
+
"Django",
|
|
54
|
+
]
|
|
55
|
+
dev = [
|
|
56
|
+
"pytest>=7.0",
|
|
57
|
+
"pytest-django>=4.5",
|
|
58
|
+
"coverage>=6.0",
|
|
59
|
+
"tox>=3.25",
|
|
60
|
+
]
|
|
61
|
+
|
|
62
|
+
[project.urls]
|
|
63
|
+
Homepage = "https://github.com/CardoAI/cardo-python-utils"
|
|
64
|
+
Repository = "https://github.com/CardoAI/cardo-python-utils.git"
|
|
65
|
+
Issues = "https://github.com/CardoAI/cardo-python-utils/issues"
|
|
66
|
+
|
|
67
|
+
[tool.setuptools]
|
|
68
|
+
include-package-data = true
|
|
69
|
+
|
|
70
|
+
[tool.setuptools.packages.find]
|
|
71
|
+
where = ["."]
|
|
72
|
+
exclude = ["tests", "tests.*"]
|
|
73
|
+
|
|
74
|
+
[tool.pytest.ini_options]
|
|
75
|
+
DJANGO_SETTINGS_MODULE = "tests.settings"
|
|
76
|
+
django_find_project = false
|
|
77
|
+
python_files = ["tests.py", "test_*.py", "*_tests.py"]
|
|
78
|
+
addopts = "--strict-markers --no-migrations --reuse-db --log-cli-level=INFO --doctest-modules"
|
|
79
|
+
|
|
80
|
+
[tool.tox]
|
|
81
|
+
legacy_tox_ini = "tox.ini"
|
|
@@ -76,7 +76,7 @@ class ChoiceEnum(Enum, metaclass=ChoiceEnumMeta):
|
|
|
76
76
|
|
|
77
77
|
@classmethod
|
|
78
78
|
def get_by_value(cls, value: str | int):
|
|
79
|
-
value_index = 0 if
|
|
79
|
+
value_index = 0 if isinstance(value, int) else 1
|
|
80
80
|
return next((v for v in cls.__members__.values() if v.value[value_index] == value), None)
|
|
81
81
|
|
|
82
82
|
@classmethod
|
|
File without changes
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
from django.conf import settings
|
|
2
|
+
from mozilla_django_oidc.auth import OIDCAuthenticationBackend
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class OIDCCustomAuthenticationBackend(OIDCAuthenticationBackend):
|
|
6
|
+
def _get_user_data(self, claims) -> dict:
|
|
7
|
+
client_roles = (
|
|
8
|
+
claims.get("resource_access", {})
|
|
9
|
+
.get(getattr(settings, "OIDC_RP_CLIENT_ID", ""), {})
|
|
10
|
+
.get("roles", [])
|
|
11
|
+
)
|
|
12
|
+
is_superuser = "Admin" in client_roles
|
|
13
|
+
|
|
14
|
+
return {
|
|
15
|
+
"username": claims.get("preferred_username"),
|
|
16
|
+
"email": claims.get("email"),
|
|
17
|
+
"first_name": claims.get("given_name", ""),
|
|
18
|
+
"last_name": claims.get("family_name", ""),
|
|
19
|
+
"is_staff": claims.get("is_staff", False),
|
|
20
|
+
"is_superuser": is_superuser,
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
def filter_users_by_claims(self, claims):
|
|
24
|
+
username = claims.get("preferred_username")
|
|
25
|
+
if not username:
|
|
26
|
+
return self.UserModel.objects.none()
|
|
27
|
+
return self.UserModel.objects.filter(username=username)
|
|
28
|
+
|
|
29
|
+
def create_user(self, claims):
|
|
30
|
+
return self.UserModel.objects.create_user(**self._get_user_data(claims))
|
|
31
|
+
|
|
32
|
+
def update_user(self, user, claims):
|
|
33
|
+
save_needed = False
|
|
34
|
+
|
|
35
|
+
for attr, value in self._get_user_data(claims).items():
|
|
36
|
+
if getattr(user, attr) != value:
|
|
37
|
+
setattr(user, attr, value)
|
|
38
|
+
save_needed = True
|
|
39
|
+
|
|
40
|
+
if save_needed:
|
|
41
|
+
user.save()
|
|
42
|
+
|
|
43
|
+
return user
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def has_permission(request):
|
|
47
|
+
# The user does not need to be staff to access the admin site
|
|
48
|
+
# Only superusers will have access to do anything in the admin site
|
|
49
|
+
return request.user.is_active and request.user.is_superuser
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import jwt
|
|
2
|
+
|
|
3
|
+
from django.conf import settings
|
|
4
|
+
from django.contrib.auth import get_user_model
|
|
5
|
+
from jwt import PyJWKClient
|
|
6
|
+
from jwt.exceptions import InvalidTokenError
|
|
7
|
+
|
|
8
|
+
from rest_framework import authentication
|
|
9
|
+
from rest_framework.exceptions import AuthenticationFailed
|
|
10
|
+
from rest_framework.permissions import BasePermission
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
jwks_client = PyJWKClient(getattr(settings, "JWKS_URL", ""))
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class AuthenticationBackend(authentication.TokenAuthentication):
|
|
17
|
+
keyword = "Bearer"
|
|
18
|
+
|
|
19
|
+
def authenticate_credentials(self, token: str):
|
|
20
|
+
signing_key = jwks_client.get_signing_key_from_jwt(token)
|
|
21
|
+
|
|
22
|
+
try:
|
|
23
|
+
payload = jwt.decode(
|
|
24
|
+
token,
|
|
25
|
+
signing_key.key,
|
|
26
|
+
algorithms=["RS256"],
|
|
27
|
+
audience=getattr(settings, "JWT_AUDIENCE", None),
|
|
28
|
+
)
|
|
29
|
+
except InvalidTokenError as e:
|
|
30
|
+
raise AuthenticationFailed(f"Invalid token: {str(e)}") from e
|
|
31
|
+
|
|
32
|
+
try:
|
|
33
|
+
username = payload["preferred_username"]
|
|
34
|
+
except KeyError as e:
|
|
35
|
+
raise AuthenticationFailed(
|
|
36
|
+
"Invalid token: preferred_username not present."
|
|
37
|
+
) from e
|
|
38
|
+
|
|
39
|
+
user = self._get_user(username, payload)
|
|
40
|
+
return user, payload
|
|
41
|
+
|
|
42
|
+
def _get_user(self, username: str, payload: dict):
|
|
43
|
+
"""
|
|
44
|
+
Get or create a user based on the JWT payload.
|
|
45
|
+
If the user exists, update their details.
|
|
46
|
+
"""
|
|
47
|
+
user_model = get_user_model()
|
|
48
|
+
user_data = {
|
|
49
|
+
"first_name": payload.get("given_name") or "",
|
|
50
|
+
"last_name": payload.get("family_name") or "",
|
|
51
|
+
"email": payload.get("email") or "",
|
|
52
|
+
"is_staff": payload.get("is_staff", False),
|
|
53
|
+
}
|
|
54
|
+
if hasattr(user_model, "is_demo"):
|
|
55
|
+
user_data["is_demo"] = payload.get("is_demo", False)
|
|
56
|
+
|
|
57
|
+
user = user_model.objects.filter(username=username).first()
|
|
58
|
+
if user:
|
|
59
|
+
update_needed = False
|
|
60
|
+
|
|
61
|
+
for field, value in user_data.items():
|
|
62
|
+
if getattr(user, field) != value:
|
|
63
|
+
setattr(user, field, value)
|
|
64
|
+
update_needed = True
|
|
65
|
+
|
|
66
|
+
if update_needed:
|
|
67
|
+
user.save(update_fields=list(user_data.keys()))
|
|
68
|
+
|
|
69
|
+
return user
|
|
70
|
+
else:
|
|
71
|
+
return user_model.objects.create(
|
|
72
|
+
username=username,
|
|
73
|
+
**user_data,
|
|
74
|
+
)
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class HasScope(BasePermission):
|
|
78
|
+
"""
|
|
79
|
+
Permission class to check for allowed scopes in the access token.
|
|
80
|
+
|
|
81
|
+
This permission class checks if any of the scopes defined in the `allowed_scopes`
|
|
82
|
+
attribute of the view are present in the 'scope' claim of the access token.
|
|
83
|
+
|
|
84
|
+
Example Usage in a View:
|
|
85
|
+
|
|
86
|
+
class MyApiView(APIView):
|
|
87
|
+
permission_classes = [IsAuthenticated, HasScope]
|
|
88
|
+
allowed_scopes = ["jobs"]
|
|
89
|
+
...
|
|
90
|
+
|
|
91
|
+
If no particular scope is required, you can set `allowed_scopes = "*"`
|
|
92
|
+
to allow access without scope checks.
|
|
93
|
+
"""
|
|
94
|
+
|
|
95
|
+
def has_permission(self, request, view):
|
|
96
|
+
allowed_scopes = getattr(view, "allowed_scopes", [])
|
|
97
|
+
|
|
98
|
+
if not allowed_scopes:
|
|
99
|
+
raise Exception(
|
|
100
|
+
f"No allowed_scopes defined on the view '{view.__class__.__name__}'. "
|
|
101
|
+
"Define allowed_scopes or set it to '*' to allow any scope."
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
if allowed_scopes == "*":
|
|
105
|
+
return True
|
|
106
|
+
|
|
107
|
+
if not request.auth or "scope" not in request.auth:
|
|
108
|
+
return False
|
|
109
|
+
|
|
110
|
+
token_scopes_str = request.auth.get("scope", "")
|
|
111
|
+
token_scopes = set(token_scopes_str.split())
|
|
112
|
+
|
|
113
|
+
for scope in allowed_scopes:
|
|
114
|
+
if f"{getattr(settings, 'JWT_SCOPE_PREFIX', '')}:{scope}" in token_scopes:
|
|
115
|
+
return True
|
|
116
|
+
|
|
117
|
+
return False
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
from typing import Literal
|
|
2
|
+
|
|
3
|
+
from jwt import PyJWKClient, decode as jwt_decode
|
|
4
|
+
from jwt.exceptions import InvalidTokenError
|
|
5
|
+
|
|
6
|
+
from django.conf import settings
|
|
7
|
+
from django.contrib.auth import get_user_model
|
|
8
|
+
from ninja.security import HttpBearer
|
|
9
|
+
from ninja.errors import AuthenticationError, HttpError
|
|
10
|
+
|
|
11
|
+
jwks_client = PyJWKClient(getattr(settings, "JWKS_URL", ""))
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class AuthBearer(HttpBearer):
|
|
15
|
+
def authenticate(self, request, token):
|
|
16
|
+
signing_key = jwks_client.get_signing_key_from_jwt(token)
|
|
17
|
+
|
|
18
|
+
try:
|
|
19
|
+
payload = jwt_decode(
|
|
20
|
+
token,
|
|
21
|
+
signing_key.key,
|
|
22
|
+
algorithms=["RS256"],
|
|
23
|
+
audience=getattr(settings, "JWT_AUDIENCE", None),
|
|
24
|
+
)
|
|
25
|
+
except InvalidTokenError as e:
|
|
26
|
+
raise AuthenticationError(f"Invalid token: {str(e)}") from e
|
|
27
|
+
|
|
28
|
+
try:
|
|
29
|
+
username = payload["preferred_username"]
|
|
30
|
+
except KeyError as e:
|
|
31
|
+
raise AuthenticationError(
|
|
32
|
+
"Invalid token: preferred_username not present."
|
|
33
|
+
) from e
|
|
34
|
+
|
|
35
|
+
user = self._get_user(username, payload)
|
|
36
|
+
|
|
37
|
+
self._verify_scopes(request, payload)
|
|
38
|
+
|
|
39
|
+
return user
|
|
40
|
+
|
|
41
|
+
def _get_user(self, username: str, payload: dict):
|
|
42
|
+
"""
|
|
43
|
+
Get or create a user based on the JWT payload.
|
|
44
|
+
If the user exists, update their details.
|
|
45
|
+
"""
|
|
46
|
+
user_model = get_user_model()
|
|
47
|
+
user_data = {
|
|
48
|
+
"first_name": payload.get("given_name") or "",
|
|
49
|
+
"last_name": payload.get("family_name") or "",
|
|
50
|
+
"email": payload.get("email") or "",
|
|
51
|
+
"is_staff": payload.get("is_staff", False),
|
|
52
|
+
}
|
|
53
|
+
if hasattr(user_model, "is_demo"):
|
|
54
|
+
user_data["is_demo"] = payload.get("is_demo", False)
|
|
55
|
+
|
|
56
|
+
user = user_model.objects.filter(username=username).first()
|
|
57
|
+
if user:
|
|
58
|
+
update_needed = False
|
|
59
|
+
|
|
60
|
+
for field, value in user_data.items():
|
|
61
|
+
if getattr(user, field) != value:
|
|
62
|
+
setattr(user, field, value)
|
|
63
|
+
update_needed = True
|
|
64
|
+
|
|
65
|
+
if update_needed:
|
|
66
|
+
user.save(update_fields=list(user_data.keys()))
|
|
67
|
+
|
|
68
|
+
return user
|
|
69
|
+
else:
|
|
70
|
+
return user_model.objects.create(
|
|
71
|
+
username=username,
|
|
72
|
+
**user_data,
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
def _verify_scopes(self, request, token_payload):
|
|
76
|
+
allowed_scopes = self._get_view_allowed_scopes(request)
|
|
77
|
+
|
|
78
|
+
if allowed_scopes == "*":
|
|
79
|
+
return
|
|
80
|
+
|
|
81
|
+
token_scopes_str = token_payload.get("scope", "")
|
|
82
|
+
token_scopes = set(token_scopes_str.split())
|
|
83
|
+
|
|
84
|
+
for scope in allowed_scopes:
|
|
85
|
+
if f"{getattr(settings, 'JWT_SCOPE_PREFIX', '')}:{scope}" in token_scopes:
|
|
86
|
+
return
|
|
87
|
+
|
|
88
|
+
raise HttpError(403, "You are not allowed to access this resource.")
|
|
89
|
+
|
|
90
|
+
def _get_view_allowed_scopes(self, request):
|
|
91
|
+
view_function = self._get_view_function(request)
|
|
92
|
+
scopes = getattr(view_function, "_allowed_scopes", None)
|
|
93
|
+
|
|
94
|
+
if scopes is None:
|
|
95
|
+
raise Exception(
|
|
96
|
+
f"No allowed_scopes defined on the view {view_function.__name__}."
|
|
97
|
+
"Add the decorator @allowed_scopes([...]) or @allowed_scopes('*') to the view."
|
|
98
|
+
)
|
|
99
|
+
|
|
100
|
+
return scopes
|
|
101
|
+
|
|
102
|
+
def _get_view_function(self, request):
|
|
103
|
+
view_func = request.resolver_match.func.__self__
|
|
104
|
+
method = request.method.upper()
|
|
105
|
+
for operation in view_func.operations:
|
|
106
|
+
if operation.methods and method in operation.methods:
|
|
107
|
+
return operation.view_func
|
|
108
|
+
|
|
109
|
+
raise Exception(
|
|
110
|
+
f"Could not determine the view function for {request.method} {request.path}."
|
|
111
|
+
)
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def allowed_scopes(scopes: list[str] | Literal["*"]):
|
|
115
|
+
"""
|
|
116
|
+
A decorator that attaches a list of required scopes to a view function
|
|
117
|
+
in the attribute `_allowed_scopes`.
|
|
118
|
+
This is used by a global authenticator to perform authorization checks.
|
|
119
|
+
"""
|
|
120
|
+
|
|
121
|
+
def decorator(view_func):
|
|
122
|
+
if not isinstance(scopes, list) and scopes != "*":
|
|
123
|
+
raise ValueError("scopes must be a list of strings or '*'")
|
|
124
|
+
|
|
125
|
+
setattr(view_func, "_allowed_scopes", scopes)
|
|
126
|
+
return view_func
|
|
127
|
+
|
|
128
|
+
return decorator
|
|
@@ -64,25 +64,6 @@ def record_to_dict(record: Model_T, exclude: List = None) -> Dict:
|
|
|
64
64
|
return initial_data
|
|
65
65
|
|
|
66
66
|
|
|
67
|
-
def get_choices_list_and_dict(choices) -> Tuple[List, Dict]:
|
|
68
|
-
"""
|
|
69
|
-
From a Choices object return a list and dict of these choices.
|
|
70
|
-
Args:
|
|
71
|
-
choices: Choices object to get the representations from
|
|
72
|
-
Returns:
|
|
73
|
-
tuple of list with human_value and dict-> {human_value: value}
|
|
74
|
-
Examples:
|
|
75
|
-
>>> from model_utils import Choices
|
|
76
|
-
>>> get_choices_list_and_dict(Choices((1, 'hello', 'HELLO')))
|
|
77
|
-
([('HELLO', 'HELLO')], {'HELLO': 1})
|
|
78
|
-
"""
|
|
79
|
-
import_optional_dependency('model_utils', dependency='django-model-utils')
|
|
80
|
-
|
|
81
|
-
choices_list = [(human_value, human_value) for _, human_value in choices]
|
|
82
|
-
choices_dict = {human_value: value for value, human_value in choices}
|
|
83
|
-
return choices_list, choices_dict
|
|
84
|
-
|
|
85
|
-
|
|
86
67
|
def perform_query(
|
|
87
68
|
sql_query: str,
|
|
88
69
|
params: Optional[List] = None,
|
|
@@ -236,25 +217,6 @@ def update_record(record: Model_T, save=True, **data) -> Model_T:
|
|
|
236
217
|
return record
|
|
237
218
|
|
|
238
219
|
|
|
239
|
-
def get_choice_value(choices, human_string: str) -> Optional[Union[int, str]]:
|
|
240
|
-
"""
|
|
241
|
-
Get database representation of a choice for a human-readable value
|
|
242
|
-
|
|
243
|
-
Args:
|
|
244
|
-
choices: model_utils Choice object to get the db value from
|
|
245
|
-
human_string: string representing the human-readable value
|
|
246
|
-
Returns:
|
|
247
|
-
db representation of choice field
|
|
248
|
-
"""
|
|
249
|
-
|
|
250
|
-
import_optional_dependency('model_utils', dependency='django-model-utils')
|
|
251
|
-
|
|
252
|
-
for value, representation in choices:
|
|
253
|
-
if representation == human_string:
|
|
254
|
-
return value
|
|
255
|
-
return None
|
|
256
|
-
|
|
257
|
-
|
|
258
220
|
def get_or_none(records: models.QuerySet, *args, **kwargs) -> Optional[Model_T]:
|
|
259
221
|
"""
|
|
260
222
|
Wrapper around queryset.get to return None if no record is found
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
import hashlib
|
|
2
2
|
import re
|
|
3
|
-
from decimal import Decimal
|
|
4
3
|
from typing import Optional, List, Any
|
|
5
4
|
|
|
6
5
|
from python_utils.types_hinting import NumberIFD
|
|
@@ -19,6 +18,7 @@ def format_percent(value: Optional[NumberIFD]):
|
|
|
19
18
|
'1300%'
|
|
20
19
|
>>> format_percent(0.13)
|
|
21
20
|
'13.0%'
|
|
21
|
+
>>> from decimal import Decimal
|
|
22
22
|
>>> format_percent(Decimal('0.123456'))
|
|
23
23
|
'12.35%'
|
|
24
24
|
"""
|
|
@@ -37,6 +37,7 @@ def format_currency(value: Optional[NumberIFD], symbol: str) -> Optional[str]:
|
|
|
37
37
|
Examples:
|
|
38
38
|
>>> format_currency(120.1234, 'EUR')
|
|
39
39
|
'EUR120.12'
|
|
40
|
+
>>> from decimal import Decimal
|
|
40
41
|
>>> format_currency(Decimal('120.1234'), 'EUR')
|
|
41
42
|
'EUR120.12'
|
|
42
43
|
>>> format_currency(None, 'EUR') is None
|
|
@@ -63,7 +64,7 @@ def human_string(text: str) -> str:
|
|
|
63
64
|
"""
|
|
64
65
|
if not text:
|
|
65
66
|
return ""
|
|
66
|
-
return
|
|
67
|
+
return " ".join(word.title() for word in text.split("_"))
|
|
67
68
|
|
|
68
69
|
|
|
69
70
|
camel_case_pattern = re.compile(r"(?<!^)(?=[A-Z])")
|
|
@@ -141,5 +142,5 @@ def create_hash(data: List[Any]) -> str:
|
|
|
141
142
|
>>> create_hash(['test', 'hash', 1])
|
|
142
143
|
'5ca1b365312e58a36bb985fc770a490b'
|
|
143
144
|
"""
|
|
144
|
-
merged_data =
|
|
145
|
-
return hashlib.md5(merged_data.encode(
|
|
145
|
+
merged_data = "-".join([str(rec) for rec in data])
|
|
146
|
+
return hashlib.md5(merged_data.encode("utf-8")).hexdigest()
|
|
@@ -1,143 +0,0 @@
|
|
|
1
|
-
from datetime import date, datetime
|
|
2
|
-
from typing import List, Dict, Union, Literal
|
|
3
|
-
|
|
4
|
-
import pandas as pd
|
|
5
|
-
|
|
6
|
-
from python_utils.time import get_date, last_day_of_month
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
def unique_not_null_values(df: pd.DataFrame, column: str) -> List:
|
|
10
|
-
"""
|
|
11
|
-
Get a list with unique values from the values of a dataframe column
|
|
12
|
-
1. Filter the df to keep rows with notnull values in the given column
|
|
13
|
-
2. Return a list with all the values of the column appearing only once
|
|
14
|
-
|
|
15
|
-
Args:
|
|
16
|
-
df: dataframe object we are getting the unique values from
|
|
17
|
-
column: the column name on which we are looking for the unique values
|
|
18
|
-
Returns:
|
|
19
|
-
list with all unique values
|
|
20
|
-
Examples:
|
|
21
|
-
>>> test_df = pd.DataFrame({'col1': [1, None, 1, 3, 2], 'col2': [3, 7, 4, 1, 3]})
|
|
22
|
-
>>> unique_not_null_values(test_df, 'col1')
|
|
23
|
-
[1.0, 3.0, 2.0]
|
|
24
|
-
"""
|
|
25
|
-
return df[df[column].notnull()][column].unique().tolist()
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
def rename_and_replace_column_information(df: pd.DataFrame, data: Dict) -> pd.DataFrame:
|
|
29
|
-
"""
|
|
30
|
-
We use this mostly when we serialize the data from postgres.
|
|
31
|
-
It replaces the column values with a dictionary and also renames the column names.
|
|
32
|
-
Performs a rename of the columns
|
|
33
|
-
|
|
34
|
-
Args:
|
|
35
|
-
df: Pandas Data Frame
|
|
36
|
-
data: data that maps columns and replacement/mapping as in example:
|
|
37
|
-
{
|
|
38
|
-
'column_in_pandas': {
|
|
39
|
-
'replace_to': 'new_column_name',
|
|
40
|
-
'map': {
|
|
41
|
-
1: 2,
|
|
42
|
-
2: 3
|
|
43
|
-
}
|
|
44
|
-
}
|
|
45
|
-
Returns:
|
|
46
|
-
New Modified Pandas Dataframe.
|
|
47
|
-
Examples:
|
|
48
|
-
>>> test_df = pd.DataFrame({'col1': ['a', 'b'], 'col2': [1, 2]})
|
|
49
|
-
>>> rename_and_replace_column_information(test_df, {'col1': {'replace_to': 'new', 'map': {'a': 1}}})
|
|
50
|
-
new col2
|
|
51
|
-
0 1 1
|
|
52
|
-
1 b 2
|
|
53
|
-
>>> test_df = pd.DataFrame({'col1': ['a', 'b'], 'col2': [1, 2]})
|
|
54
|
-
>>> rename_and_replace_column_information(test_df, {})
|
|
55
|
-
col1 col2
|
|
56
|
-
0 a 1
|
|
57
|
-
1 b 2
|
|
58
|
-
"""
|
|
59
|
-
map_data = {key: value.get('map') for key, value in data.items()}
|
|
60
|
-
if map_data:
|
|
61
|
-
df.replace(map_data, inplace=True)
|
|
62
|
-
|
|
63
|
-
rename_columns = {key: value.get('replace_to') for key, value in data.items()}
|
|
64
|
-
if rename_columns:
|
|
65
|
-
df.rename(columns=rename_columns, inplace=True)
|
|
66
|
-
return df
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
def get_dates_between(
|
|
70
|
-
start_date: Union[date, datetime, str],
|
|
71
|
-
end_date: Union[date, datetime, str],
|
|
72
|
-
inclusive: Literal["both", "neither", "left", "right"] = "both"
|
|
73
|
-
) -> List[date]:
|
|
74
|
-
"""
|
|
75
|
-
1. Give start and end dates.
|
|
76
|
-
2. Set inclusive if you want, by default the value is both
|
|
77
|
-
3. Return list of dates
|
|
78
|
-
Args:
|
|
79
|
-
start_date (date, datetime, str): The left bound for the date generation by pandas
|
|
80
|
-
end_date (date, datetime, str): The right bound for the date generation by pandas
|
|
81
|
-
inclusive: Include boundaries; Whether to set each bound as closed or open
|
|
82
|
-
Returns:
|
|
83
|
-
list of date obj representing the dates in between the start and end
|
|
84
|
-
Raises:
|
|
85
|
-
ValueError: if one of passed dates is str and not well formatted to be converted to date obj
|
|
86
|
-
Examples:
|
|
87
|
-
>>> get_dates_between(date(2021, 2, 1), date(2021, 2, 3))
|
|
88
|
-
[datetime.date(2021, 2, 1), datetime.date(2021, 2, 2), datetime.date(2021, 2, 3)]
|
|
89
|
-
>>> get_dates_between(datetime(2021, 2, 1, 0, 0), datetime(2021, 2, 3, 0, 0))
|
|
90
|
-
[datetime.date(2021, 2, 1), datetime.date(2021, 2, 2), datetime.date(2021, 2, 3)]
|
|
91
|
-
>>> get_dates_between("2021-2-1", "2021-2-3")
|
|
92
|
-
[datetime.date(2021, 2, 1), datetime.date(2021, 2, 2), datetime.date(2021, 2, 3)]
|
|
93
|
-
>>> get_dates_between(date(2021, 2, 1), date(2021, 2, 3), inclusive="right")
|
|
94
|
-
[datetime.date(2021, 2, 2), datetime.date(2021, 2, 3)]
|
|
95
|
-
>>> get_dates_between("2021-2-1-1-1", "2021-3-1-1-1-1")
|
|
96
|
-
Traceback (most recent call last):
|
|
97
|
-
...
|
|
98
|
-
ValueError: Invalid date 2021-2-1-1-1
|
|
99
|
-
"""
|
|
100
|
-
start_date = get_date(start_date, raise_error=True)
|
|
101
|
-
end_date = get_date(end_date, raise_error=True)
|
|
102
|
-
dates_range = pd.date_range(start=start_date, end=end_date, inclusive=inclusive).tolist()
|
|
103
|
-
return [date(d.year, d.month, d.day) for d in dates_range]
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
def get_months_between_dates(
|
|
107
|
-
start_date: Union[date, datetime, str],
|
|
108
|
-
end_date: Union[date, datetime, str],
|
|
109
|
-
inclusive: Literal["both", "neither", "left", "right"] = "both"
|
|
110
|
-
) -> List[date]:
|
|
111
|
-
"""
|
|
112
|
-
Get a timestamp for each month between the two dates.
|
|
113
|
-
The start_date is appended because the pandas list begins from the next month
|
|
114
|
-
|
|
115
|
-
Args:
|
|
116
|
-
start_date (date, datetime, str): The left bound for the date generation by pandas
|
|
117
|
-
end_date (date, datetime, str): The right bound for the date generation by pandas
|
|
118
|
-
inclusive: Include boundaries; Whether to set each bound as closed or open
|
|
119
|
-
Returns:
|
|
120
|
-
list of date obj representing the end date of each month in between
|
|
121
|
-
Raises:
|
|
122
|
-
ValueError: if one of passed dates is str and not well formatted to be converted to date obj
|
|
123
|
-
Examples:
|
|
124
|
-
>>> get_months_between_dates(date(2021, 2, 1), date(2021, 3, 1))
|
|
125
|
-
[datetime.date(2021, 2, 28), datetime.date(2021, 3, 31)]
|
|
126
|
-
>>> get_months_between_dates(datetime(2021, 2, 1, 0, 0), datetime(2021, 3, 1, 1, 1))
|
|
127
|
-
[datetime.date(2021, 2, 28), datetime.date(2021, 3, 31)]
|
|
128
|
-
>>> get_months_between_dates("2021-2-1", "2021-3-1")
|
|
129
|
-
[datetime.date(2021, 2, 28), datetime.date(2021, 3, 31)]
|
|
130
|
-
>>> get_months_between_dates(date(2021, 2, 1), date(2021, 3, 1), inclusive="right")
|
|
131
|
-
[datetime.date(2021, 3, 31)]
|
|
132
|
-
>>> get_months_between_dates("2021-2-1-1-1", "2021-3-1-1-1-1")
|
|
133
|
-
Traceback (most recent call last):
|
|
134
|
-
...
|
|
135
|
-
ValueError: Invalid date 2021-2-1-1-1
|
|
136
|
-
"""
|
|
137
|
-
start_date = get_date(start_date, raise_error=True)
|
|
138
|
-
end_date = get_date(end_date, raise_error=True)
|
|
139
|
-
dates_range = pd.date_range(start_date, end_date, freq="MS", inclusive=inclusive).to_list()
|
|
140
|
-
return [
|
|
141
|
-
last_day_of_month(year=cur_month.year, month=cur_month.month)
|
|
142
|
-
for cur_month in dates_range
|
|
143
|
-
]
|
|
@@ -1,37 +0,0 @@
|
|
|
1
|
-
from urllib.parse import urlencode
|
|
2
|
-
|
|
3
|
-
from rest_framework.response import Response
|
|
4
|
-
from rest_framework.status import HTTP_400_BAD_REQUEST, HTTP_401_UNAUTHORIZED, HTTP_403_FORBIDDEN, \
|
|
5
|
-
HTTP_502_BAD_GATEWAY
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
def bad_request(message="Bad request."): # pragma no cover
|
|
9
|
-
return Response(data={"message": message}, status=HTTP_400_BAD_REQUEST)
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
def unauthorized(message="Unauthorized."): # pragma no cover
|
|
13
|
-
return Response(data={"message": message}, status=HTTP_401_UNAUTHORIZED)
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
def forbidden(message="Forbidden."): # pragma no cover
|
|
17
|
-
return Response(data={"message": message}, status=HTTP_403_FORBIDDEN)
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
def bad_gateway(message="Bad Gateway."): # pragma no cover
|
|
21
|
-
return Response(data={"message": message}, status=HTTP_502_BAD_GATEWAY)
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
def add_params_to_url(url: str, **kwargs) -> str:
|
|
25
|
-
"""
|
|
26
|
-
Join the url and params ready for making requests
|
|
27
|
-
|
|
28
|
-
Args:
|
|
29
|
-
url: string representing the url to be called
|
|
30
|
-
**kwargs: all the params to join the url
|
|
31
|
-
Returns:
|
|
32
|
-
Final url ready for request
|
|
33
|
-
Examples:
|
|
34
|
-
>>> add_params_to_url('https://test.com', p1='20', p2=30, p3=None, p4='test', p5='<html/>')
|
|
35
|
-
'https://test.com?p1=20&p2=30&p3=None&p4=test&p5=%3Chtml%2F%3E'
|
|
36
|
-
"""
|
|
37
|
-
return url if not kwargs else f"{url}?{urlencode(kwargs)}"
|
|
@@ -1,44 +0,0 @@
|
|
|
1
|
-
[metadata]
|
|
2
|
-
name = cardo-python-utils
|
|
3
|
-
version = 0.4.2
|
|
4
|
-
description = Python library enhanced with a wide range of functions for different scenarios.
|
|
5
|
-
long_description = file: README.rst
|
|
6
|
-
url = https://github.com/CardoAI/cardo-python-utils
|
|
7
|
-
author = Kristi Kotini
|
|
8
|
-
author_email = hello@cardoai.com
|
|
9
|
-
license = MIT (X11)
|
|
10
|
-
classifiers =
|
|
11
|
-
Environment :: Web Environment
|
|
12
|
-
Framework :: Django
|
|
13
|
-
Intended Audience :: Developers
|
|
14
|
-
License :: OSI Approved :: BSD License
|
|
15
|
-
Operating System :: OS Independent
|
|
16
|
-
Programming Language :: Python
|
|
17
|
-
Programming Language :: Python :: 3
|
|
18
|
-
Programming Language :: Python :: 3 :: Only
|
|
19
|
-
Programming Language :: Python :: 3.8
|
|
20
|
-
Programming Language :: Python :: 3.9
|
|
21
|
-
Programming Language :: Python :: 3.10
|
|
22
|
-
Topic :: Internet :: WWW/HTTP
|
|
23
|
-
Topic :: Internet :: WWW/HTTP :: Dynamic Content
|
|
24
|
-
|
|
25
|
-
[options]
|
|
26
|
-
include_package_data = true
|
|
27
|
-
packages = find:
|
|
28
|
-
python_requires = >=3.8
|
|
29
|
-
|
|
30
|
-
[options.extras_require]
|
|
31
|
-
pandas = pandas>=1.4.0
|
|
32
|
-
django = Django; django-model-utils==4.2.0
|
|
33
|
-
rest = djangorestframework; requests
|
|
34
|
-
all =
|
|
35
|
-
Django
|
|
36
|
-
pandas>=1.4.0
|
|
37
|
-
django-model-utils>=4.2.0
|
|
38
|
-
djangorestframework
|
|
39
|
-
requests
|
|
40
|
-
|
|
41
|
-
[egg_info]
|
|
42
|
-
tag_build =
|
|
43
|
-
tag_date = 0
|
|
44
|
-
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|