Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Advance python minimum version to 3.9 / update dependencies #204

Merged
merged 8 commits into from
Sep 9, 2023
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
6 changes: 3 additions & 3 deletions .github/workflows/build-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ jobs:
# fail it if doesn't conform to black
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- uses: psf/black@stable
with:
options: "--check --verbose"
Expand All @@ -25,10 +25,10 @@ jobs:
strategy:
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
python-version: ["3.8", "3.11"]
python-version: ["3.9", "3.11"]

steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
with:
Expand Down
6 changes: 3 additions & 3 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ jobs:
deploy:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4

- name: Set up Python
uses: actions/setup-python@v4
Expand All @@ -38,7 +38,7 @@ jobs:
run: |
i=0
while [ $i -lt 12 ] && [ "${{ github.ref_name }}" != $(pip index versions -i https://test.pypi.org/simple --pre market_prices | cut -d'(' -f2 | cut -d')' -f1 | sed 1q) ];\
do echo "waiting for package to appear in test index, i is $i, sleeping 5s"; sleep 5s; echo "woken up"; ((i++)); echo "next i is $i"; done
do echo "waiting for package to appear in test index, i is $i"; echo "sleeping 5s"; sleep 5s; echo "woken up"; ((i++)); echo "next i is $i"; done
pip install --index-url https://test.pypi.org/simple market_prices==${{ github.ref_name }} --no-deps
pip install -r etc/requirements.txt
python -c 'import market_prices;print(market_prices.__version__)'
Expand All @@ -58,6 +58,6 @@ jobs:
run: |
i=0
while [ $i -lt 12 ] && [ "${{ github.ref_name }}" != $(pip index versions -i https://pypi.org/simple --pre market_prices | cut -d'(' -f2 | cut -d')' -f1 | sed 1q) ];\
do echo "waiting for package to appear in index, i is $i, sleeping 5s"; sleep 5s; echo "woken up"; ((i++)); echo "next i is $i"; done
do echo "waiting for package to appear in index, i is $i"; echo "sleeping 5s"; sleep 5s; echo "woken up"; ((i++)); echo "next i is $i"; done
pip install --index-url https://pypi.org/simple market_prices==${{ github.ref_name }}
python -c 'import market_prices;print(market_prices.__version__)'
6 changes: 3 additions & 3 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -107,9 +107,9 @@ celerybeat.pid

# Environments
.env
.venv
env/
venv/
.venv*/
env
venv*/
ENV/
env.bak/
venv.bak/
Expand Down
6 changes: 3 additions & 3 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,16 +4,16 @@ repos:
hooks:
- id: check-yaml
- repo: https://github.com/psf/black
rev: 22.3.0
rev: 23.7.0
hooks:
- id: black
# It is recommended to specify the latest version of Python
# supported by your project here, or alternatively use
# pre-commit's default_language_version, see
# https://pre-commit.com/#top_level-default_language_version
language_version: python3.8
language_version: python3.11
- repo: https://github.com/PyCQA/flake8
rev: 4.0.1
rev: 6.1.0
hooks:
- id: flake8
additional_dependencies: [flake8-docstrings]
4 changes: 2 additions & 2 deletions .pylintrc
Original file line number Diff line number Diff line change
Expand Up @@ -82,8 +82,8 @@ max-line-length=100
max-module-lines=2000

[TYPING]
py-version=3.8
runtime-typing=no
py-version=3.9
runtime-typing=True

[PARAMETER_DOCUMENTATION]
accept-no-param-doc=no
Expand Down
5 changes: 2 additions & 3 deletions docs/developers/typing_doc.md
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,9 @@ Errors arising for reasons listed in the comments towards the top fo the `mypy.i

### mptypes.py

Types specific to `market_prices` are defined on the `mptypes.py` module. These include
type aliases, custom pydantic types and internal enums.
Types specific to `market_prices` are defined on the `mptypes.py` module. These include type aliases, custom types and internal enums.

The annotation of any public parameter that takes an mptype should begin `mptypes.` in order to explictly declare the type as being specific to `market_prices`.
The type annotation of any public parameter that takes a type defined on the mptypes module should begin `mptypes.`. This is to explictly declare the type as being specific to `market_prices`.

## Documentation

Expand Down
56 changes: 33 additions & 23 deletions docs/public/parsing.md
Original file line number Diff line number Diff line change
@@ -1,34 +1,44 @@
# Parsing

`market_prices` uses the [pydantic library](https://pydantic-docs.helpmanual.io/) to parse parameters received by public functions and methods. Pydantic ensures that the type passed to a formal parameter conforms with the parameter's type annotation. Passing a object with an invalid type will raise a `pydantic.ValidationError` with a message advising of the invalid inputs and what was expected.
`market_prices` uses the [`valimp`](https://github.com/maread99/valimp) library to parse parameters received by public functions and methods.

## Type validation
The `valimp.parse` decorator ensures that the objects passed to a function's parameters conform with the corresponding type annotation. Where a parameter takes a container this validation extends to validating the type of the container items. For example, an input for the following parameter would be validated as being a `dict` and each item of that dictionary would be validated as having the key as a `str` and the value as either an `int` or a `float`:

```python
param: dict[str, Union[int, float]]
```

An instance of `valimp.InputsError` is raised if at least one object passed to a function does not confrom with the corresponding type annotation.

## Coercing

When a parameter receives an invalid type pydantic will try to coerce it to a valid type. For example, a parameter annoated with `int` could be passed a `str` "3" which would be coerced to the `int` 3. It could also be passed a `float` 2.99 which would be coerced the `int` 2!
An instance of `valimp.Coerce` in a parameter's annotation simply indicates that the object will
be subsequently coerced to a specific type. For example, the following 'start' parameter can take an object of type `pd.Timestamp`, `str`, `datetime.datetime`, `int`, or `float`. In all cases the object will be coerced to a `pd.Timestamp` (NB a None value is never coerced).

Parameters that do not allow coercing are typed with a pydantic 'Strict' type, for example `pydantic.StrictInt`.
```python
start: Annotated[
Union[pd.Timestamp, str, datetime.datetime, int, float, None],
Coerce(pd.Timestamp),
] = None,
```

(NB The type annotation is wrapped in `typing.Annotated` and the `valimp.Coerce` instance is passed to the annotated metadata.)

## Custom pydantic types
`market_prices` defines custom pydantic types for certain parameters. The parsing of custom types may perform additional validations and define default values.
## Ad-hoc validation

For example, the type `mptypes.PricesTimezone` is defined for parameters that allow a timezone to be specified by way of a symbol or `pytz` timezone object. The parsing process checks that the input is of a valid type and value and then passes through a pytz timezone object to the formal parameter.
An instance of `valimp.Parser` in the type annotation indicates that the input will be subsequently parsed before reaching the decorated function. This parsing may undertake further validation or dynamically assign a default value. For example, the following 'session' parameter will be coerced to a `pd.Timestamp` which in turn will be verified as representing a date (as opposed to a time) by the `parsing.verify_datetimestamp` function.

The type's documentation includes the requirements for input to be considered valid.
```python
>>> from market_prices.mptypes import DateTimestamp
>>> help(DateTimestamp)
session: Annotated[
Union[pd.Timestamp, str, datetime.datetime, int, float, None],
Coerce(pd.Timestamp),
Parser(parsing.verify_datetimestamp, parse_none=False),
] = None,
```
Help on class DateTimestamp in module market_prices.mptypes:

class DateTimestamp(Timestamp)
| Type to parse to a pd.Timestamp and validate as a date.
|
| Considered a valid date (rather than a time), if:
| - no time component or time component defined as 00:00.
| - tz-naive.
|
| A parameter annotated with this class can take any object that is
| acceptable as a single-argument input to pd.Timestamp:
| Union[pd.Timestamp, str, datetime.datetime, int, float]
|
| The formal parameter will be assigned a pd.Timestamp.

In this case if the input does not represent a date then `parsing.verify_datetimestamp` will raise an appropriate error (the parsing functions' documentation offer advices as to what's required for an input to be considered valid).

(NB The `parse_none` argument indicates to the `parse` decorator that a `None` value should not be parsed.)

(NB The type annotation is wrapped in `typing.Annotated` and the `valimp.Parser` instance is passed to the annotated metadata.)
16 changes: 7 additions & 9 deletions docs/public/typing.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,19 +5,17 @@ Third party types will usually be defined with a full dotted path from the packa

## mptypes

`market_prices` defines [type aliases](#Type-aliases) and [custom pydantic types](#Custom-pydantic-types) to annotate some parameters of public methods. Such types are all defined in the `mptypes.py` module.
`market_prices` defines [type aliases](#Type-aliases) and custom types to annotate some parameters of public methods. Such types are defined in the `mptypes.py` module.

When a parameter takes an mptype the underlying valid types are expressed in the 'Parameters' section of the method's documentation.
When a parameter takes an mptype the underlying valid types are expressed in the 'Parameters' section of the method's documentation.

### Type aliases
`market_prices` uses type aliases to represent multiple underlying types that are acceptable input. The underlying types can be inspected by calling the type alias:
`market_prices` occassionally uses type aliases to represent multiple underlying types that are acceptable input. The underlying types can be inspected by calling the type alias:

```python
>>> from market_prices.mptypes import Calendar
>>> from market_prices.mptypes import Symbols, Calendar
>>> Symbols
typing.Union[list[str], str]
>>> Calendar
typing.Union[pydantic.types.StrictStr, exchange_calendars.exchange_calendar.ExchangeCalendar]
typing.Union[str, exchange_calendars.exchange_calendar.ExchangeCalendar]
```

### Custom pydantic types

The [parsing](./parsing.md) documentation explains how custom pydantic types are sometimes used to validate and parse parameters.
6 changes: 3 additions & 3 deletions docs/tutorials/data_availability.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,10 @@
],
"source": [
"import pandas as pd\n",
"now = pd.Timestamp.now(tz=\"UTC\").floor(\"T\")\n",
"from zoneinfo import ZoneInfo\n",
"now = pd.Timestamp.now(tz=ZoneInfo(\"UTC\")).floor(\"T\")\n",
"print(f\"{now!r}\")\n",
"print(f\"{now.astimezone('America/New_York')!r}\")"
"print(f\"{now.astimezone(ZoneInfo('America/New_York'))!r}\")"
]
},
{
Expand All @@ -77,7 +78,6 @@
"outputs": [],
"source": [
"from market_prices import PricesYahoo, helpers\n",
"import pytz\n",
"from market_prices.support import tutorial_helpers as th"
]
},
Expand Down
12 changes: 6 additions & 6 deletions docs/tutorials/intervals.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,10 @@
],
"source": [
"import pandas as pd\n",
"now = pd.Timestamp.now(tz=\"UTC\").floor(\"T\")\n",
"from zoneinfo import ZoneInfo\n",
"now = pd.Timestamp.now(tz=ZoneInfo(\"UTC\")).floor(\"T\")\n",
"print(f\"{now!r}\")\n",
"print(f\"{now.astimezone('America/New_York')!r}\")"
"print(f\"{now.astimezone(ZoneInfo('America/New_York'))!r}\")"
]
},
{
Expand All @@ -72,7 +73,6 @@
"outputs": [],
"source": [
"from market_prices import PricesYahoo\n",
"import pytz\n",
"import pandas as pd\n",
"from market_prices.support import tutorial_helpers as th"
]
Expand Down Expand Up @@ -2658,9 +2658,9 @@
],
"metadata": {
"kernelspec": {
"display_name": "mkt_prices 3.8.2",
"display_name": "venv",
"language": "python",
"name": "mkt_prices"
"name": "python3"
},
"language_info": {
"codemirror_mode": {
Expand All @@ -2672,7 +2672,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.8.2"
"version": "3.8.10"
},
"widgets": {
"application/vnd.jupyter.widget-state+json": {
Expand Down
Loading