Skip to content

Commit

Permalink
tests
Browse files Browse the repository at this point in the history
  • Loading branch information
tlambert03 committed Sep 29, 2023
1 parent 7241b6f commit ace3c76
Show file tree
Hide file tree
Showing 6 changed files with 172 additions and 100 deletions.
34 changes: 12 additions & 22 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,38 +12,27 @@ on:
# run every week (for --pre release tests)
- cron: "0 0 * * 0"

jobs:
check-manifest:
# check-manifest is a tool that checks that all files in version control are
# included in the sdist (unless explicitly excluded)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- run: pipx run check-manifest
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
test:
name: ${{ matrix.platform }} (${{ matrix.python-version }})
runs-on: ${{ matrix.platform }}
strategy:
fail-fast: false
matrix:
python-version: ["3.8", "3.9", "3.10", "3.11"]
platform: [ubuntu-latest, macos-latest, windows-latest]
python-version: ["3.8", "3.11"]
platform: [ubuntu-latest]

steps:
- name: 🛑 Cancel Previous Runs
uses: styfle/cancel-workflow-action@0.11.0
with:
access_token: ${{ github.token }}

- uses: actions/checkout@v3
- uses: actions/checkout@v4

- name: 🐍 Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v4
with:
python-version: ${{ matrix.python-version }}
cache-dependency-path: "pyproject.toml"
cache: "pip"

- name: Install Dependencies
run: |
Expand Down Expand Up @@ -76,9 +65,11 @@ jobs:
needs: test
if: success() && startsWith(github.ref, 'refs/tags/') && github.event_name != 'schedule'
runs-on: ubuntu-latest

permissions:
id-token: write
contents: write
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v4
with:
fetch-depth: 0

Expand All @@ -94,9 +85,8 @@ jobs:
- name: 🚢 Publish to PyPI
uses: pypa/gh-action-pypi-publish@release/v1
with:
password: ${{ secrets.TWINE_API_KEY }}

- uses: softprops/action-gh-release@v1
with:
generate_release_notes: true
files: "./dist/*"
3 changes: 2 additions & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -126,7 +126,8 @@ exclude_lines = [
"raise NotImplementedError()",
]
[tool.coverage.run]
source = ["src"]
source = ["pyconify"]
omit = ["src/pyconify/types.py"]

# https://github.com/mgedmin/check-manifest#configuration
# add files that you want check-manifest to explicitly ignore here
Expand Down
6 changes: 4 additions & 2 deletions src/pyconify/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

try:
__version__ = version("pyconify")
except PackageNotFoundError:
except PackageNotFoundError: # pragma: no cover
__version__ = "uninstalled"
__author__ = "Talley Lambert"
__email__ = "talley.lambert@gmail.com"
Expand All @@ -13,11 +13,12 @@
"collections",
"css",
"icon_data",
"iconify_version",
"keywords",
"last_modified",
"search",
"svg",
"temp_svg",
"iconify_version",
]

from .api import (
Expand All @@ -27,6 +28,7 @@
icon_data,
iconify_version,
keywords,
last_modified,
search,
svg,
temp_svg,
Expand Down
29 changes: 0 additions & 29 deletions src/pyconify/_util.py

This file was deleted.

118 changes: 74 additions & 44 deletions src/pyconify/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@
import atexit
import os
import tempfile
import warnings
from contextlib import suppress
from functools import lru_cache
from logging import warn
from typing import TYPE_CHECKING, Literal, Sequence
from typing import TYPE_CHECKING, Iterable, Literal, cast, overload

import requests

Expand All @@ -21,23 +21,48 @@
Rotation,
)

ROOT = "https://api.iconify.design"


@overload
def _split_prefix_name(
key: tuple[str, ...], allow_many: Literal[False] = ...
) -> tuple[str, str]:
...


@overload
def _split_prefix_name(
key: tuple[str, ...], allow_many: Literal[True]
) -> tuple[str, tuple[str, ...]]:
...


def _split_prefix_name(
key: tuple[str, ...], allow_many: bool = False
) -> tuple[str, str] | tuple[str, tuple[str, ...]]:
"""Convenience function to split prefix and name from key.
def _split_prefix_name(key: tuple[str, ...]) -> tuple[str, str]:
Examples
--------
>>> _split_prefix_name(("mdi", "account"))
("mdi", "account")
>>> _split_prefix_name(("mdi:account",))
("mdi", "account")
"""
if len(key) == 1:
if ":" in key[0]:
return tuple(key[0].split(":", maxsplit=1)) # type: ignore
else:
raise ValueError(
"If only one argument is passed, it must be in the format "
f"'prefix:name'. got {key[0]!r}"
"Single-argument icon names must be in the format 'prefix:name'. "
f"Got {key[0]!r}"
)
elif len(key) == 2:
return key # type: ignore
else:
raise ValueError("QIconify must be initialized with either 1 or 2 arguments.")


ROOT = "https://api.iconify.design"
return cast("tuple[str, str]", key)
elif not allow_many:
raise ValueError("icon key must be either 1 or 2 arguments.")
return key[0], key[1:]


@lru_cache(maxsize=None)
Expand All @@ -54,9 +79,9 @@ def collections(*prefixes: str) -> dict[str, IconifyInfo]:
end with "-", such as "mdi-" matches "mdi-light".
"""
query_params = {"prefixes": ",".join(prefixes)}
req = requests.get(f"{ROOT}/collections", params=query_params)
req.raise_for_status()
return req.json() # type: ignore
resp = requests.get(f"{ROOT}/collections", params=query_params)
resp.raise_for_status()
return resp.json() # type: ignore


@lru_cache(maxsize=None)
Expand All @@ -83,10 +108,10 @@ def collection(
query_params["chars"] = 1
if info:
query_params["info"] = 1
req = requests.get(f"{ROOT}/collection?prefix={prefix}", params=query_params)
req.raise_for_status()
if (content := req.json()) == 404:
raise ValueError(f"Icon set {prefix} not found.")
resp = requests.get(f"{ROOT}/collection?prefix={prefix}", params=query_params)
resp.raise_for_status()
if (content := resp.json()) == 404:
raise requests.HTTPError(f"Icon set {prefix!r} not found.", response=resp)
return content # type: ignore


Expand All @@ -103,9 +128,9 @@ def last_modified(*prefixes: str) -> APIv3LastModifiedResponse:
"""
# https://api.iconify.design/last-modified?prefixes=mdi,mdi-light,tabler
query_params = {"prefixes": ",".join(prefixes)}
req = requests.get(f"{ROOT}/last-modified", params=query_params)
req.raise_for_status()
return req.json() # type: ignore
resp = requests.get(f"{ROOT}/last-modified", params=query_params)
resp.raise_for_status()
return resp.json() # type: ignore


@lru_cache(maxsize=None)
Expand Down Expand Up @@ -163,9 +188,11 @@ def svg(
}
if box:
query_params["box"] = 1
req = requests.get(f"{ROOT}/{prefix}/{name}.svg", params=query_params)
req.raise_for_status()
return req.content
resp = requests.get(f"{ROOT}/{prefix}/{name}.svg", params=query_params)
resp.raise_for_status()
if resp.content == b"404":
raise requests.HTTPError(f"Icon '{prefix}:{name}' not found.", response=resp)
return resp.content


@lru_cache(maxsize=None)
Expand Down Expand Up @@ -194,7 +221,7 @@ def temp_svg(

@atexit.register
def _remove_tmp_svg() -> None:
with suppress(FileNotFoundError):
with suppress(FileNotFoundError): # pragma: no cover
os.remove(tmp_name)

return tmp_name
Expand All @@ -213,9 +240,9 @@ def css(prefix: str, *icons: str) -> str:
# format. Stylesheet formatting option. Matches options used in Sass. Supported values: "expanded", "compact", "compressed".

# /mdi.css?icons=account-box,account-cash,account,home
req = requests.get(f"{ROOT}/{prefix}.css?icons={','.join(icons)}")
req.raise_for_status()
return req.text
resp = requests.get(f"{ROOT}/{prefix}.css?icons={','.join(icons)}")
resp.raise_for_status()
return resp.text


def icon_data(prefix: str, *names: str) -> IconifyJSON:
Expand All @@ -233,18 +260,18 @@ def icon_data(prefix: str, *names: str) -> IconifyJSON:
names : str, optional
Icon name(s).
"""
req = requests.get(f"{ROOT}/{prefix}.json?icons={','.join(names)}")
req.raise_for_status()
if (content := req.json()) == 404:
raise requests.HTTPError(f"No data returned for {prefix!r}", response=req)
resp = requests.get(f"{ROOT}/{prefix}.json?icons={','.join(names)}")
resp.raise_for_status()
if (content := resp.json()) == 404:
raise requests.HTTPError(f"No data returned for {prefix!r}", response=resp)
return content # type: ignore


def search(
query: str,
limit: int | None = None,
start: int | None = None,
prefixes: Sequence[str] | None = None,
prefixes: Iterable[str] | None = None,
category: str | None = None,
# similar: bool | None = None,
) -> APIv2SearchResponse:
Expand Down Expand Up @@ -283,7 +310,7 @@ def search(
show.
start : int, optional
Start index for results, default is 0.
prefixes : str, optional
prefixes : str | Iterable[str], optional
List of icon set prefixes. You can use partial prefixes that
end with "-", such as "mdi-" matches "mdi-light".
category : str, optional
Expand All @@ -301,9 +328,9 @@ def search(
params["prefixes"] = ",".join(prefixes)
if category is not None:
params["category"] = category
req = requests.get(f"{ROOT}/search?query={query}", params=params)
req.raise_for_status()
return req.json() # type: ignore
resp = requests.get(f"{ROOT}/search?query={query}", params=params)
resp.raise_for_status()
return resp.json() # type: ignore


def keywords(
Expand All @@ -327,20 +354,23 @@ def keywords(
"""
if prefix:
if keyword:
warn("Both prefix and keyword specified. Ignoring keyword.")
warnings.warn(
"Cannot specify both prefix and keyword. Ignoring keyword.",
stacklevel=2,
)
params = {"prefix": prefix}
elif keyword:
params = {"keyword": keyword}
else:
params = {}
req = requests.get(f"{ROOT}/keywords", params=params)
req.raise_for_status()
return req.json() # type: ignore
resp = requests.get(f"{ROOT}/keywords", params=params)
resp.raise_for_status()
return resp.json() # type: ignore


@lru_cache(maxsize=None)
def iconify_version() -> str:
"""Return version of iconify API."""
req = requests.get(f"{ROOT}/version")
req.raise_for_status()
return req.text
resp = requests.get(f"{ROOT}/version")
resp.raise_for_status()
return resp.text
Loading

0 comments on commit ace3c76

Please sign in to comment.