Skip to content

Commit 727ebd4

Browse files
committed
reworking for best practices
1 parent a236cdf commit 727ebd4

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

61 files changed

+2299
-1813
lines changed

.github/workflows/ci.yml

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches: [main]
6+
pull_request:
7+
branches: [main]
8+
9+
jobs:
10+
test:
11+
runs-on: ubuntu-latest
12+
strategy:
13+
matrix:
14+
python-version: ["3.10", "3.11", "3.12"]
15+
16+
steps:
17+
- uses: actions/checkout@v4
18+
19+
- name: Install uv
20+
uses: astral-sh/setup-uv@v1
21+
22+
- name: Set up Python ${{ matrix.python-version }}
23+
run: uv python install ${{ matrix.python-version }}
24+
25+
- name: Install dependencies
26+
run: uv sync --all-extras --dev
27+
28+
- name: Run tests
29+
run: uv run pytest
30+
31+
- name: Run linting
32+
run: uv run ruff check .

.github/workflows/pypi-publish.yml

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
name: Publish to PyPI
2+
3+
on:
4+
push:
5+
tags:
6+
- 'v*'
7+
workflow_dispatch:
8+
9+
jobs:
10+
pypi-publish:
11+
name: Build and publish Python distribution
12+
runs-on: ubuntu-latest
13+
permissions:
14+
contents: read
15+
16+
steps:
17+
- name: Checkout source
18+
uses: actions/checkout@v4
19+
20+
- name: Install uv
21+
uses: astral-sh/setup-uv@v5
22+
23+
- name: Set up Python
24+
uses: actions/setup-python@v5
25+
with:
26+
python-version: "3.12"
27+
28+
- name: Build package
29+
run: uv build
30+
31+
- name: Publish to PyPI
32+
run: uv publish --token ${{ secrets.PYPI_TOKEN }}

.pre-commit-config.yaml

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
repos:
2+
- repo: https://github.com/pre-commit/pre-commit-hooks
3+
rev: v4.5.0
4+
hooks:
5+
- id: trailing-whitespace
6+
- id: end-of-file-fixer
7+
- id: check-yaml
8+
- id: check-added-large-files
9+
10+
- repo: https://github.com/astral-sh/ruff-pre-commit
11+
rev: v0.1.11
12+
hooks:
13+
- id: ruff
14+
args: [--fix]
15+
- id: ruff-format

DEVELOPER.md

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,30 @@ It's recommended that you use `RSXML-RiverscapesXML.code-workspace` if you're in
44

55
Always work on a branch and use a pull request.
66

7+
### Prerequisites
8+
9+
- [uv](https://github.com/astral-sh/uv) for dependency management.
10+
- [pre-commit](https://pre-commit.com/) for git hooks.
11+
12+
### Setup
13+
14+
1. Install dependencies:
15+
```bash
16+
uv sync --all-extras --dev
17+
```
18+
2. Install pre-commit hooks:
19+
```bash
20+
pre-commit install
21+
```
22+
23+
## Running tests
24+
25+
We use `nox` to run tests across multiple Python versions.
26+
27+
```bash
28+
nox
29+
```
30+
731
## Running examples locally
832

933
In order to run the examples you will need to install `rsxml`. If you want that installed version to be editable, you can use `pip install -e .` from the root rsxml directory.
@@ -12,13 +36,7 @@ In order to run the examples you will need to install `rsxml`. If you want that
1236

1337
There are two scripts to deploy the package to PyPI:
1438

15-
- `build.sh` - builds the package and puts it in `dist/`. This script also does some checking for compliance with PyPI's requirements.
16-
- `deploy.sh` - Push the package to the PyPI server. This script will prompt you for your PyPI credentials.
39+
- `build.sh` - builds the package and puts it in `dist/`.
40+
- `deploy.sh` - Push the package to the PyPI server. This script requires `PYPI_TOKEN` environment variable.
1741

18-
In order to run these scripts, you'll need to install some dependencies:
19-
20-
```bash
21-
pip install wheel twine
22-
# On OSX you'll need to install pandoc. (WINDOWS INSTRUCTIONS PENDING)
23-
brew install pandoc
24-
```
42+
The scripts will automatically install `uv` if it is not present.
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
{
2+
"folders": [
3+
{
4+
"name": "📦 rsxml",
5+
"path": "."
6+
},
7+
],
8+
"settings": {
9+
"terminal.integrated.defaultProfile.linux": "zsh",
10+
"[python]": {
11+
"editor.tabSize": 4,
12+
"editor.formatOnSave": true,
13+
},
14+
"autopep8.args": [
15+
"--max-line-length=240",
16+
],
17+
"pylint.args": [
18+
"--extension-pkg-whitelist=pygeoprocessing",
19+
"--disable=C0301,C0114,C0103,W0719,W0718",
20+
"--max-line-length=240"
21+
],
22+
"python.terminal.activateEnvironment": true,
23+
"python.testing.pytestEnabled": true,
24+
"python.testing.unittestEnabled": true,
25+
"files.watcherExclude": {
26+
"**/*.egg-info/**": true,
27+
"**/docs/**": true,
28+
"**/*.pytest_cache/**": true,
29+
"**/.venv/**": true,
30+
"**/__pycache__/**": true
31+
},
32+
"search.exclude": {
33+
"**/*.egg-info/**": true,
34+
"**/*.pytest_cache/**": true,
35+
"**/.venv/**": true,
36+
"**/__pycache__/**": true
37+
},
38+
"files.exclude": {
39+
"**/*.egg-info/**": true,
40+
"**/*.pytest_cache/**": true,
41+
"**/.venv/**": true,
42+
"**/__pycache__/**": true
43+
}
44+
},
45+
// Suggested extensions that will be installed for everyone
46+
"extensions": {
47+
"recommendations": [
48+
"mhutchie.git-graph",
49+
"ms-python.autopep8",
50+
"ms-python.pylint",
51+
"ms-python.python",
52+
"ms-python.vscode-pylance",
53+
"njpwerner.autodocstring",
54+
"redhat.vscode-xml",
55+
"unifiedjs.vscode-mdx",
56+
"yzhang.markdown-all-in-one",
57+
"GitHub.copilot-chat",
58+
"GitHub.copilot"
59+
]
60+
}
61+
}

examples/debug/process_debug.py

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,10 @@
1010
Note: You need to install psutil and matplotlib `pip install psutil matplotlib` for this to work.
1111
1212
"""
13-
from time import sleep
13+
1414
import tempfile
15+
from time import sleep
16+
1517
from rsxml import Logger, debug
1618

1719

@@ -21,27 +23,26 @@ def my_work_method(some_text_arg: str):
2123
Args:
2224
some_text_arg (str): _description_
2325
"""
24-
log = Logger('my_work_method')
26+
log = Logger("my_work_method")
2527
log.info(some_text_arg)
2628
sleep(10)
2729

2830

29-
if __name__ == '__main__':
30-
logmain = Logger('main')
31+
if __name__ == "__main__":
32+
logmain = Logger("main")
3133

3234
# Usully you'd get these from argparse
33-
SOME_TEXT_ARG = 'This is some text'
35+
SOME_TEXT_ARG = "This is some text"
3436
DEBUG = True
3537

3638
# Temporary files are just for this example
37-
with tempfile.NamedTemporaryFile(suffix='.csv') as f:
38-
39+
with tempfile.NamedTemporaryFile(suffix=".csv") as f:
3940
# Here we make a choice to either run our work method normally or in debug mode
4041
if DEBUG is True:
4142
# Do the import here so it doesn't cause any errors if we're not debugging
4243
retcode, max_obj = debug.thread_run(my_work_method, f.name, SOME_TEXT_ARG)
4344
# It returns some useful information
44-
logmain.info('Return code: {}, [Max process usage] {}'.format(retcode, max_obj))
45+
logmain.info("Return code: {}, [Max process usage] {}".format(retcode, max_obj))
4546
else:
4647
my_work_method(SOME_TEXT_ARG)
4748

examples/debug/timers.py

Lines changed: 23 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -6,39 +6,41 @@
66
to time how long it takes to do something and then write the results to a csv file.
77
88
"""
9-
from time import sleep
10-
import tempfile
9+
1110
import sqlite3
12-
from rsxml.debug import Timer, LoopTimer, TimerBuckets, TimerWaypoints
11+
import tempfile
12+
from time import sleep
13+
1314
from rsxml import Logger
15+
from rsxml.debug import LoopTimer, Timer, TimerBuckets, TimerWaypoints
1416

1517

1618
def simple():
1719
"""
1820
Basic Timer
1921
20-
This is a really simple timer with a date subtraction.
22+
This is a really simple timer with a date subtraction.
2123
"""
22-
log = Logger('Simple Timer')
24+
log = Logger("Simple Timer")
2325
# Here is a basic timer. Define it and then call elapsed() to get the time
2426
_tmr = Timer()
2527
sleep(1)
2628

27-
log.info(f'Timer: {_tmr.elapsed()}')
29+
log.info(f"Timer: {_tmr.elapsed()}")
2830
# [info] [Simple Timer] Timer: 1.0054419169900939
2931

30-
log.info(f'Timer: {_tmr.toString()}')
32+
log.info(f"Timer: {_tmr.toString()}")
3133
# [info] [Simple Timer] Timer: 1.0 seconds
3234

3335

3436
def loop_timer():
3537
"""
3638
Loop Timer
3739
38-
Loop timers are useful for debugging why for loops are taking so long.
40+
Loop timers are useful for debugging why for loops are taking so long.
3941
"""
40-
log = Logger('Loop Timer')
41-
_lt = LoopTimer('My Loop Timer', log)
42+
log = Logger("Loop Timer")
43+
_lt = LoopTimer("My Loop Timer", log)
4244
for _i in range(10):
4345
_lt.tick() # Signal to the loop timer that one iteration has happened
4446
_lt.progprint() # print the current status of the loop timer continuously to the loop
@@ -60,19 +62,19 @@ def timer_buckets(csv_file: str):
6062
everything inside the with statement will be counted as part of the timer.
6163
"""
6264
_tmr_buckets = TimerBuckets(table_name="debug_table", csv_path=csv_file)
63-
conn = sqlite3.connect(':memory:')
65+
conn = sqlite3.connect(":memory:")
6466

6567
for i in range(10):
66-
arbitrary_columns = {'something': i, 'something_else': f"KEY {i}"}
68+
arbitrary_columns = {"something": i, "something_else": f"KEY {i}"}
6769
_tmr_buckets.tick(arbitrary_columns)
6870

69-
with TimerBuckets('key1'):
71+
with TimerBuckets("key1"):
7072
sleep(0.2)
7173

72-
with TimerBuckets('key1'):
74+
with TimerBuckets("key1"):
7375
sleep(0.1)
7476

75-
with TimerBuckets('key2'):
77+
with TimerBuckets("key2"):
7678
sleep(0.15)
7779

7880
_tmr_buckets.write_csv(csv_file)
@@ -98,19 +100,19 @@ def timer_waypoints():
98100
Waypoints are a way to track the time between points in your code.
99101
Think of them like lap markers.
100102
"""
101-
log = Logger('Timer Waypoints')
102-
log.setlevel('DEBUG')
103+
log = Logger("Timer Waypoints")
104+
log.setlevel("DEBUG")
103105

104106
_tmr_waypoints = TimerWaypoints()
105107

106108
sleep(1)
107-
_tmr_waypoints.timer_break('first thing')
109+
_tmr_waypoints.timer_break("first thing")
108110

109111
sleep(3)
110-
_tmr_waypoints.timer_break('second thing')
112+
_tmr_waypoints.timer_break("second thing")
111113

112114
sleep(2)
113-
_tmr_waypoints.timer_break('third thing')
115+
_tmr_waypoints.timer_break("third thing")
114116

115117
log.info(_tmr_waypoints.toString())
116118

@@ -128,7 +130,7 @@ def timer_waypoints():
128130
simple()
129131
loop_timer()
130132

131-
with tempfile.NamedTemporaryFile(suffix='.csv') as f:
133+
with tempfile.NamedTemporaryFile(suffix=".csv") as f:
132134
timer_buckets(f.name)
133135

134136
timer_waypoints()

0 commit comments

Comments
 (0)