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
13 changes: 7 additions & 6 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,25 +4,26 @@ on: [ push, pull_request ]

jobs:
main:
runs-on: ubuntu-latest
runs-on: ubuntu-24.04
strategy:
matrix:
python-version: ["3.8", "3.9", "3.10", "3.11"]
python-version: ["3.12"]
steps:
- uses: actions/checkout@v2
- uses: actions/setup-python@v2
- uses: actions/checkout@master
- uses: actions/setup-python@v6
name: Setup Python ${{ matrix.python-version }}
with:
python-version: ${{ matrix.python-version }}
- name: Install requirements 📦
run: |
python3 -m pip install --upgrade pip
pip3 install setuptools
pip3 install -r requirements.txt
pip3 install -r requirements-dev.txt
- name: Install package 📦
run: python3 setup.py install
run: pip3 install .
- name: run tests ⚙️
run: python3 setup.py test
run: python3 tests/run_tests.py
- name: run flake8 ⚙️
run: |
find . -type f -name "*.py" | xargs flake8
Expand Down
25 changes: 20 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,7 @@ cd pywiscat
git clone https://github.com/wmo-im/pywiscat.git
cd pywiscat
pip3 install -r requirements.txt
python3 setup.py build
python3 setup.py install
pip3 install .
```

## Running
Expand Down Expand Up @@ -62,6 +61,25 @@ pywiscat search --bbox -142,42,-52,84

# get more information about a WIS2 GDC record
pywiscat get urn:x-wmo:md:can:eccc-msc:c7c9d726-c48a-49e3-98ab-78a1ab87cda8

## Archive utilities

# download and extract a WIS2 GDC metadata archive zipfile to a specific directory
pywiscat archive get /path/to/archive

## Metrics analyzers

# analyze core records by centre identifier
pywiscat metrics core /path/to/archive

# analyze recommended records by centre identifier
pywiscat metrics recommended /path/to/archive

# analyze Earth system disciplines by centre identifier
pywiscat metrics earth-system-discipline /path/to/archive

# analyze Key Performance Indicators (KPIs) by centre identifier
pywiscat metrics kpi ca-eccc-msc /path/to/archive
```

## Using the API
Expand Down Expand Up @@ -94,9 +112,6 @@ python3 setup.py install
### Running tests

```bash
# via setuptools
python3 setup.py test
# manually
python3 tests/run_tests.py
```

Expand Down
3 changes: 3 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[build-system]
requires = ["setuptools>=46.4", "wheel"]
build-backend = "setuptools.build_meta"
6 changes: 5 additions & 1 deletion pywiscat/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
#
# Authors: Tom Kralidis <tomkralidis@gmail.com>
#
# Copyright (c) 2024 Tom Kralidis
# Copyright (c) 2026 Tom Kralidis
#
# Permission is hereby granted, free of charge, to any person
# obtaining a copy of this software and associated documentation
Expand All @@ -29,7 +29,9 @@

import click

from pywiscat.wis2.archive import archive
from pywiscat.wis2.catalogue import get_gdc_record, search_gdc
from pywiscat.wis2.metrics import metrics

__version__ = '0.3.dev2'

Expand All @@ -42,3 +44,5 @@ def cli():

cli.add_command(search_gdc)
cli.add_command(get_gdc_record)
cli.add_command(archive)
cli.add_command(metrics)
33 changes: 33 additions & 0 deletions pywiscat/env.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
# =================================================================
#
# Authors: Tom Kralidis <tomkralidis@gmail.com>
#
# Copyright (c) 2026 Tom Kralidis
#
# 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.
#
# =================================================================

import os

GDC_URL = os.environ.get('PYWISCAT_GDC_URL', 'https://wis2-gdc.weather.gc.ca')
GDC_URL = f'{GDC_URL}/collections/wis2-discovery-metadata'
104 changes: 104 additions & 0 deletions pywiscat/wis2/archive.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
# =================================================================
#
# Authors: Tom Kralidis <tomkralidis@gmail.com>
#
# Copyright (c) 2026 Tom Kralidis
#
# 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.
#
# =================================================================

import logging

from io import BytesIO
import zipfile

import click
import requests

from pywiscat.cli_helpers import cli_option_verbosity
from pywiscat.env import GDC_URL

LOGGER = logging.getLogger(__name__)


def download_and_extract_archive(gdc_url: str, output_dir: str) -> bool:
"""
Download and extract a metadata archive zipfile from a WIS2 GDC

:param gdc_url: URL of WIS2 GDC
:param output_dir: output directory

:returns: `bool` of result
"""

archive_link = None

LOGGER.debug(f'Fetching GDC collection information from {GDC_URL}')
response = requests.get(GDC_URL)
response.raise_for_status()

response = response.json()

for link in response['links']:
if link.get('rel') == 'archives':
archive_link = link['href']
LOGGER.debug(f'Archive link found: {archive_link}')
break

if archive_link is None:
LOGGER.warning('Archive link not found')
return False

LOGGER.debug(f'Fetching metadata archive zipfile from {archive_link}')
response = requests.get(archive_link)
response.raise_for_status()

LOGGER.debug(f'Extracting zipfile to {output_dir}')
with zipfile.ZipFile(BytesIO(response.content)) as fh:
fh.extractall(output_dir)

return True


@click.group()
def archive():
"""Run archive utilities against a WIS2 GDC"""

pass


@click.command()
@click.pass_context
@cli_option_verbosity
@click.argument('output_dir')
def get(ctx, output_dir, verbosity='NOTSET'):
"""Download and extract archive"""

click.echo(f'Downloading and extracting zipfile from {GDC_URL} to {output_dir}') # noqa
if not download_and_extract_archive(GDC_URL, output_dir):
click.echo('Download and extract failed. Set -v DEBUG for more information') # noqa

click.echo('Done')


archive.add_command(get)
14 changes: 6 additions & 8 deletions pywiscat/wis2/catalogue.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
#
# Authors: Tom Kralidis <tomkralidis@gmail.com>
#
# Copyright (c) 2025 Tom Kralidis
# Copyright (c) 2026 Tom Kralidis
#
# Permission is hereby granted, free of charge, to any person
# obtaining a copy of this software and associated documentation
Expand All @@ -28,7 +28,6 @@
# =================================================================

import logging
import os
from textwrap import indent, wrap

import click
Expand All @@ -38,13 +37,12 @@
import requests

from pywiscat.cli_helpers import cli_option_verbosity
LOGGER = logging.getLogger(__name__)
from pywiscat.env import GDC_URL

GDC_URL = os.environ.get('PYWISCAT_GDC_URL', 'https://wis2-gdc.weather.gc.ca')
GDC_URL = f'{GDC_URL}/collections/wis2-discovery-metadata'
LOGGER = logging.getLogger(__name__)


def get_country_and_centre(identifier):
def get_country_and_centre_id(identifier):
"""
Get country and centre id from a WCMP2 identifier

Expand Down Expand Up @@ -185,7 +183,7 @@ def search(**kwargs: dict) -> dict:

LOGGER.debug('Building up results')
for item in response_json['features']:
country, centre_id = get_country_and_centre(item['id'])
country, centre_id = get_country_and_centre_id(item['id'])

output['fields'] = [
'id',
Expand Down Expand Up @@ -324,7 +322,7 @@ def get_gdc_record(ctx, identifier, verbosity):
if 'description' in result:
raise click.ClickException(f'Record identifier {identifier} not found')

country, centre_id = get_country_and_centre(result['id'])
country, centre_id = get_country_and_centre_id(result['id'])
country = get_country_prettified(country)

click.echo(f"Record: {result['properties']['title']}\n")
Expand Down
Loading