ouro-py 0.0.1__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.
- ouro_py-0.0.1/.gitignore +160 -0
- ouro_py-0.0.1/PKG-INFO +122 -0
- ouro_py-0.0.1/README.md +104 -0
- ouro_py-0.0.1/ouro/__init__.py +89 -0
- ouro_py-0.0.1/ouro/air/__init__.py +182 -0
- ouro_py-0.0.1/ouro/earth/__init__.py +161 -0
- ouro_py-0.0.1/ouro/ouro.py +0 -0
- ouro_py-0.0.1/ouro/utils/__init__.py +37 -0
- ouro_py-0.0.1/pyproject.toml +32 -0
ouro_py-0.0.1/.gitignore
ADDED
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
# Byte-compiled / optimized / DLL files
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*$py.class
|
|
5
|
+
|
|
6
|
+
# C extensions
|
|
7
|
+
*.so
|
|
8
|
+
|
|
9
|
+
# Distribution / packaging
|
|
10
|
+
.Python
|
|
11
|
+
build/
|
|
12
|
+
develop-eggs/
|
|
13
|
+
dist/
|
|
14
|
+
downloads/
|
|
15
|
+
eggs/
|
|
16
|
+
.eggs/
|
|
17
|
+
lib/
|
|
18
|
+
lib64/
|
|
19
|
+
parts/
|
|
20
|
+
sdist/
|
|
21
|
+
var/
|
|
22
|
+
wheels/
|
|
23
|
+
share/python-wheels/
|
|
24
|
+
*.egg-info/
|
|
25
|
+
.installed.cfg
|
|
26
|
+
*.egg
|
|
27
|
+
MANIFEST
|
|
28
|
+
|
|
29
|
+
# PyInstaller
|
|
30
|
+
# Usually these files are written by a python script from a template
|
|
31
|
+
# before PyInstaller builds the exe, so as to inject date/other infos into it.
|
|
32
|
+
*.manifest
|
|
33
|
+
*.spec
|
|
34
|
+
|
|
35
|
+
# Installer logs
|
|
36
|
+
pip-log.txt
|
|
37
|
+
pip-delete-this-directory.txt
|
|
38
|
+
|
|
39
|
+
# Unit test / coverage reports
|
|
40
|
+
htmlcov/
|
|
41
|
+
.tox/
|
|
42
|
+
.nox/
|
|
43
|
+
.coverage
|
|
44
|
+
.coverage.*
|
|
45
|
+
.cache
|
|
46
|
+
nosetests.xml
|
|
47
|
+
coverage.xml
|
|
48
|
+
*.cover
|
|
49
|
+
*.py,cover
|
|
50
|
+
.hypothesis/
|
|
51
|
+
.pytest_cache/
|
|
52
|
+
cover/
|
|
53
|
+
|
|
54
|
+
# Translations
|
|
55
|
+
*.mo
|
|
56
|
+
*.pot
|
|
57
|
+
|
|
58
|
+
# Django stuff:
|
|
59
|
+
*.log
|
|
60
|
+
local_settings.py
|
|
61
|
+
db.sqlite3
|
|
62
|
+
db.sqlite3-journal
|
|
63
|
+
|
|
64
|
+
# Flask stuff:
|
|
65
|
+
instance/
|
|
66
|
+
.webassets-cache
|
|
67
|
+
|
|
68
|
+
# Scrapy stuff:
|
|
69
|
+
.scrapy
|
|
70
|
+
|
|
71
|
+
# Sphinx documentation
|
|
72
|
+
docs/_build/
|
|
73
|
+
|
|
74
|
+
# PyBuilder
|
|
75
|
+
.pybuilder/
|
|
76
|
+
target/
|
|
77
|
+
|
|
78
|
+
# Jupyter Notebook
|
|
79
|
+
.ipynb_checkpoints
|
|
80
|
+
|
|
81
|
+
# IPython
|
|
82
|
+
profile_default/
|
|
83
|
+
ipython_config.py
|
|
84
|
+
|
|
85
|
+
# pyenv
|
|
86
|
+
# For a library or package, you might want to ignore these files since the code is
|
|
87
|
+
# intended to run in multiple environments; otherwise, check them in:
|
|
88
|
+
# .python-version
|
|
89
|
+
|
|
90
|
+
# pipenv
|
|
91
|
+
# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
|
|
92
|
+
# However, in case of collaboration, if having platform-specific dependencies or dependencies
|
|
93
|
+
# having no cross-platform support, pipenv may install dependencies that don't work, or not
|
|
94
|
+
# install all needed dependencies.
|
|
95
|
+
#Pipfile.lock
|
|
96
|
+
|
|
97
|
+
# poetry
|
|
98
|
+
# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control.
|
|
99
|
+
# This is especially recommended for binary packages to ensure reproducibility, and is more
|
|
100
|
+
# commonly ignored for libraries.
|
|
101
|
+
# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control
|
|
102
|
+
#poetry.lock
|
|
103
|
+
|
|
104
|
+
# pdm
|
|
105
|
+
# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control.
|
|
106
|
+
#pdm.lock
|
|
107
|
+
# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it
|
|
108
|
+
# in version control.
|
|
109
|
+
# https://pdm.fming.dev/#use-with-ide
|
|
110
|
+
.pdm.toml
|
|
111
|
+
|
|
112
|
+
# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm
|
|
113
|
+
__pypackages__/
|
|
114
|
+
|
|
115
|
+
# Celery stuff
|
|
116
|
+
celerybeat-schedule
|
|
117
|
+
celerybeat.pid
|
|
118
|
+
|
|
119
|
+
# SageMath parsed files
|
|
120
|
+
*.sage.py
|
|
121
|
+
|
|
122
|
+
# Environments
|
|
123
|
+
.env
|
|
124
|
+
.venv
|
|
125
|
+
env/
|
|
126
|
+
venv/
|
|
127
|
+
ENV/
|
|
128
|
+
env.bak/
|
|
129
|
+
venv.bak/
|
|
130
|
+
|
|
131
|
+
# Spyder project settings
|
|
132
|
+
.spyderproject
|
|
133
|
+
.spyproject
|
|
134
|
+
|
|
135
|
+
# Rope project settings
|
|
136
|
+
.ropeproject
|
|
137
|
+
|
|
138
|
+
# mkdocs documentation
|
|
139
|
+
/site
|
|
140
|
+
|
|
141
|
+
# mypy
|
|
142
|
+
.mypy_cache/
|
|
143
|
+
.dmypy.json
|
|
144
|
+
dmypy.json
|
|
145
|
+
|
|
146
|
+
# Pyre type checker
|
|
147
|
+
.pyre/
|
|
148
|
+
|
|
149
|
+
# pytype static type analyzer
|
|
150
|
+
.pytype/
|
|
151
|
+
|
|
152
|
+
# Cython debug symbols
|
|
153
|
+
cython_debug/
|
|
154
|
+
|
|
155
|
+
# PyCharm
|
|
156
|
+
# JetBrains specific template is maintained in a separate JetBrains.gitignore that can
|
|
157
|
+
# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore
|
|
158
|
+
# and can be added to the global gitignore or merged into this file. For a more nuclear
|
|
159
|
+
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
|
|
160
|
+
#.idea/
|
ouro_py-0.0.1/PKG-INFO
ADDED
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: ouro-py
|
|
3
|
+
Version: 0.0.1
|
|
4
|
+
Summary: Python wrapper for the Ouro API
|
|
5
|
+
Project-URL: Homepage, https://github.com/ourofoundation/ouro-py
|
|
6
|
+
Project-URL: PyPI, https://pypi.org/project/ouro-py
|
|
7
|
+
Author-email: Matt Moderwell <matt@ouro.foundation>
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
Keywords: ouro
|
|
10
|
+
Requires-Python: >=3.7
|
|
11
|
+
Requires-Dist: httpx
|
|
12
|
+
Requires-Dist: numpy
|
|
13
|
+
Requires-Dist: pandas
|
|
14
|
+
Requires-Dist: postgrest
|
|
15
|
+
Requires-Dist: python-dotenv
|
|
16
|
+
Requires-Dist: supabase
|
|
17
|
+
Description-Content-Type: text/markdown
|
|
18
|
+
|
|
19
|
+
# `ouro-py`
|
|
20
|
+
|
|
21
|
+
Python client for [Ouro](https://ouro.foundation)
|
|
22
|
+
|
|
23
|
+
- Documentation: [ouro.foundation/docs](https://ouro.foundation/docs)
|
|
24
|
+
|
|
25
|
+
## Usage
|
|
26
|
+
|
|
27
|
+
Generate an API key from your account settings by going to [ouro.foundation/app/settings/api-keys](https://ouro.foundation/app/settings/api-keys).
|
|
28
|
+
|
|
29
|
+
Set your Ouro environment variables in a dotenv file, or using the shell:
|
|
30
|
+
|
|
31
|
+
```bash
|
|
32
|
+
export USER_API_KEY="your_ouro_api_key"
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
Init client:
|
|
36
|
+
|
|
37
|
+
```python
|
|
38
|
+
import os
|
|
39
|
+
from ouro import Ouro
|
|
40
|
+
|
|
41
|
+
api_key = os.environ.get("USER_API_KEY")
|
|
42
|
+
ouro = Ouro()
|
|
43
|
+
ouro.login(api_key)
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
Use the client to interface with the Ouro framework.
|
|
47
|
+
|
|
48
|
+
### Create a dataset
|
|
49
|
+
|
|
50
|
+
```python
|
|
51
|
+
data = pd.DataFrame([
|
|
52
|
+
{"name": "Bob", "age": 30},
|
|
53
|
+
{"name": "Alice", "age": 27},
|
|
54
|
+
{"name": "Matt", "age": 26},
|
|
55
|
+
|
|
56
|
+
])
|
|
57
|
+
|
|
58
|
+
dataset = ouro.earth.create_dataset({
|
|
59
|
+
"name": "unique_dataset_name",
|
|
60
|
+
"visibility": "private",
|
|
61
|
+
},
|
|
62
|
+
data
|
|
63
|
+
)
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Contributing
|
|
67
|
+
|
|
68
|
+
Contributing to the Python library is a great way to get involved with the Ouro community. Reach out to us on our [Github Discussions](https://github.com/orgs/ourofoundation/discussions) page if you want to get involved.
|
|
69
|
+
|
|
70
|
+
## Set up a Local Development Environment
|
|
71
|
+
|
|
72
|
+
### Clone the Repository
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
git clone git@github.com:ourofoundation/ouro-py.git
|
|
76
|
+
cd ouro-py
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Create and Activate a Virtual Environment
|
|
80
|
+
|
|
81
|
+
We recommend activating your virtual environment. Click [here](https://docs.python.org/3/library/venv.html) for more about Python virtual environments and working with [conda](https://conda.io/projects/conda/en/latest/user-guide/tasks/manage-environments.html#activating-an-environment) and [poetry](https://python-poetry.org/docs/basic-usage/).
|
|
82
|
+
|
|
83
|
+
Using venv (Python 3 built-in):
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
python3 -m venv env
|
|
87
|
+
source env/bin/activate # On Windows, use .\env\Scripts\activate
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Using conda:
|
|
91
|
+
|
|
92
|
+
```bash
|
|
93
|
+
conda create --name ouro-py
|
|
94
|
+
conda activate ouro-py
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
### PyPi installation
|
|
98
|
+
|
|
99
|
+
Install the package (for > Python 3.7):
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
# with pip
|
|
103
|
+
pip install ouro
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### Local installation
|
|
107
|
+
|
|
108
|
+
You can also install locally after cloning this repo. Install Development mode with `pip install -e`, which makes it so when you edit the source code the changes will be reflected in your python module.
|
|
109
|
+
|
|
110
|
+
## Badges
|
|
111
|
+
|
|
112
|
+
[](https://opensource.org/licenses/MIT)
|
|
113
|
+
[](https://github.com/ourofoundation/ouro-py/actions/workflows/ci.yml)
|
|
114
|
+
[](https://pypi.org/project/ouro)
|
|
115
|
+
[](https://pypi.org/project/ouro)
|
|
116
|
+
[](https://codecov.io/gh/ourofoundation/ouro-py)
|
|
117
|
+
[](https://github.com/ourofoundation/ouro-py/commits)
|
|
118
|
+
[](https://github.com/ourofoundation/ouro-py/commits)
|
|
119
|
+
[](https://github.com/ourofoundation/ouro-py/stargazers)
|
|
120
|
+
[](https://github.com/ourofoundation/ouro-py/network/members)
|
|
121
|
+
[](https://github.com/ourofoundation/ouro-py)
|
|
122
|
+
[](https://github.com/ourofoundation/ouro-py/graphs/contributors)
|
ouro_py-0.0.1/README.md
ADDED
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# `ouro-py`
|
|
2
|
+
|
|
3
|
+
Python client for [Ouro](https://ouro.foundation)
|
|
4
|
+
|
|
5
|
+
- Documentation: [ouro.foundation/docs](https://ouro.foundation/docs)
|
|
6
|
+
|
|
7
|
+
## Usage
|
|
8
|
+
|
|
9
|
+
Generate an API key from your account settings by going to [ouro.foundation/app/settings/api-keys](https://ouro.foundation/app/settings/api-keys).
|
|
10
|
+
|
|
11
|
+
Set your Ouro environment variables in a dotenv file, or using the shell:
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
export USER_API_KEY="your_ouro_api_key"
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
Init client:
|
|
18
|
+
|
|
19
|
+
```python
|
|
20
|
+
import os
|
|
21
|
+
from ouro import Ouro
|
|
22
|
+
|
|
23
|
+
api_key = os.environ.get("USER_API_KEY")
|
|
24
|
+
ouro = Ouro()
|
|
25
|
+
ouro.login(api_key)
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
Use the client to interface with the Ouro framework.
|
|
29
|
+
|
|
30
|
+
### Create a dataset
|
|
31
|
+
|
|
32
|
+
```python
|
|
33
|
+
data = pd.DataFrame([
|
|
34
|
+
{"name": "Bob", "age": 30},
|
|
35
|
+
{"name": "Alice", "age": 27},
|
|
36
|
+
{"name": "Matt", "age": 26},
|
|
37
|
+
|
|
38
|
+
])
|
|
39
|
+
|
|
40
|
+
dataset = ouro.earth.create_dataset({
|
|
41
|
+
"name": "unique_dataset_name",
|
|
42
|
+
"visibility": "private",
|
|
43
|
+
},
|
|
44
|
+
data
|
|
45
|
+
)
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Contributing
|
|
49
|
+
|
|
50
|
+
Contributing to the Python library is a great way to get involved with the Ouro community. Reach out to us on our [Github Discussions](https://github.com/orgs/ourofoundation/discussions) page if you want to get involved.
|
|
51
|
+
|
|
52
|
+
## Set up a Local Development Environment
|
|
53
|
+
|
|
54
|
+
### Clone the Repository
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
git clone git@github.com:ourofoundation/ouro-py.git
|
|
58
|
+
cd ouro-py
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### Create and Activate a Virtual Environment
|
|
62
|
+
|
|
63
|
+
We recommend activating your virtual environment. Click [here](https://docs.python.org/3/library/venv.html) for more about Python virtual environments and working with [conda](https://conda.io/projects/conda/en/latest/user-guide/tasks/manage-environments.html#activating-an-environment) and [poetry](https://python-poetry.org/docs/basic-usage/).
|
|
64
|
+
|
|
65
|
+
Using venv (Python 3 built-in):
|
|
66
|
+
|
|
67
|
+
```bash
|
|
68
|
+
python3 -m venv env
|
|
69
|
+
source env/bin/activate # On Windows, use .\env\Scripts\activate
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Using conda:
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
conda create --name ouro-py
|
|
76
|
+
conda activate ouro-py
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### PyPi installation
|
|
80
|
+
|
|
81
|
+
Install the package (for > Python 3.7):
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
# with pip
|
|
85
|
+
pip install ouro
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
### Local installation
|
|
89
|
+
|
|
90
|
+
You can also install locally after cloning this repo. Install Development mode with `pip install -e`, which makes it so when you edit the source code the changes will be reflected in your python module.
|
|
91
|
+
|
|
92
|
+
## Badges
|
|
93
|
+
|
|
94
|
+
[](https://opensource.org/licenses/MIT)
|
|
95
|
+
[](https://github.com/ourofoundation/ouro-py/actions/workflows/ci.yml)
|
|
96
|
+
[](https://pypi.org/project/ouro)
|
|
97
|
+
[](https://pypi.org/project/ouro)
|
|
98
|
+
[](https://codecov.io/gh/ourofoundation/ouro-py)
|
|
99
|
+
[](https://github.com/ourofoundation/ouro-py/commits)
|
|
100
|
+
[](https://github.com/ourofoundation/ouro-py/commits)
|
|
101
|
+
[](https://github.com/ourofoundation/ouro-py/stargazers)
|
|
102
|
+
[](https://github.com/ourofoundation/ouro-py/network/members)
|
|
103
|
+
[](https://github.com/ourofoundation/ouro-py)
|
|
104
|
+
[](https://github.com/ourofoundation/ouro-py/graphs/contributors)
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
VERSION = "0.0.1"
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
import logging
|
|
5
|
+
import os
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
import requests
|
|
9
|
+
from dotenv import load_dotenv
|
|
10
|
+
from supabase.client import ClientOptions
|
|
11
|
+
|
|
12
|
+
from supabase import Client, create_client
|
|
13
|
+
from ouro.air import Air
|
|
14
|
+
from ouro.earth import Earth
|
|
15
|
+
|
|
16
|
+
logger = logging.getLogger(__name__)
|
|
17
|
+
logger.setLevel(logging.INFO)
|
|
18
|
+
console_handler = logging.StreamHandler()
|
|
19
|
+
log_format = "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
|
|
20
|
+
formatter = logging.Formatter(log_format)
|
|
21
|
+
console_handler.setFormatter(formatter)
|
|
22
|
+
logger.addHandler(console_handler)
|
|
23
|
+
|
|
24
|
+
postgres_logger = logging.getLogger("httpx")
|
|
25
|
+
postgres_logger.setLevel(logging.WARNING)
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
load_dotenv()
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
class Ouro:
|
|
32
|
+
def __init__(self):
|
|
33
|
+
self.client = None
|
|
34
|
+
self.public_client = None
|
|
35
|
+
self.user = None
|
|
36
|
+
self.token = None
|
|
37
|
+
|
|
38
|
+
self.earth = None
|
|
39
|
+
self.water = None
|
|
40
|
+
self.air = None
|
|
41
|
+
self.fire = None
|
|
42
|
+
|
|
43
|
+
# Class Instances
|
|
44
|
+
# self.MakeAirPost = MakeAirPost
|
|
45
|
+
|
|
46
|
+
def login(self, api_key: str):
|
|
47
|
+
url: str = os.environ.get("SUPABASE_URL")
|
|
48
|
+
key: str = os.environ.get("SUPABASE_ANON_KEY")
|
|
49
|
+
|
|
50
|
+
if not api_key:
|
|
51
|
+
raise Exception("No API key found")
|
|
52
|
+
|
|
53
|
+
# Send a request to Ouro Backend to get an access token
|
|
54
|
+
req = requests.post(
|
|
55
|
+
f"{os.environ.get('OURO_BACKEND_URL')}/users/get-token",
|
|
56
|
+
json={"pat": api_key},
|
|
57
|
+
)
|
|
58
|
+
json = req.json()
|
|
59
|
+
self.token = json["token"]
|
|
60
|
+
self.client: Client = create_client(
|
|
61
|
+
url,
|
|
62
|
+
key,
|
|
63
|
+
options=ClientOptions(
|
|
64
|
+
schema="datasets",
|
|
65
|
+
auto_refresh_token=True,
|
|
66
|
+
persist_session=False,
|
|
67
|
+
),
|
|
68
|
+
)
|
|
69
|
+
self.public_client: Client = create_client(
|
|
70
|
+
url,
|
|
71
|
+
key,
|
|
72
|
+
options=ClientOptions(
|
|
73
|
+
auto_refresh_token=True,
|
|
74
|
+
persist_session=False,
|
|
75
|
+
),
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
if not self.token:
|
|
79
|
+
raise Exception("No user found for this API key")
|
|
80
|
+
|
|
81
|
+
self.client.postgrest.auth(self.token)
|
|
82
|
+
self.public_client.postgrest.auth(self.token)
|
|
83
|
+
|
|
84
|
+
self.user = self.client.auth.get_user(self.token).user
|
|
85
|
+
print(f"Successfully logged in as {self.user.email}.")
|
|
86
|
+
|
|
87
|
+
# Instanciate classes
|
|
88
|
+
self.earth = Earth(config=self)
|
|
89
|
+
self.air = Air(config=self)
|
|
@@ -0,0 +1,182 @@
|
|
|
1
|
+
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
from supabase import Client
|
|
5
|
+
import time
|
|
6
|
+
import logging
|
|
7
|
+
import json
|
|
8
|
+
import os
|
|
9
|
+
import requests
|
|
10
|
+
import pandas as pd
|
|
11
|
+
|
|
12
|
+
logger = logging.getLogger(__name__)
|
|
13
|
+
logger.setLevel(logging.INFO)
|
|
14
|
+
console_handler = logging.StreamHandler()
|
|
15
|
+
log_format = "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
|
|
16
|
+
formatter = logging.Formatter(log_format)
|
|
17
|
+
console_handler.setFormatter(formatter)
|
|
18
|
+
logger.addHandler(console_handler)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class Post:
|
|
22
|
+
"""Create a new Air post. Formats the data to be viewed with the AirViewer.
|
|
23
|
+
|
|
24
|
+
Inspired by https://github.com/didix21/mdutils
|
|
25
|
+
"""
|
|
26
|
+
|
|
27
|
+
def __init__(self, data: dict = {}, content: dict = {}):
|
|
28
|
+
self.data = data
|
|
29
|
+
self.content = {
|
|
30
|
+
"type": "doc",
|
|
31
|
+
"content": [],
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
def new_header(self, level: int, title: str):
|
|
35
|
+
element = {
|
|
36
|
+
"type": "heading",
|
|
37
|
+
"attrs": {"level": level},
|
|
38
|
+
"content": [{"text": title, "type": "text"}],
|
|
39
|
+
}
|
|
40
|
+
self.content["content"].append(element)
|
|
41
|
+
|
|
42
|
+
def new_paragraph(self, text: str):
|
|
43
|
+
element = {
|
|
44
|
+
"type": "paragraph",
|
|
45
|
+
"content": [{"text": text, "type": "text"}],
|
|
46
|
+
}
|
|
47
|
+
self.content["content"].append(element)
|
|
48
|
+
|
|
49
|
+
def new_line(self):
|
|
50
|
+
element = {
|
|
51
|
+
"type": "paragraph",
|
|
52
|
+
"content": [{"text": "", "type": "text"}],
|
|
53
|
+
}
|
|
54
|
+
self.content["content"].append(element)
|
|
55
|
+
|
|
56
|
+
def new_code_block(self, code: str, language: str = None):
|
|
57
|
+
element = {
|
|
58
|
+
"type": "codeBlock",
|
|
59
|
+
"attrs": {"language": language},
|
|
60
|
+
"content": [{"text": code, "type": "text"}],
|
|
61
|
+
}
|
|
62
|
+
self.content["content"].append(element)
|
|
63
|
+
|
|
64
|
+
def new_table(self, data: pd.DataFrame):
|
|
65
|
+
element = {
|
|
66
|
+
"type": "table",
|
|
67
|
+
"content": [],
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
# Generate the header row
|
|
71
|
+
header_row = {
|
|
72
|
+
"type": "tableRow",
|
|
73
|
+
"content": list(
|
|
74
|
+
map(
|
|
75
|
+
(
|
|
76
|
+
lambda x: {
|
|
77
|
+
"type": "tableHeader",
|
|
78
|
+
"attrs": {"colspan": 1, "rowspan": 1, "colwidth": None},
|
|
79
|
+
"content": [
|
|
80
|
+
{
|
|
81
|
+
"type": "paragraph",
|
|
82
|
+
"content": [{"text": str(x), "type": "text"}],
|
|
83
|
+
}
|
|
84
|
+
],
|
|
85
|
+
}
|
|
86
|
+
),
|
|
87
|
+
data.columns,
|
|
88
|
+
)
|
|
89
|
+
),
|
|
90
|
+
}
|
|
91
|
+
# Generate the rows
|
|
92
|
+
rows = list(
|
|
93
|
+
map(
|
|
94
|
+
(
|
|
95
|
+
lambda x: {
|
|
96
|
+
"type": "tableRow",
|
|
97
|
+
"content": list(
|
|
98
|
+
map(
|
|
99
|
+
(
|
|
100
|
+
lambda y: {
|
|
101
|
+
"type": "tableCell",
|
|
102
|
+
"attrs": {
|
|
103
|
+
"colspan": 1,
|
|
104
|
+
"rowspan": 1,
|
|
105
|
+
"colwidth": None,
|
|
106
|
+
},
|
|
107
|
+
"content": [
|
|
108
|
+
{
|
|
109
|
+
"type": "paragraph",
|
|
110
|
+
"content": [
|
|
111
|
+
{
|
|
112
|
+
"text": str(y),
|
|
113
|
+
"type": "text",
|
|
114
|
+
}
|
|
115
|
+
],
|
|
116
|
+
}
|
|
117
|
+
],
|
|
118
|
+
}
|
|
119
|
+
),
|
|
120
|
+
x[1].values,
|
|
121
|
+
)
|
|
122
|
+
),
|
|
123
|
+
}
|
|
124
|
+
),
|
|
125
|
+
data.iterrows(),
|
|
126
|
+
)
|
|
127
|
+
)
|
|
128
|
+
# Add the header row and rows to the table
|
|
129
|
+
element["content"] = [header_row, *rows]
|
|
130
|
+
|
|
131
|
+
self.content["content"].append(element)
|
|
132
|
+
|
|
133
|
+
def new_inline_image(self, src: str, alt: str):
|
|
134
|
+
element = {
|
|
135
|
+
"type": "image",
|
|
136
|
+
"attrs": {"src": src, "alt": alt},
|
|
137
|
+
}
|
|
138
|
+
self.content["content"].append(element)
|
|
139
|
+
|
|
140
|
+
def new_inline_asset(
|
|
141
|
+
self,
|
|
142
|
+
id: str,
|
|
143
|
+
asset_type: str,
|
|
144
|
+
filters: dict = None,
|
|
145
|
+
view_mode: str = "default",
|
|
146
|
+
):
|
|
147
|
+
element = {
|
|
148
|
+
# "type": "paragraph",
|
|
149
|
+
# "content": [
|
|
150
|
+
# {
|
|
151
|
+
"type": "assetComponent",
|
|
152
|
+
"attrs": {
|
|
153
|
+
"id": id,
|
|
154
|
+
"assetType": asset_type,
|
|
155
|
+
"filters": filters,
|
|
156
|
+
"viewMode": view_mode,
|
|
157
|
+
},
|
|
158
|
+
# }
|
|
159
|
+
# ],
|
|
160
|
+
}
|
|
161
|
+
self.content["content"].append(element)
|
|
162
|
+
|
|
163
|
+
|
|
164
|
+
class Air:
|
|
165
|
+
def __init__(self, config):
|
|
166
|
+
self.config = config
|
|
167
|
+
self.Post = Post
|
|
168
|
+
|
|
169
|
+
def create_post(self, post: Post):
|
|
170
|
+
request = requests.post(f"{os.environ.get('OURO_BACKEND_URL')}/elements/air/create",
|
|
171
|
+
headers={
|
|
172
|
+
"Authorization": f"{self.config.token}",
|
|
173
|
+
"Content-Type": "application/json",
|
|
174
|
+
},
|
|
175
|
+
json={
|
|
176
|
+
"content": {"json": post.content, "text": ""},
|
|
177
|
+
"post": post.data
|
|
178
|
+
},
|
|
179
|
+
)
|
|
180
|
+
request.raise_for_status()
|
|
181
|
+
|
|
182
|
+
return request.json()
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
from supabase import Client
|
|
2
|
+
import time
|
|
3
|
+
import logging
|
|
4
|
+
import httpx
|
|
5
|
+
import os
|
|
6
|
+
import pandas as pd
|
|
7
|
+
import numpy as np
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
logger = logging.getLogger(__name__)
|
|
11
|
+
logger.setLevel(logging.INFO)
|
|
12
|
+
# console_handler = logging.StreamHandler()
|
|
13
|
+
log_format = "%(asctime)s - %(name)s - %(levelname)s - %(message)s"
|
|
14
|
+
formatter = logging.Formatter(log_format)
|
|
15
|
+
# console_handler.setFormatter(formatter)
|
|
16
|
+
# logger.addHandler(console_handler)
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class Earth:
|
|
20
|
+
def __init__(self, config):
|
|
21
|
+
self.config = config
|
|
22
|
+
|
|
23
|
+
def create_dataset(self, dataset: dict, data: pd.DataFrame):
|
|
24
|
+
df = data.copy()
|
|
25
|
+
# Get a sql safe table name from the name
|
|
26
|
+
table_name = dataset["name"].replace(" ", "_").lower()
|
|
27
|
+
|
|
28
|
+
# Reset the index if it exists to use as the primary key
|
|
29
|
+
index_name = df.index.name
|
|
30
|
+
if index_name:
|
|
31
|
+
df.reset_index(inplace=True)
|
|
32
|
+
|
|
33
|
+
create_table_sql = pd.io.sql.get_schema(
|
|
34
|
+
df, name=table_name, schema="datasets", keys=index_name
|
|
35
|
+
)
|
|
36
|
+
|
|
37
|
+
create_table_sql = create_table_sql.replace(
|
|
38
|
+
"TIMESTAMP", "TIMESTAMP WITH TIME ZONE"
|
|
39
|
+
)
|
|
40
|
+
create_table_sql = create_table_sql.replace(
|
|
41
|
+
"CREATE TABLE", "CREATE TABLE IF NOT EXISTS"
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
logger.info(f"{create_table_sql}")
|
|
45
|
+
|
|
46
|
+
request = httpx.post(
|
|
47
|
+
f"{os.environ.get('OURO_BACKEND_URL')}/elements/earth/create/from-schema",
|
|
48
|
+
headers={
|
|
49
|
+
"Authorization": f"{self.config.token}",
|
|
50
|
+
"Content-Type": "application/json",
|
|
51
|
+
},
|
|
52
|
+
json={"dataset": {**dataset, "schema": create_table_sql}},
|
|
53
|
+
)
|
|
54
|
+
request.raise_for_status()
|
|
55
|
+
response = request.json()
|
|
56
|
+
|
|
57
|
+
logger.info(response)
|
|
58
|
+
|
|
59
|
+
if response["error"]:
|
|
60
|
+
logger.error(response["error"])
|
|
61
|
+
raise Exception(response["error"])
|
|
62
|
+
|
|
63
|
+
# If we've created the dataset, we can now insert the data
|
|
64
|
+
# if response["data"] and not response["error"]:
|
|
65
|
+
created = response["data"]
|
|
66
|
+
table_name = created["metadata"]["table_name"]
|
|
67
|
+
|
|
68
|
+
# Format the DataFrame to be inserted
|
|
69
|
+
# Manually format any dates to be ISO 8601
|
|
70
|
+
|
|
71
|
+
# insert_data = data.to_json(orient="records")
|
|
72
|
+
# insert_data = pd.read_json(insert_data)
|
|
73
|
+
|
|
74
|
+
for column in df.columns:
|
|
75
|
+
if df[column].dtype == "datetime64[ns]":
|
|
76
|
+
df[column] = df[column].dt.strftime("%Y-%m-%d")
|
|
77
|
+
|
|
78
|
+
# Fill NaN values with None
|
|
79
|
+
df = df.where(pd.notnull(df), None)
|
|
80
|
+
df = df.map(lambda x: None if pd.isna(x) or x == "" else x)
|
|
81
|
+
|
|
82
|
+
insert_data = df.to_dict(orient="records")
|
|
83
|
+
|
|
84
|
+
# Ensure that we're not inserting any NaN values by converting them to None
|
|
85
|
+
insert_data = [
|
|
86
|
+
{k: v if not pd.isna(v) else None for k, v in row.items()}
|
|
87
|
+
for row in insert_data
|
|
88
|
+
]
|
|
89
|
+
|
|
90
|
+
insert = self.config.client.table(table_name).insert(insert_data).execute()
|
|
91
|
+
if len(insert.data) > 0:
|
|
92
|
+
logger.info(f"Inserted {len(insert.data)} rows into {table_name}")
|
|
93
|
+
|
|
94
|
+
def get_dataset(self, dataset_id: str):
|
|
95
|
+
dataset = (
|
|
96
|
+
self.public_client.table("datasets")
|
|
97
|
+
.select("*")
|
|
98
|
+
.eq("id", dataset_id)
|
|
99
|
+
.limit(1)
|
|
100
|
+
.single()
|
|
101
|
+
.execute()
|
|
102
|
+
).data
|
|
103
|
+
return dataset
|
|
104
|
+
|
|
105
|
+
def get_dataset_from_name(self, name: str):
|
|
106
|
+
dataset = (
|
|
107
|
+
self.public_client.table("datasets")
|
|
108
|
+
.select("*")
|
|
109
|
+
.eq("name", name)
|
|
110
|
+
.limit(1)
|
|
111
|
+
.single()
|
|
112
|
+
.execute()
|
|
113
|
+
).data
|
|
114
|
+
return dataset
|
|
115
|
+
|
|
116
|
+
def get_dataset_schema(self, dataset_id: str):
|
|
117
|
+
dataset = (
|
|
118
|
+
self.public_client.table("datasets")
|
|
119
|
+
.select("*")
|
|
120
|
+
.eq("id", dataset_id)
|
|
121
|
+
.limit(1)
|
|
122
|
+
.single()
|
|
123
|
+
.execute()
|
|
124
|
+
).data
|
|
125
|
+
# Get the schema with an RPC call to the database
|
|
126
|
+
schema = (
|
|
127
|
+
self.public_client.rpc(
|
|
128
|
+
"get_table_schema",
|
|
129
|
+
{"table_schema_name": "datasets", "table_name": dataset["table_name"]},
|
|
130
|
+
).execute()
|
|
131
|
+
).data
|
|
132
|
+
return schema
|
|
133
|
+
|
|
134
|
+
def load_dataset(self, table_name: str, schema: str = "datasets"):
|
|
135
|
+
start = time.time()
|
|
136
|
+
|
|
137
|
+
row_count = self.client.table(table_name).select("*", count="exact").execute()
|
|
138
|
+
row_count = row_count.count
|
|
139
|
+
|
|
140
|
+
logger.info(f"Loading {row_count} rows from {schema}.{table_name}...")
|
|
141
|
+
# Batch load the data if it's too big
|
|
142
|
+
if row_count > 1_000_000:
|
|
143
|
+
data = []
|
|
144
|
+
for i in range(0, row_count, 1_000_000):
|
|
145
|
+
logger.debug(f"Loading rows {i} to {i+1_000_000}")
|
|
146
|
+
res = (
|
|
147
|
+
self.client.table(table_name)
|
|
148
|
+
.select("*")
|
|
149
|
+
.range(i, i + 1_000_000)
|
|
150
|
+
.execute()
|
|
151
|
+
)
|
|
152
|
+
data.extend(res.data)
|
|
153
|
+
else:
|
|
154
|
+
res = self.client.table(table_name).select("*").limit(1_000_000).execute()
|
|
155
|
+
data = res.data
|
|
156
|
+
|
|
157
|
+
end = time.time()
|
|
158
|
+
logger.info(f"Finished loading data in {round(end - start, 2)} seconds.")
|
|
159
|
+
|
|
160
|
+
self.data = data
|
|
161
|
+
return data
|
|
File without changes
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
|
|
2
|
+
def ouro_field(key, value):
|
|
3
|
+
def decorator(func):
|
|
4
|
+
if not hasattr(func, "ouro_fields"):
|
|
5
|
+
func.ouro_fields = {}
|
|
6
|
+
func.ouro_fields[key] = value
|
|
7
|
+
return func
|
|
8
|
+
return decorator
|
|
9
|
+
|
|
10
|
+
def get_custom_openapi(app, get_openapi):
|
|
11
|
+
def custom_openapi():
|
|
12
|
+
if app.openapi_schema:
|
|
13
|
+
return app.openapi_schema
|
|
14
|
+
|
|
15
|
+
openapi_schema = get_openapi(
|
|
16
|
+
title=app.title,
|
|
17
|
+
version=app.version,
|
|
18
|
+
summary=app.summary,
|
|
19
|
+
description=app.description,
|
|
20
|
+
routes=app.routes,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
for path, path_item in openapi_schema["paths"].items():
|
|
24
|
+
for method, operation in path_item.items():
|
|
25
|
+
endpoint = app.routes[0]
|
|
26
|
+
for route in app.routes:
|
|
27
|
+
if route.path == path and method.upper() in route.methods:
|
|
28
|
+
endpoint = route.endpoint
|
|
29
|
+
break
|
|
30
|
+
|
|
31
|
+
if hasattr(endpoint, "ouro_fields"):
|
|
32
|
+
operation.update(endpoint.ouro_fields)
|
|
33
|
+
|
|
34
|
+
app.openapi_schema = openapi_schema
|
|
35
|
+
return app.openapi_schema
|
|
36
|
+
|
|
37
|
+
return custom_openapi
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["hatchling"]
|
|
3
|
+
build-backend = "hatchling.build"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "ouro-py"
|
|
7
|
+
dynamic = ["version"]
|
|
8
|
+
description = "Python wrapper for the Ouro API"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
license = "MIT"
|
|
11
|
+
requires-python = ">=3.7"
|
|
12
|
+
authors = [{ name = "Matt Moderwell", email = "matt@ouro.foundation" }]
|
|
13
|
+
keywords = ["ouro"]
|
|
14
|
+
classifiers = []
|
|
15
|
+
dependencies = [
|
|
16
|
+
"httpx",
|
|
17
|
+
"numpy",
|
|
18
|
+
"pandas",
|
|
19
|
+
"postgrest",
|
|
20
|
+
"python-dotenv",
|
|
21
|
+
"supabase",
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
[project.urls]
|
|
25
|
+
Homepage = "https://github.com/ourofoundation/ouro-py"
|
|
26
|
+
PyPI = "https://pypi.org/project/ouro-py"
|
|
27
|
+
|
|
28
|
+
[tool.hatch.version]
|
|
29
|
+
path = "ouro/__init__.py"
|
|
30
|
+
|
|
31
|
+
[tool.hatch.build.targets.sdist]
|
|
32
|
+
include = ["/ouro"]
|