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

Teams deprecation #1081

Merged
merged 1 commit into from Mar 2, 2022
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.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
15 changes: 7 additions & 8 deletions docs/source/release-notes/3.2.0.rst
@@ -1,13 +1,12 @@
3.2.0: 2022-xx-xx
3.2.0: 2022-03-02
-----------------

Dependency Change
`````````````````

Bug Fixes
`````````

Features Added
``````````````
- Migrate to GitHub's supported Teams endpoints for select methods that were
relying on the deprecated endpoints. See also gh-1080_


Bug Fixes
`````````
.. _gh-1080:
https://github.com/sigmavirus24/github3.py/issues/1080
2 changes: 1 addition & 1 deletion src/github3/__about__.py
Expand Up @@ -5,7 +5,7 @@
__author_email__ = "graffatcolmingov@gmail.com"
__license__ = "Modified BSD"
__copyright__ = "Copyright 2012-2022 Ian Stapleton Cordasco"
__version__ = "3.1.2"
__version__ = "3.2.0"
__version_info__ = tuple(
int(i) for i in __version__.split(".") if i.isdigit()
)
Expand Down
28 changes: 18 additions & 10 deletions src/github3/models.py
@@ -1,16 +1,24 @@
"""This module provides the basic models used in github3.py."""
import json as jsonlib
import logging
import typing as t

import dateutil.parser
import requests.compat

from . import exceptions
from . import session


if t.TYPE_CHECKING:
from . import structs

LOG = logging.getLogger(__package__)


T = t.TypeVar("T")


class GitHubCore:
"""The base object for all objects that require a session.

Expand All @@ -20,9 +28,9 @@ class GitHubCore:
"""

_ratelimit_resource = "core"
_refresh_to = None
_refresh_to: t.Optional["GitHubCore"] = None

def __init__(self, json, session):
def __init__(self, json, session: session.GitHubSession):
"""Initialize our basic object.

Pretty much every object will pass in decoded JSON and a Session.
Expand Down Expand Up @@ -244,14 +252,14 @@ def _api(self, uri):

def _iter(
self,
count,
url,
cls,
params=None,
etag=None,
headers=None,
list_key=None,
):
count: int,
url: str,
cls: t.Type[T],
params: t.Optional[t.Mapping[str, t.Optional[str]]] = None,
etag: t.Optional[str] = None,
headers: t.Optional[t.Mapping[str, str]] = None,
list_key: t.Optional[str] = None,
) -> "structs.GitHubIterator[T]":
"""Generic iterator for this project.

:param int count: How many items to return.
Expand Down
46 changes: 34 additions & 12 deletions src/github3/orgs.py
Expand Up @@ -144,7 +144,7 @@ def edit(
bool
"""
if name:
data = {"name": name}
data: t.Dict[str, t.Union[str, int]] = {"name": name}
if permission:
data["permission"] = permission
if parent_team_id is not None:
Expand Down Expand Up @@ -478,7 +478,14 @@ def add_repository(self, repository, team_id): # FIXME(jlk): add perms
if int(team_id) < 0:
return False

url = self._build_url("teams", str(team_id), "repos", str(repository))
url = self._build_url(
"organizations",
str(self.id),
"team",
str(team_id),
"repos",
str(repository),
)
return self._boolean(self._put(url), 204, 404)

@requires_auth
Expand Down Expand Up @@ -736,10 +743,14 @@ def create_team(
:rtype:
:class:`~github3.orgs.Team`
"""
data = {
data: t.Dict[str, t.Union[t.List[str], str, int]] = {
"name": name,
"repo_names": [getattr(r, "full_name", r) for r in repo_names],
"maintainers": [getattr(m, "login", m) for m in maintainers],
"repo_names": [
getattr(r, "full_name", r) for r in (repo_names or [])
],
"maintainers": [
getattr(m, "login", m) for m in (maintainers or [])
],
"permission": permission,
"privacy": privacy,
}
Expand Down Expand Up @@ -1149,7 +1160,7 @@ def teams(self, number=-1, etag=None):
return self._iter(int(number), url, ShortTeam, etag=etag)

@requires_auth
def publicize_member(self, username):
def publicize_member(self, username: str) -> bool:
"""Make ``username``'s membership in this organization public.

:param str username:
Expand All @@ -1163,7 +1174,7 @@ def publicize_member(self, username):
return self._boolean(self._put(url), 204, 404)

@requires_auth
def remove_member(self, username):
def remove_member(self, username: str) -> bool:
"""Remove the user named ``username`` from this organization.

.. note::
Expand All @@ -1182,7 +1193,11 @@ def remove_member(self, username):
return self._boolean(self._delete(url), 204, 404)

@requires_auth
def remove_repository(self, repository, team_id):
def remove_repository(
self,
repository: t.Union[Repository, ShortRepository, str],
team_id: int,
):
"""Remove ``repository`` from the team with ``team_id``.

:param str repository:
Expand All @@ -1196,13 +1211,18 @@ def remove_repository(self, repository, team_id):
"""
if int(team_id) > 0:
url = self._build_url(
"teams", str(team_id), "repos", str(repository)
"organizations",
str(self.id),
"team",
str(team_id),
"repos",
str(repository),
)
return self._boolean(self._delete(url), 204, 404)
return False

@requires_auth
def team(self, team_id):
def team(self, team_id: int) -> t.Optional[Team]:
"""Return the team specified by ``team_id``.

:param int team_id:
Expand All @@ -1214,12 +1234,14 @@ def team(self, team_id):
"""
json = None
if int(team_id) > 0:
url = self._build_url("teams", str(team_id))
url = self._build_url(
"organizations", str(self.id), "team", str(team_id)
)
json = self._json(self._get(url), 200)
return self._instance_or_null(Team, json)

@requires_auth
def team_by_name(self, team_slug):
def team_by_name(self, team_slug: str) -> t.Optional[Team]:
"""Return the team specified by ``team_slug``.

:param str team_slug:
Expand Down
84 changes: 50 additions & 34 deletions src/github3/structs.py
@@ -1,66 +1,75 @@
import collections.abc as abc_collections
import collections.abc
import functools
import typing as t

from requests.compat import urlencode
from requests.compat import urlparse

from . import exceptions
from . import models

if t.TYPE_CHECKING:
import requests.models

class GitHubIterator(models.GitHubCore, abc_collections.Iterator):
from . import session


T = t.TypeVar("T")


class GitHubIterator(models.GitHubCore, collections.abc.Iterator):
"""The :class:`GitHubIterator` class powers all of the iter_* methods."""

def __init__(
self,
count,
url,
cls,
session,
params=None,
etag=None,
headers=None,
list_key=None,
):
count: int,
url: str,
cls: t.Type[T],
session: "session.GitHubSession",
params: t.Optional[t.Mapping[str, t.Optional[str]]] = None,
etag: t.Optional[str] = None,
headers: t.Optional[t.Mapping[str, str]] = None,
list_key: t.Optional[str] = None,
) -> None:
models.GitHubCore.__init__(self, {}, session)
#: Original number of items requested
self.original = count
self.original: t.Final[int] = count
#: Number of items left in the iterator
self.count = count
self.count: int = count
#: URL the class used to make it's first GET
self.url = url
self.url: str = url
#: Last URL that was requested
self.last_url = None
self._api = self.url
self.last_url: t.Optional[str] = None
self._api: str = self.url
#: Class for constructing an item to return
self.cls = cls
self.cls: t.Type[T] = cls
#: Parameters of the query string
self.params = params or {}
self.params: t.Mapping[str, t.Optional[str]] = params or {}
self._remove_none(self.params)
# We do not set this from the parameter sent. We want this to
# represent the ETag header returned by GitHub no matter what.
# If this is not None, then it won't be set from the response and
# that's not what we want.
#: The ETag Header value returned by GitHub
self.etag = None
self.etag: t.Optional[str] = None
#: Headers generated for the GET request
self.headers = headers or {}
self.headers: t.Dict[str, str] = dict(headers or {})
#: The last response seen
self.last_response = None
self.last_response: "requests.models.Response" = None
#: Last status code received
self.last_status = 0
self.last_status: int = 0
#: Key to get the list of items in case a dict is returned
self.list_key = list_key
self.list_key: t.Final[t.Optional[str]] = list_key

if etag:
self.headers.update({"If-None-Match": etag})

self.path = urlparse(self.url).path
self.path: str = urlparse(self.url).path

def _repr(self):
def _repr(self) -> str:
return f"<GitHubIterator [{self.count}, {self.path}]>"

def __iter__(self):
def __iter__(self) -> t.Generator[T, None, None]:
self.last_url, params = self.url, self.params
headers = self.headers

Expand Down Expand Up @@ -127,23 +136,23 @@ def __iter__(self):
rel_next = response.links.get("next", {})
self.last_url = rel_next.get("url", "")

def __next__(self):
def __next__(self) -> T:
if not hasattr(self, "__i__"):
self.__i__ = self.__iter__()
return next(self.__i__)

def _get_json(self, response):
def _get_json(self, response: "requests.models.Response"):
return self._json(response, 200)

def refresh(self, conditional=False):
def refresh(self, conditional: bool = False) -> "GitHubIterator":
self.count = self.original
if conditional:
if conditional and self.etag:
self.headers["If-None-Match"] = self.etag
self.etag = None
self.__i__ = self.__iter__()
return self

def next(self):
def next(self) -> T:
return self.__next__()


Expand All @@ -160,13 +169,20 @@ class SearchIterator(GitHubIterator):
_ratelimit_resource = "search"

def __init__(
self, count, url, cls, session, params=None, etag=None, headers=None
self,
count: int,
url: str,
cls: t.Type[T],
session: "session.GitHubSession",
params: t.Optional[t.Mapping[str, t.Optional[str]]] = None,
etag: t.Optional[str] = None,
headers: t.Optional[t.Mapping[str, str]] = None,
):
super().__init__(count, url, cls, session, params, etag, headers)
#: Total count returned by GitHub
self.total_count = 0
self.total_count: int = 0
#: Items array returned in the last request
self.items = []
self.items: t.List[t.Mapping[str, t.Any]] = []

def _repr(self):
return "<SearchIterator [{}, {}?{}]>".format(
Expand Down