Skip to content

Commit

Permalink
Merge branch 'main' into include-pull-request-diffs
Browse files Browse the repository at this point in the history
  • Loading branch information
edgarrmondragon authored Jan 25, 2025
2 parents 43cabe0 + 78d490f commit 6d37306
Show file tree
Hide file tree
Showing 22 changed files with 849 additions and 925 deletions.
44 changes: 44 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
name: Build and Publish Python Package

on:
push:

permissions:
contents: write
id-token: write

jobs:
build:
name: Build wheel and sdist
runs-on: ubuntu-latest
outputs:
version: ${{ steps.baipp.outputs.package_version }}
steps:
- uses: actions/checkout@v4
- uses: hynek/build-and-inspect-python-package@v2
id: baipp

publish:
name: Publish to PyPI
runs-on: ubuntu-latest
needs: [build]
environment:
name: pypi
url: https://pypi.org/project/meltanolabs-tap-github/${{ needs.build.outputs.version }}
if: startsWith(github.ref, 'refs/tags/')

steps:
- uses: actions/download-artifact@v4
with:
name: Packages
path: dist
- name: Upload wheel to release
uses: svenstaro/upload-release-action@v2
with:
repo_token: ${{ secrets.GITHUB_TOKEN }}
file: dist/*.whl
tag: ${{ github.ref }}
overwrite: true
file_glob: true
- name: Deploy to PyPI
uses: pypa/gh-action-pypi-publish@v1.12.3
4 changes: 2 additions & 2 deletions .github/workflows/test_tap.yml
Original file line number Diff line number Diff line change
Expand Up @@ -34,11 +34,11 @@ jobs:
strategy:
matrix:
python-version:
- "3.13"
- "3.12"
- "3.11"
- "3.10"
- "3.9"
- "3.8"
# run the matrix jobs one after the other so they can benefit from caching
max-parallel: 1

Expand All @@ -64,7 +64,7 @@ jobs:
uses: snok/install-poetry@v1
with:
# Version of Poetry to use
version: 1.8.4
version: 1.8.5
virtualenvs-create: true
virtualenvs-in-project: true
- name: Set up Python ${{ matrix.python-version }}
Expand Down
4 changes: 2 additions & 2 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,14 +16,14 @@ repos:


- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.8.1
rev: v0.8.6
hooks:
- id: ruff
args: [ --fix ]
- id: ruff-format

- repo: https://github.com/pre-commit/mirrors-mypy
rev: v1.13.0
rev: v1.14.1
hooks:
- id: mypy
pass_filenames: true
Expand Down
2 changes: 1 addition & 1 deletion .sonarcloud.properties
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
sonar.python.version=3.7, 3.8, 3.9, 3.10
sonar.python.version=3.9, 3.10, 3.11, 3.12, 3.13
sonar.cpd.exclusions=**/*
2 changes: 2 additions & 0 deletions meltano.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
version: 1
send_anonymous_usage_stats: false
project_id: 96584f7b-a36c-46e0-b41a-7f9074293137
venv:
backend: uv
plugins:
extractors:
- name: tap-github
Expand Down
1,069 changes: 410 additions & 659 deletions poetry.lock

Large diffs are not rendered by default.

59 changes: 40 additions & 19 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,40 +1,48 @@
[tool.poetry]
name = "tap-github"
version = "1.4.2"
description = "`tap-github` is Singer tap for GitHub, built with the Singer SDK."
authors = ["Meltano and Meltano Community"]
name = "meltanolabs-tap-github"
version = "1.10.0"
description = "Singer tap for GitHub, built with the Singer SDK."
authors = ["Meltano and Meltano Community <hello@meltano.com>"]
maintainers = [
"Meltano and Meltano Community <hello@meltano.com>",
"Edgar Ramírez-Mondragón <edgarrm358@gmail.com>",
]
homepage = "https://github.com/MeltanoLabs/tap-github"
repository = "https://github.com/MeltanoLabs/tap-github"
license = "Apache 2.0"
license = "Apache-2.0"
keywords = ["Meltano", "Singer", "Meltano SDK", "Singer SDK", "ELT", "Github"]
readme = "README.md"
classifiers = [
"Intended Audience :: Developers",
"License :: OSI Approved :: Apache Software License",
"Operating System :: OS Independent",
"Programming Language :: Python :: 3.8",
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Programming Language :: Python :: Implementation :: CPython",
"Typing :: Typed",
]
packages = [
{ include = "tap_github", format = ["sdist", "wheel"] }
]

[tool.poetry.urls]
"Issue Tracker" = "https://github.com/MeltanoLabs/tap-github/issues"

[tool.poetry.dependencies]
beautifulsoup4 = "~=4.12.0"
nested-lookup = "~=0.2.25"
PyJWT = "2.9.0"
python = ">=3.8"
PyJWT = "2.10.1"
python = ">=3.9"
python-dateutil = "~=2.9"
requests = "~=2.32.3"
# For local SDK dev:
# singer-sdk = {path = "../singer-sdk", develop = true}
singer-sdk = "~=0.35.0"
singer-sdk = "~=0.43.1"

[tool.poetry.dev-dependencies]
[tool.poetry.group.dev.dependencies]
pytest = ">=7.3.1"
requests-cache = ">=1.0.1"
types-beautifulsoup4 = ">=4.12.0"
Expand All @@ -61,17 +69,30 @@ markers = [
]

[tool.ruff]
target-version = "py38"
target-version = "py39"

[tool.ruff.lint]
ignore = []
select = [
"F", # Pyflakes
"E", # pycodestyle (errors)
"W", # pycodestyle (warnings)
"I", # isort
"UP", # pyupgrade
"FA", # flake8-future-annotations
"SIM", # flake8-simplify
"RUF", # Ruff-specific rules
"F", # Pyflakes
"E", # pycodestyle (errors)
"W", # pycodestyle (warnings)
"I", # isort
"N", # pep8-naming
"UP", # pyupgrade
"YTT", # flake8-2020
"ANN", # flake8-annotations
"B", # flake8-bugbear
"A", # flake8-builtins
"C4", # flake8-comprehensions
"DTZ", # flake8-datetimez
"FA", # flake8-future-annotations
"SIM", # flake8-simplify
"TC", # flake8-type-checking
"PERF", # Perflint
"FURB", # refurb
"RUF", # Ruff-specific rules
]

[tool.ruff.lint.per-file-ignores]
"tap_github/tests/*" = ["ANN"]
14 changes: 0 additions & 14 deletions tap-github.sh

This file was deleted.

29 changes: 18 additions & 11 deletions tap_github/authenticator.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,14 @@
from datetime import datetime, timedelta, timezone
from os import environ
from random import choice, shuffle
from typing import Any
from typing import TYPE_CHECKING, Any

import jwt
import requests
from singer_sdk.authenticators import APIAuthenticatorBase
from singer_sdk.streams import RESTStream

if TYPE_CHECKING:
from singer_sdk.streams import RESTStream


class TokenManager:
Expand All @@ -31,8 +33,8 @@ def __init__(
self,
token: str | None,
rate_limit_buffer: int | None = None,
logger: Any | None = None,
):
logger: Any | None = None, # noqa: ANN401
) -> None:
"""Init TokenManager info."""
self.token = token
self.logger = logger
Expand All @@ -46,7 +48,7 @@ def __init__(
else self.DEFAULT_RATE_LIMIT_BUFFER
)

def update_rate_limit(self, response_headers: Any) -> None:
def update_rate_limit(self, response_headers: Any) -> None: # noqa: ANN401
self.rate_limit = int(response_headers["X-RateLimit-Limit"])
self.rate_limit_remaining = int(response_headers["X-RateLimit-Remaining"])
self.rate_limit_reset = datetime.fromtimestamp(
Expand Down Expand Up @@ -95,7 +97,12 @@ def has_calls_remaining(self) -> bool:
class PersonalTokenManager(TokenManager):
"""A class to store token rate limiting information."""

def __init__(self, token: str, rate_limit_buffer: int | None = None, **kwargs):
def __init__(
self,
token: str,
rate_limit_buffer: int | None = None,
**kwargs, # noqa: ANN003
) -> None:
"""Init PersonalTokenRateLimit info."""
super().__init__(token, rate_limit_buffer=rate_limit_buffer, **kwargs)

Expand Down Expand Up @@ -164,8 +171,8 @@ def __init__(
env_key: str,
rate_limit_buffer: int | None = None,
expiry_time_buffer: int | None = None,
**kwargs,
):
**kwargs, # noqa: ANN003
) -> None:
if rate_limit_buffer is None:
rate_limit_buffer = self.DEFAULT_RATE_LIMIT_BUFFER
super().__init__(None, rate_limit_buffer=rate_limit_buffer, **kwargs)
Expand All @@ -182,7 +189,7 @@ def __init__(
self.token_expires_at: datetime | None = None
self.claim_token()

def claim_token(self):
def claim_token(self) -> None:
"""Updates the TokenManager's token and token_expires_at attributes.
The outcome will be _either_ that self.token is updated to a newly claimed valid token and
Expand Down Expand Up @@ -240,7 +247,7 @@ class GitHubTokenAuthenticator(APIAuthenticatorBase):
"""Base class for offloading API auth."""

@staticmethod
def get_env():
def get_env(): # noqa: ANN205
return dict(environ)

def prepare_tokens(self) -> list[TokenManager]:
Expand Down Expand Up @@ -307,7 +314,7 @@ def prepare_tokens(self) -> list[TokenManager]:
)
if app_token_manager.is_valid_token():
app_token_managers.append(app_token_manager)
except ValueError as e:
except ValueError as e: # noqa: PERF203
self.logger.warning(
f"An error was thrown while preparing an app token: {e}"
)
Expand Down
34 changes: 23 additions & 11 deletions tap_github/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,9 @@
import random
import time
from types import FrameType
from typing import Any, ClassVar, Iterable, cast
from typing import TYPE_CHECKING, Any, ClassVar, cast
from urllib.parse import parse_qs, urlparse

import requests
from backoff.types import Details
from dateutil.parser import parse
from nested_lookup import nested_lookup
from singer_sdk.exceptions import FatalAPIError, RetriableAPIError
Expand All @@ -20,6 +18,12 @@

from tap_github.authenticator import GitHubTokenAuthenticator

if TYPE_CHECKING:
from collections.abc import Iterable

import requests
from backoff.types import Details

EMPTY_REPO_ERROR_STATUS = 409


Expand Down Expand Up @@ -61,8 +65,10 @@ def http_headers(self) -> dict[str, str]:
return headers

def get_next_page_token(
self, response: requests.Response, previous_token: Any | None
) -> Any | None:
self,
response: requests.Response,
previous_token: Any | None, # noqa: ANN401
) -> Any | None: # noqa: ANN401
"""Return a token for identifying next page or None if no more pages."""
if (
previous_token
Expand Down Expand Up @@ -132,7 +138,9 @@ def get_next_page_token(
return (previous_token or 1) + 1

def get_url_params(
self, context: dict | None, next_page_token: Any | None
self,
context: dict | None,
next_page_token: Any | None, # noqa: ANN401
) -> dict[str, Any]:
"""Return a dictionary of values to be used in URL parameterization."""
params: dict = {"per_page": self.MAX_PER_PAGE}
Expand Down Expand Up @@ -324,8 +332,10 @@ def parse_response(self, response: requests.Response) -> Iterable[dict]:
yield from extract_jsonpath(self.query_jsonpath, input=resp_json)

def get_next_page_token(
self, response: requests.Response, previous_token: Any | None
) -> Any | None:
self,
response: requests.Response,
previous_token: Any | None, # noqa: ANN401
) -> Any | None: # noqa: ANN401
"""
Return a dict of cursors for identifying next page or None if no more pages.
Expand Down Expand Up @@ -366,7 +376,7 @@ def get_next_page_token(

# We leverage previous_token to remember the pagination cursors
# for indices below max_pagination_index.
next_page_cursors: dict[str, str] = dict()
next_page_cursors: dict[str, str] = {}
for key, value in (previous_token or {}).items():
# Only keep pagination info for indices below max_pagination_index.
pagination_index = int(str(key).split("_")[1])
Expand All @@ -388,10 +398,12 @@ def get_next_page_token(
return next_page_cursors

def get_url_params(
self, context: dict | None, next_page_token: Any | None
self,
context: dict | None,
next_page_token: Any | None, # noqa: ANN401
) -> dict[str, Any]:
"""Return a dictionary of values to be used in URL parameterization."""
params = context.copy() if context else dict()
params = context.copy() if context else {}
params["per_page"] = self.MAX_PER_PAGE
if next_page_token:
params.update(next_page_token)
Expand Down
Loading

0 comments on commit 6d37306

Please sign in to comment.