Metadata-Version: 2.4
Name: pygarden
Version: 0.3.24
Summary: Package to perform everyday tasks - logging, accessing database, etc.
Project-URL: Source, https://code.ornl.gov/pygarden/pygarden
Author: Alec Hamaker
Author-email: "Grant, Josh" <grantjn@ornl.gov>, Jonathan Huihui <huihuijk@ornl.gov>, Bhaskar Bishnoi <bishnoib@ornl.gov>, Kyle Medlen <medlenwk@ornl.gov>, Midgie MacFarland <macfarlandmj@ornl.gov>, Chris Tester <testercb@ornl.gov>, Jacob Isber <isberj@ornl.gov>, Mitchell Broughton <broxsonmr@ornl.gov>
License: Copyright 2021 ORNL
        
        Permission is hereby granted, free of charge, to any person obtaining a copy of
        this software and associated documentation files (the "Software"), to deal in 
        the Software without restriction, including without limitation the rights to 
        use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 
        of the Software, and to permit persons to whom the Software is furnished to do 
        so, subject to the following conditions:
        
        The above copyright notice and this permission notice shall be included in all
        copies or substantial portions of the Software.
        
        THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 
        IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 
        FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 
        AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 
        LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 
        OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 
        SOFTWARE.
License-File: LICENSE
Keywords: database,logging,mixins,mysqlmixin,pandasmixin,postgresmixin
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Software Development :: Build Tools
Classifier: Topic :: Software Development :: Libraries
Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: <3.13,>=3.10
Requires-Dist: loguru>=0.7.2
Requires-Dist: rich>=13.7.1
Provides-Extra: all
Requires-Dist: asyncpg; extra == 'all'
Requires-Dist: beautifulsoup4~=4.10.0; extra == 'all'
Requires-Dist: cfscrape~=2.1.1; extra == 'all'
Requires-Dist: click==8.0.3; extra == 'all'
Requires-Dist: cloudscraper~=1.2.60; extra == 'all'
Requires-Dist: flask>=2.0.2; extra == 'all'
Requires-Dist: influxdb==5.3.1; extra == 'all'
Requires-Dist: ldap3==2.9.1; extra == 'all'
Requires-Dist: minio~=7.1.0; extra == 'all'
Requires-Dist: pandas==2.1.4; extra == 'all'
Requires-Dist: psycopg2-binary==2.9.9; extra == 'all'
Requires-Dist: pymssql>=2.2.11; extra == 'all'
Requires-Dist: requests~=2.27.1; extra == 'all'
Requires-Dist: rich==13.7.1; extra == 'all'
Requires-Dist: selenium~=4.1.3; extra == 'all'
Requires-Dist: sqlalchemy~=2.0.23; extra == 'all'
Requires-Dist: urllib3~=1.26.9; extra == 'all'
Requires-Dist: websocket~=0.2.1; extra == 'all'
Provides-Extra: auth
Requires-Dist: flask>=2.0.2; extra == 'auth'
Requires-Dist: ldap3==2.9.1; extra == 'auth'
Provides-Extra: cli
Requires-Dist: click==8.0.3; extra == 'cli'
Requires-Dist: rich==13.7.1; extra == 'cli'
Provides-Extra: db-pandas
Requires-Dist: pandas==2.1.4; extra == 'db-pandas'
Requires-Dist: sqlalchemy~=2.0.23; extra == 'db-pandas'
Provides-Extra: dev
Requires-Dist: bumpversion>=0.6.0; extra == 'dev'
Requires-Dist: moto==5.0.11; extra == 'dev'
Requires-Dist: pytest-cov==5.0.0; extra == 'dev'
Requires-Dist: pytest==8.3.1; extra == 'dev'
Requires-Dist: python-hcl2==7.1.0; extra == 'dev'
Requires-Dist: ruff==0.5.4; extra == 'dev'
Provides-Extra: influx
Requires-Dist: influxdb==5.3.1; extra == 'influx'
Provides-Extra: llama
Requires-Dist: llama-cpp-python==0.2.74; extra == 'llama'
Provides-Extra: minio
Requires-Dist: minio~=7.1.0; extra == 'minio'
Provides-Extra: mssql
Requires-Dist: pymssql>=2.2.11; extra == 'mssql'
Provides-Extra: postgres
Requires-Dist: asyncpg; extra == 'postgres'
Requires-Dist: psycopg2-binary==2.9.9; extra == 'postgres'
Provides-Extra: scrapers
Requires-Dist: beautifulsoup4~=4.10.0; extra == 'scrapers'
Requires-Dist: cfscrape~=2.1.1; extra == 'scrapers'
Requires-Dist: cloudscraper~=1.2.60; extra == 'scrapers'
Requires-Dist: pandas==2.1.4; extra == 'scrapers'
Requires-Dist: requests~=2.27.1; extra == 'scrapers'
Requires-Dist: selenium~=4.1.3; extra == 'scrapers'
Requires-Dist: urllib3~=1.26.9; extra == 'scrapers'
Requires-Dist: websocket~=0.2.1; extra == 'scrapers'
Description-Content-Type: text/markdown

# pyGARDEN Package

Code for the pyGARDEN (**G**eneral **A**pplication **R**esource **D**evelopment **E**nvironment **N**etwork) Python Package to include easy injectable and rich logging, environment checking, and database
connections and query. By default, only SQLite is available as a mixin, but other mixin types to the `Database` class
are Postgres (`[postgres]` extra) or Microsoft SQL Server (`[mssql]` extra). See the [extras](#Extras) section for more
information on how to install these extras, when to choose them, and how to use them.
Some highlights from `pyGARDEN`:

- This package contains an extensible `Database` metaclass with a generic query function that is usable out of the box.
- Everything is configurable with environmental variables -- including email sending, logging, and database connections.

## Installation

### Installation via `uv`

If you have a `uv venv`, you can run the following command in the root of the repo:

`uv pip install -e ".[dev,cli,postgres]"`

You may then need to source your `uv` environment to use the `pygarden` command line interface, e.g. `source .venv/bin/activate`.

Replace the above extras with the extras of your choice.

## Extras

To enable support for specific databases, use the following extras:

- `postgres`: Enables Postgres support via `psycopg2`
- `mssql`: Enables MSSQL support via `pymssql`

#### `pymssql` on MacOS

Before install the `mssql` extra, you may need to install `freetds` using `brew`:

```bash
brew install freetds openssl@3.0
export LDFLAGS="-L/opt/homebrew/opt/freetds/lib -L/opt/homebrew/opt/openssl@3.0/lib"
export CPPFLAGS="-I/opt/homebrew/opt/freetds/include -I/opt/homebrew/opt/openssl@3.0/include"
export PKG_CONFIG_PATH="/opt/homebrew/opt/openssl@3.0/lib/pkgconfig"
```

Then, install `pymssql` to your uv environment on its own using:

```bash
uv pip install pymssql==2.2.11 --no-binary :all:
```

After than, you can run `uv sync --extra mssql` or `uv sync --extra all` to get the rest of the dependencies in the group.

### Installation via pip

Run this command to install version 0.3.24 (latest) via pip:

`python3 -m pip --no-cache-dir install pygarden==0.3.24`

This will install latest (not recommended):

`python3 -m pip --no-cache-dir install pygarden`

## Getting the Image

The Docker image for this project is hosted on [Savannah](https://savannah.ornl.gov/),
and is tagged with `savannah.ornl.gov/common/pygarden:${PYTHON_VERSION:-3.12}-latest`,
where `${PYTHON_VERSION:-3.12}` is the Python version you want to use. To pull the
image, you can run:

```bash
docker pull savannah.ornl.gov/common/pygarden:${PYTHON_VERSION:-3.12}-latest
```

## Saving the Image to a Tar File

If you want to save the image to a tar file, you can run:

```bash
docker save savannah.ornl.gov/common/pygarden:${PYTHON_VERSION:-3.12}-latest -o pygarden.tar
gzip pygarden.tar  # optionally compress the tar file
```

Now you can transport the image wherever you'd like!

### Configuration via Environment Variables

Below is a list of environmental variables and what they do:

- DATABASE_TIMEOUT, PG_TIMEOUT: an integer representing the seconds to wait before deciding a timeout occurred.
- DATABASE_DB, PG_DATABASE: a string representing the database to connect to
- DATABASE_USER, PG_USER: a string representing the user to connect to the database as
- DATABASE_PW, PG_PASSWORD: a string representing the password for the DATABASE_USER
- DATABASE_HOST, PG_HOST: a string representing the hostname or IP address hosting the database
- DATABASE_PORT, PG_PORT: an integer representing the port to connect to the database on
- DATABASE_SCHEMA, PG_SCHEMA: a string representing the schema to default to when creating a database connection

These environmental variables have been assigned default values for the Docker container in the file `envfile`, which is called in `docker-compose.yaml` and `docker-compose.test.yaml`

### Creating an extensible Database Python Class

Some `Database` methods such as `query` and `open` rely on
[python mixins](https://www.python.org/dev/peps/pep-0487/), which allow
abstract classes to interact with different types of databases and provide
additional functionality not provided by the `Database` class. Below is an
example of how to create a Database class that uses the PostgresMixin:

```python
from pygarden.mixins.postgres import PostgresMixin
from pygarden.database import Database


class PostgresDatabase(Database, PostgresMixin):
    """The class that allows Database to interact with psycopg2."""
    # TODO add additional functions for your class here, specific to your needs

with PostgresDatabase() as db:
    db.query('SELECT NOW()')
```

### Creating a CRUD table with crud_table.py

```python
from pygarden.mixins.postgres import PostgresMixin
from pygarden.database import Database
from pygarden.crud_table import CRUDTable

# PLEASE NOTE: the name of class MUST be consistent with the name of the
# table in the database itself.
class Users(CRUDTable):
    """Provides CRUD access to the users table"""
    def __init__(self, db):
    """__init__:

    :param db: the database that contains the 'users' table.
    """
    # be sure to define the columns as they appear in the database.
    # in this example, the users table has 6 columns.
    columns = {
        'id': int,
        'email': str,
        'password': str,
    }
    # initialize the super class with the column definition, the schema that
    # the table is in, and the database object.
    super().__init__(columns, schema='public', db)

    # TODO add additional functions for your class here, specific to your needs


class PostgresDatabase(Database, PostgresMixin):
    """The class that allows Database to interact with psycopg2."""
    def __init__(self):
        super().__init__()
        # here we are assigning the CRUD table to the database's 'users'
        # variable and passing the database object to the constructor
        self.users = users(self)
    # TODO add additional functions for your class here, specific to your needs

with PostgresDatabase() as db:
    db.query('SELECT NOW()')

    # this will create a user in the database, with the specified fields and
    # null for the columns not specified
    db.users.create(id=1337, email='admin@test.com', password='*****')

    # this will read the entire user's table 'SELECT * FROM public.users;'
    users_table = db.users.read()

    # this will read only id, and email columns from the users table
    # 'SELECT id, email FROM public.users;'
    columns_users_table = db.users.read(columns=['id', 'email'])

    # data can be read from the database in 'json' format by specifying
    # json=True in your call. The 'json' format returned is a python dictionary
    # that is json serializable
    json_data = db.users.read(json=True)

    # where clauses can be passed as keyword arguments to the call
    # 'SELECT * FROM public.users WHERE id = 1337;
    filtered_users_table = db.users.read(id=1337)

    # to update entries, a where clause must be passed as follows
    # 'UPDATE public.users SET email='update@test.com' WHERE id=1337;'
    db.users.update(where={'id': 1337}, email='update@test.com')

    # to delete entries, a where clause must also be passed. The where clause
    # for a deletion should come in the form of keyword arguments
    # 'DELETE FROM public.users WHERE id=1337;'
    db.users.delete(id=1337)
```

## Testing

The tests for `pygarden` use `pytest` and are available in the `dev` extra.
