Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .github/workflows/test_suite.yml
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,13 @@ jobs:
- name: 'Reinstall PyGEM'
run: pip install --break-system-packages -e .

- name: 'Run ruff linting check'
run: ruff check .

- name: 'Run ruff formatting check'
if: ${{ !cancelled() }}
run: ruff format . --check

- name: 'Initialize PyGEM'
run: initialize

Expand Down
5 changes: 2 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
# pycache
# Subdirectories
__pycache__/

# vscode
sample_data/
.vscode/

# python bytecode
Expand Down
54 changes: 28 additions & 26 deletions docs/conf.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,47 +8,49 @@

import os
import sys

import tomllib

sys.path.insert(0, os.path.abspath('../pygem/'))

# source pyproject.toml to get release
with open("../pyproject.toml", "rb") as f:
with open('../pyproject.toml', 'rb') as f:
pyproject = tomllib.load(f)

project = 'PyGEM'
copyright = '2023, David Rounce'
author = 'David Rounce'
release = pyproject["tool"]["poetry"]["version"]
release = pyproject['tool']['poetry']['version']

# -- General configuration ---------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#general-configuration

extensions = ['sphinx_book_theme',
'myst_parser',
'sphinx.ext.autodoc',
'sphinx.ext.autosummary',
'sphinx.ext.intersphinx',
'numpydoc',
'sphinx.ext.viewcode',
'sphinx_togglebutton',
]
extensions = [
'sphinx_book_theme',
'myst_parser',
'sphinx.ext.autodoc',
'sphinx.ext.autosummary',
'sphinx.ext.intersphinx',
'numpydoc',
'sphinx.ext.viewcode',
'sphinx_togglebutton',
]

myst_enable_extensions = [
"amsmath",
"attrs_inline",
"colon_fence",
"deflist",
"dollarmath",
"fieldlist",
"html_admonition",
"html_image",
'amsmath',
'attrs_inline',
'colon_fence',
'deflist',
'dollarmath',
'fieldlist',
'html_admonition',
'html_image',
]

#templates_path = ['_templates']
# templates_path = ['_templates']
exclude_patterns = ['_build', 'Thumbs.db', '.DS_Store']



# -- Options for HTML output -------------------------------------------------
# https://www.sphinx-doc.org/en/master/usage/configuration.html#options-for-html-output

Expand All @@ -57,8 +59,8 @@
html_static_path = ['_static']

html_theme_options = {
"repository_url": "https://github.com/PyGEM-Community/PyGEM",
"use_repository_button": True,
"show_nav_level":2,
"navigation_depth":3,
}
'repository_url': 'https://github.com/PyGEM-Community/PyGEM',
'use_repository_button': True,
'show_nav_level': 2,
'navigation_depth': 3,
}
20 changes: 20 additions & 0 deletions docs/contributing.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ Installing a package in editable mode creates a symbolic link to your source cod
- First, open a Draft PR. Then consider:
- Have you finished making changes?
- Have you added tests for all new functionalities you introduced?
- Have you run the ruff linter and formatter? See [the linting and formatting section below](ruff_target) on how to do that.
- Have all tests passed in the CI? (Check the progress in the Checks tab of the PR.)

If the answer to all of the above is "yes", mark the PR as "Ready for review" and request a review from an appropriate reviewer. If in doubt of which reviewer to assign, assign [drounce](https://github.com/drounce).
Expand All @@ -56,3 +57,22 @@ Installing a package in editable mode creates a symbolic link to your source cod
- After responding to a reviewer's comment, do not mark it as resolved.
- Once all comments are addressed, request a new review from the same reviewer. The reviewer should then resolve the comments they are satisfied with.
- After approving someone else's PR, do not merge it. Let the original author of the PR merge it when they are ready, as they might notice necessary last-minute changes.

(ruff_target)=
## Code linting and formatting
PyGEM **requires** all code to be linted and formatted using [ruff](https://docs.astral.sh/ruff/formatter). Ruff enforces a consistent coding style (based on [Black](https://black.readthedocs.io/en/stable/the_black_code_style/index.html)) and helps prevent potential errors, stylistic issues, or deviations from coding standards. The configuration for Ruff can be found in the `pyproject.toml` file.

⚠️ **Both linting and formatting must be completed before code is merged.** These checks are run automatically in the CI pipeline. If any issues are detected, the pipeline will fail.

### Lint the codebase
To lint the codebase using Ruff, run the following command:
```
ruff check /path/to/code
```
Please address all reported errors. Many errors may be automatically and safely fixed by passing `--fix` to the above command. Other errors will need to be manually addressed.

### Format the codebase
To automatically format the codebase using Ruff, run the following command:
```
ruff format /path/to/code
```
4 changes: 3 additions & 1 deletion pygem/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,10 @@

Distrubted under the MIT lisence
"""

from importlib.metadata import version

try:
__version__ = version(__name__)
except:
__version__ = None
__version__ = None
41 changes: 30 additions & 11 deletions pygem/bin/op/duplicate_gdirs.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,43 +7,62 @@

duplicate OGGM glacier directories
"""

import argparse
import os
import shutil

# pygem imports
from pygem.setup.config import ConfigManager

# instantiate ConfigManager
config_manager = ConfigManager()
# read the config
pygem_prms = config_manager.read_config()


def main():
parser = argparse.ArgumentParser(description="Script to make duplicate oggm glacier directories - primarily to avoid corruption if parellelizing runs on a single glacier")
parser = argparse.ArgumentParser(
description='Script to make duplicate oggm glacier directories - primarily to avoid corruption if parellelizing runs on a single glacier'
)
# add arguments
parser.add_argument('-rgi_glac_number', type=str, default=None,
help='Randoph Glacier Inventory region')
parser.add_argument('-num_copies', type=int, default=1,
help='Number of copies to create of the glacier directory data')
parser.add_argument(
'-rgi_glac_number',
type=str,
default=None,
help='Randoph Glacier Inventory region',
)
parser.add_argument(
'-num_copies',
type=int,
default=1,
help='Number of copies to create of the glacier directory data',
)
args = parser.parse_args()
num_copies = args.num_copies
glac_num = args.rgi_glac_number

if (glac_num is not None) and (num_copies)>1:
reg,id = glac_num.split('.')
if (glac_num is not None) and (num_copies) > 1:
reg, id = glac_num.split('.')
reg = reg.zfill(2)
thous = id[:2]

root = pygem_prms['root'] + '/' + pygem_prms['oggm']['oggm_gdir_relpath']
sfix = '/per_glacier/' + f'RGI60-{reg}/' + f'RGI60-{reg}.{thous}/'

for n in range(num_copies):
nroot = os.path.abspath(root.replace('gdirs',f'gdirs_{n+1}'))
nroot = os.path.abspath(root.replace('gdirs', f'gdirs_{n + 1}'))
# duplicate structure
os.makedirs(nroot + sfix + f'RGI60-{reg}.{id}', exist_ok=True)
# copy directory data
shutil.copytree(root + sfix + f'RGI60-{reg}.{id}', nroot + sfix + f'RGI60-{reg}.{id}', dirs_exist_ok=True)
shutil.copytree(
root + sfix + f'RGI60-{reg}.{id}',
nroot + sfix + f'RGI60-{reg}.{id}',
dirs_exist_ok=True,
)

return


if __name__ == '__main__':
main()
main()
71 changes: 44 additions & 27 deletions pygem/bin/op/initialize.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,79 +7,90 @@

initialization script (ensure config.yaml and get sample datasets)
"""
import requests
import zipfile
import os,sys

import os
import shutil
import zipfile

import requests

from pygem.setup.config import ConfigManager

# instantiate ConfigManager
config_manager = ConfigManager(overwrite=True)
# read the config
pygem_prms = config_manager.read_config()

def print_file_tree(start_path, indent=""):

def print_file_tree(start_path, indent=''):
# Loop through all files and directories in the current directory
for item in os.listdir(start_path):
path = os.path.join(start_path, item)

# Print the current item with indentation
print(indent + "|-- " + item)
print(indent + '|-- ' + item)

# Recursively call this function if the item is a directory
if os.path.isdir(path):
print_file_tree(path, indent + " ")

print_file_tree(path, indent + ' ')


def get_confirm_token(response):
"""Extract confirmation token for Google Drive large file download."""
for key, value in response.cookies.items():
if key.startswith("download_warning"):
if key.startswith('download_warning'):
return value
return None


def save_response_content(response, destination):
"""Save the response content to a file."""
chunk_size = 32768
with open(destination, "wb") as file:
with open(destination, 'wb') as file:
for chunk in response.iter_content(chunk_size):
if chunk: # Filter out keep-alive chunks
file.write(chunk)


def get_unique_folder_name(dir):
"""Generate a unique folder name by appending a suffix if the folder already exists."""
counter = 1
unique_dir = dir
while os.path.exists(unique_dir):
unique_dir = f"{dir}_{counter}"
unique_dir = f'{dir}_{counter}'
counter += 1
return unique_dir


def download_and_unzip_from_google_drive(file_id, output_dir):
"""
Download a ZIP file from Google Drive and extract its contents.

Args:
file_id (str): The Google Drive file ID.
output_dir (str): The directory to save and extract the contents of the ZIP file.

Returns:
int: 1 if the ZIP file was successfully downloaded and extracted, 0 otherwise.
"""
# Google Drive URL template
base_url = "https://drive.google.com/uc?export=download"
base_url = 'https://drive.google.com/uc?export=download'

# Make sure the output directory exists
os.makedirs(output_dir, exist_ok=True)

# Path to save the downloaded file
zip_path = os.path.join(output_dir, "tmp_download.zip")
zip_path = os.path.join(output_dir, 'tmp_download.zip')

try:
# Start the download process
with requests.Session() as session:
response = session.get(base_url, params={"id": file_id}, stream=True)
response = session.get(base_url, params={'id': file_id}, stream=True)
token = get_confirm_token(response)
if token:
response = session.get(base_url, params={"id": file_id, "confirm": token}, stream=True)
response = session.get(
base_url, params={'id': file_id, 'confirm': token}, stream=True
)
save_response_content(response, zip_path)

# Unzip the file
Expand All @@ -88,7 +99,11 @@ def download_and_unzip_from_google_drive(file_id, output_dir):
zip_ref.extractall(tmppath)

# get root dir name of zipped files
dir = [item for item in os.listdir(tmppath) if os.path.isdir(os.path.join(tmppath, item))][0]
dir = [
item
for item in os.listdir(tmppath)
if os.path.isdir(os.path.join(tmppath, item))
][0]
unzip_dir = os.path.join(tmppath, dir)
# get unique name if root dir name already exists in output_dir
output_dir = get_unique_folder_name(os.path.join(output_dir, dir))
Expand All @@ -100,31 +115,33 @@ def download_and_unzip_from_google_drive(file_id, output_dir):

except (requests.RequestException, zipfile.BadZipFile, Exception) as e:
return None # Failure



def main():
# Define the base directory
basedir = os.path.join(os.path.expanduser('~'), 'PyGEM')
# Google Drive file id for sample dataset
file_id = "1Wu4ZqpOKxnc4EYhcRHQbwGq95FoOxMfZ"
file_id = '1Wu4ZqpOKxnc4EYhcRHQbwGq95FoOxMfZ'
# download and unzip
out = download_and_unzip_from_google_drive(file_id, basedir)

if out:
print(f"Downloaded PyGEM sample dataset:")
print('Downloaded PyGEM sample dataset:')
print(os.path.abspath(out))
try:
print_file_tree(out)
except:
pass

else:
print(f'Error downloading PyGEM sample dataset.')
print('Error downloading PyGEM sample dataset.')

# update root path in config.yaml
try:
config_manager.update_config(updates={'root':f'{out}/sample_data'})
config_manager.update_config(updates={'root': f'{out}/sample_data'})
except:
pass

if __name__ == "__main__":
main()


if __name__ == '__main__':
main()
Loading