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

Rewrite to use pdm and updated ruff #23

Merged
merged 1 commit into from
Dec 28, 2024
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
70 changes: 24 additions & 46 deletions .github/workflows/build.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -27,58 +27,36 @@ jobs:
with:
fetch-depth: 0

- name: "Load cached poetry installation @ ${{ matrix.python-version }}"
id: cached-poetry
uses: actions/cache@v4
- name: "Setup PDM @ ${{ matrix.python-version }}"
uses: pdm-project/setup-pdm@v4
with:
path: ~/.local
key: poetry-0
python-version: ${{matrix.python-version}}
cache: true

- name: "Setup Poetry @ ${{ matrix.python-version }}"
if: steps.cached-poetry.outputs.cache-hit != 'true'
uses: snok/install-poetry@v1
with:
version: latest
virtualenvs-create: true
virtualenvs-in-project: true
virtualenvs-path: ~/.venv

- name: "Setup Python @ ${{ matrix.python-version }}"
id: setup-python
uses: actions/setup-python@v5
with:
python-version: "${{ matrix.python-version }}"
cache: "poetry"

- name: "Load cached venv @ ${{ matrix.python-version }}"
id: cached-pip-wheels
uses: actions/cache@v4
with:
path: .venv/
key: venv-${{ runner.os }}-${{ steps.setup-python.outputs.python-version }}-${{ hashFiles('**/poetry.lock') }}

- name: "Install Python deps @ ${{ matrix.python-version }}"
if: ${{ steps.cached-pip-wheels.outputs.cache-hit != 'true' }}
id: install-deps
- name: "Install deps @ ${{ matrix.python-version }}"
run: |
poetry install --no-interaction
pdm install --prod --check --no-editable

- name: Activate venv @ ${{ matrix.python-version }}
- name: "Activate venv @ ${{ matrix.python-version }}"
run: |
echo "$(poetry env info --path)/bin" >> $GITHUB_PATH
echo "$(pdm info --where)/.venv/bin" >> $GITHUB_PATH

- name: "Check it imports @ ${{ matrix.python-version }}"
run: |
poetry run python -c 'import mystbin'
pdm run python -c 'import hondana'

- name: "Test Suite @ ${{ matrix.python-version }}"
run: |
pdm run tests

- name: "Build wheels @ ${{ matrix.python-version}}"
run: |
poetry build
pdm build

- name: "Build docs @ ${{ matrix.python-version}}"
working-directory: docs/
run: |
cd docs/
poetry run sphinx-build -aETW --keep-going . build
pdm run docs

- name: "Upload artifacts @ ${{ matrix.python-version}}"
if: ${{ matrix.python-version != '3.x' }}
Expand All @@ -94,6 +72,9 @@ jobs:
needs: [build]
runs-on: ubuntu-latest
if: github.event_name == 'push' && startsWith(github.event.ref, 'refs/tags/')
permissions:
contents: read
id-token: write

steps:
- uses: actions/checkout@v4
Expand All @@ -120,15 +101,12 @@ jobs:
tag_name="${GITHUB_REF##*/}"
gh release create "$tag_name" -F "CHANGELOG.md" "${assets[@]}"

- name: "Set up Poetry"
uses: snok/install-poetry@v1
- name: Set up PDM
uses: pdm-project/setup-pdm@v4
with:
virtualenvs-create: true
virtualenvs-in-project: false
python-version: 3.8
cache: true

- name: Publish to PyPI
env:
PYPI_TOKEN: ${{ secrets.PYPI_TOKEN }}
run: |
poetry config pypi-token.pypi $PYPI_TOKEN
poetry publish
pdm publish
54 changes: 16 additions & 38 deletions .github/workflows/coverage_and_lint.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -24,54 +24,32 @@ jobs:
with:
fetch-depth: 0

- name: "Load cached poetry installation @ ${{ matrix.python-version }}"
id: cached-poetry
uses: actions/cache@v4
- name: Setup PDM @ ${{ matrix.python-version }}
uses: pdm-project/setup-pdm@v4
with:
path: ~/.local
key: poetry-0
python-version: ${{ matrix.python-version }}
cache: true

- name: "Setup Poetry @ ${{ matrix.python-version }}"
if: steps.cached-poetry.outputs.cache-hit != 'true'
uses: snok/install-poetry@v1
with:
version: latest
virtualenvs-create: true
virtualenvs-in-project: true
virtualenvs-path: .venv

- name: "Setup Python @ ${{ matrix.python-version }}"
id: setup-python
uses: actions/setup-python@v5
with:
python-version: "${{ matrix.python-version }}"
cache: "poetry"

- name: "Load cached venv @ ${{ matrix.python-version }}"
id: cached-pip-wheels
uses: actions/cache@v4
with:
path: ~/.venv/
key: venv-${{ runner.os }}-${{ steps.setup-python.outputs.python-version }}-${{ hashFiles('**/poetry.lock') }}

- name: "Install Python deps @ ${{ matrix.python-version }}"
if: ${{ steps.cached-pip-wheels.outputs.cache-hit != 'true' }}
id: install-deps
- name: Install deps @ ${{ matrix.python-version }}
run: |
poetry install --all-extras -n --no-cache --no-interaction
pdm install --check --no-editable

- name: Activate venv @ ${{ matrix.python-version }}
run: |
echo "$(poetry env info --path)/bin" >> $GITHUB_PATH
echo "$(pdm info --where)/.venv/bin" >> $GITHUB_PATH

- name: "Run Pyright @ ${{ matrix.python-version }}"
uses: jakebailey/pyright-action@v2
with:
warnings: false
verify-types: "mystbin"
ignore-external: true
no-comments: ${{ matrix.python-version != '3.x' }}
annotate: "${{ matrix.python-version != '3.x' }}"

- name: Lint check
uses: astral-sh/ruff-action@v2
with:
args: check .

- name: Lint
if: ${{ always() && steps.install-deps.outcome == 'success' }}
- name: Formatting check
uses: chartboost/ruff-action@v1
with:
args: format --check
10 changes: 8 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
.vscode/
*.py[cod]
.venv/
.env
venv/
.ruff_cache/
.pdm-python

.vscode/
.idea/

docs/build
dist/
.ruff_cache/
.pdm-build/
4 changes: 2 additions & 2 deletions mystbin/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@
DEALINGS IN THE SOFTWARE.
"""

__version__ = "7.0.2"
__version__ = "7.1.0"

from typing import Literal, NamedTuple

Expand All @@ -39,6 +39,6 @@ class VersionInfo(NamedTuple):
serial: int


version_info: VersionInfo = VersionInfo(major=7, minor=0, micro=2, releaselevel="final", serial=0)
version_info: VersionInfo = VersionInfo(major=7, minor=1, micro=0, releaselevel="final", serial=0)

del NamedTuple, Literal, VersionInfo
11 changes: 7 additions & 4 deletions mystbin/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,19 +48,22 @@ class Client:
If not provided, a new session will be created.
api_base: :class:`str`
The base URL for the mystbin instance.
Defaults to ``https://mystb.in/``.
This should begin with ``https://`` and should be the root URL of the mystbin instance.
Defaults to ``mystb.in``.
"""

__slots__ = ("http",)

def __init__(self, *, session: ClientSession | None = None, api_base: str = "https://mystb.in/") -> None:
def __init__(self, *, session: ClientSession | None = None, api_base: str = "mystb.in") -> None:
self.http: HTTPClient = HTTPClient(session=session, api_base=api_base)

async def __aenter__(self) -> Self:
return self

async def __aexit__(
self, exc_cls: type[BaseException] | None, exc_value: BaseException | None, traceback: TracebackType | None
self,
exc_cls: type[BaseException] | None,
exc_value: BaseException | None,
traceback: TracebackType | None,
) -> None:
await self.close()

Expand Down
43 changes: 22 additions & 21 deletions mystbin/http.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
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.
"""
""" # noqa: A005 # we access this via namespace

from __future__ import annotations

Expand Down Expand Up @@ -52,7 +52,6 @@
T = TypeVar("T")
Response = Coroutine[None, None, T]
MU = TypeVar("MU", bound="MaybeUnlock")
BE = TypeVar("BE", bound=BaseException)
from .types.responses import CreatePasteResponse, GetPasteResponse


Expand All @@ -69,8 +68,14 @@ def _clean_dt(dt: datetime.datetime) -> str:
return dt.isoformat()


async def json_or_text(response: aiohttp.ClientResponse, /) -> dict[str, Any] | str:
"""A quick method to parse a `aiohttp.ClientResponse` and test if it's json or text."""
async def _json_or_text(response: aiohttp.ClientResponse, /) -> dict[str, Any] | str:
"""A quick method to parse a `aiohttp.ClientResponse` and test if it's json or text.

Returns
--------
Union[Dict[:class:`str`, Any], :class:`str`]
The JSON object, or request text.
"""
text = await response.text(encoding="utf-8")
try:
if response.headers["content-type"] == "application/json":
Expand All @@ -97,8 +102,8 @@ def defer(self) -> None:

def __exit__(
self,
exc_type: type[BE] | None,
exc: BE | None,
exc_type: type[BaseException] | None,
exc: BaseException | None,
traceback: TracebackType | None,
) -> None:
if self._unlock:
Expand All @@ -115,14 +120,14 @@ class Route:
API_BASE: ClassVar[str] = "https://mystb.in/api"

def __init__(self, verb: SupportedHTTPVerb, path: str, **params: Any) -> None:

self.verb: SupportedHTTPVerb = verb
self.path: str = path
url = self.API_BASE + path
if params:
url = url.format_map({k: _uriquote(v) if isinstance(v, str) else v for k, v in params.items()})
self.url: str = url


class HTTPClient:
__slots__ = (
"_async",
Expand All @@ -131,7 +136,7 @@ class HTTPClient:
"_session",
"_token",
"api_base",
"user_agent"
"user_agent",
)

def __init__(self, *, session: aiohttp.ClientSession | None = None, api_base: str | None = None) -> None:
Expand All @@ -142,12 +147,9 @@ def __init__(self, *, session: aiohttp.ClientSession | None = None, api_base: st
self.user_agent: str = user_agent.format(__version__, sys.version_info, aiohttp.__version__)
self._resolve_api(api_base)

def _resolve_api(self, api: str | None) -> None:
if api:
Route.API_BASE = api + "api" if api.endswith("/") else api + "/api"
self.api_base = api + ("/" if not api.endswith("/") else "")
else:
self.api_base = "https://mystb.in/"
def _resolve_api(self, api_base: str | None, /) -> None:
if api_base:
Route.API_BASE = f"https://{api_base}/api"

async def close(self) -> None:
if self._session and self._owns_session:
Expand Down Expand Up @@ -194,28 +196,28 @@ async def request(self, route: Route, **kwargs: Any) -> Any:
retry = response.headers.get("x-ratelimit-retry-after", None)
LOGGER.debug("retry is: %s", retry)
if retry is not None:
retry = datetime.datetime.fromtimestamp(int(retry))
retry = datetime.datetime.fromtimestamp(int(retry), tz=datetime.timezone.utc)
# The total ratelimit session hits
limit = response.headers.get("x-ratelimit-limit", None)
LOGGER.debug("limit is: %s", limit)

if remaining == "0" and response.status != 429:
assert retry is not None
delta = retry - datetime.datetime.now()
delta = retry - datetime.datetime.now(datetime.timezone.utc)
sleep = delta.total_seconds() + 1
LOGGER.warning("A ratelimit has been exhausted, sleeping for: %d", sleep)
maybe_lock.defer()
loop = asyncio.get_running_loop()
loop.call_later(sleep, lock.release)

data = await json_or_text(response)
data = await _json_or_text(response)

if 300 > response.status >= 200:
return data

if response.status == 429:
assert retry is not None
delta = retry - datetime.datetime.now()
delta = retry - datetime.datetime.now(datetime.timezone.utc)
sleep = delta.total_seconds() + 1
LOGGER.warning("A ratelimit has been hit, sleeping for: %d", sleep)
await asyncio.sleep(sleep)
Expand All @@ -227,15 +229,14 @@ async def request(self, route: Route, **kwargs: Any) -> Any:
await asyncio.sleep(sleep_)
continue

print(data)
assert isinstance(data, dict)
LOGGER.exception("Unhandled HTTP error occurred: %s -> %s", response.status, data)
raise APIException(
response=response,
status_code=response.status,
)
except (aiohttp.ServerDisconnectedError, aiohttp.ServerTimeoutError) as error:
LOGGER.exception("Network error occurred: %s", error)
except (aiohttp.ServerDisconnectedError, aiohttp.ServerTimeoutError):
LOGGER.exception("Network error occurred:")
await asyncio.sleep(5)
continue

Expand Down
Loading
Loading