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.
@@ -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
+ [![License: MIT](https://img.shields.io/badge/License-MIT-green.svg?label=license)](https://opensource.org/licenses/MIT)
113
+ [![CI](https://github.com/ourofoundation/ouro-py/actions/workflows/ci.yml/badge.svg)](https://github.com/ourofoundation/ouro-py/actions/workflows/ci.yml)
114
+ [![Python](https://img.shields.io/pypi/pyversions/ouro)](https://pypi.org/project/ouro)
115
+ [![Version](https://img.shields.io/pypi/v/ouro?color=%2334D058)](https://pypi.org/project/ouro)
116
+ [![Codecov](https://codecov.io/gh/ourofoundation/ouro-py/branch/develop/graph/badge.svg)](https://codecov.io/gh/ourofoundation/ouro-py)
117
+ [![Last commit](https://img.shields.io/github/last-commit/ourofoundation/ouro-py.svg?style=flat)](https://github.com/ourofoundation/ouro-py/commits)
118
+ [![GitHub commit activity](https://img.shields.io/github/commit-activity/m/ourofoundation/ouro-py)](https://github.com/ourofoundation/ouro-py/commits)
119
+ [![Github Stars](https://img.shields.io/github/stars/ourofoundation/ouro-py?style=flat&logo=github)](https://github.com/ourofoundation/ouro-py/stargazers)
120
+ [![Github Forks](https://img.shields.io/github/forks/ourofoundation/ouro-py?style=flat&logo=github)](https://github.com/ourofoundation/ouro-py/network/members)
121
+ [![Github Watchers](https://img.shields.io/github/watchers/ourofoundation/ouro-py?style=flat&logo=github)](https://github.com/ourofoundation/ouro-py)
122
+ [![GitHub contributors](https://img.shields.io/github/contributors/ourofoundation/ouro-py)](https://github.com/ourofoundation/ouro-py/graphs/contributors)
@@ -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
+ [![License: MIT](https://img.shields.io/badge/License-MIT-green.svg?label=license)](https://opensource.org/licenses/MIT)
95
+ [![CI](https://github.com/ourofoundation/ouro-py/actions/workflows/ci.yml/badge.svg)](https://github.com/ourofoundation/ouro-py/actions/workflows/ci.yml)
96
+ [![Python](https://img.shields.io/pypi/pyversions/ouro)](https://pypi.org/project/ouro)
97
+ [![Version](https://img.shields.io/pypi/v/ouro?color=%2334D058)](https://pypi.org/project/ouro)
98
+ [![Codecov](https://codecov.io/gh/ourofoundation/ouro-py/branch/develop/graph/badge.svg)](https://codecov.io/gh/ourofoundation/ouro-py)
99
+ [![Last commit](https://img.shields.io/github/last-commit/ourofoundation/ouro-py.svg?style=flat)](https://github.com/ourofoundation/ouro-py/commits)
100
+ [![GitHub commit activity](https://img.shields.io/github/commit-activity/m/ourofoundation/ouro-py)](https://github.com/ourofoundation/ouro-py/commits)
101
+ [![Github Stars](https://img.shields.io/github/stars/ourofoundation/ouro-py?style=flat&logo=github)](https://github.com/ourofoundation/ouro-py/stargazers)
102
+ [![Github Forks](https://img.shields.io/github/forks/ourofoundation/ouro-py?style=flat&logo=github)](https://github.com/ourofoundation/ouro-py/network/members)
103
+ [![Github Watchers](https://img.shields.io/github/watchers/ourofoundation/ouro-py?style=flat&logo=github)](https://github.com/ourofoundation/ouro-py)
104
+ [![GitHub contributors](https://img.shields.io/github/contributors/ourofoundation/ouro-py)](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"]