Repository: yanyongyu/githubkit (language: python)

Issue:
Underlying HTTPX clients are shared among several GitHub instances
I discovered a strange behavior while testing out version [v0.11.0a2](https://github.com/yanyongyu/githubkit/releases/tag/v0.11.0a2). It seems that if you create several `GitHub` client in the same process/thread, they reuse the underlying HTTPX client, including the auth flow.

It's a problem if you need to have several clients with different kind of authentications running in your process.

## Reproducible example

```py
from githubkit import GitHub, TokenAuthStrategy, UnauthAuthStrategy


def multiple_clients() -> None:
    github_unauth = GitHub(UnauthAuthStrategy())
    response_unauth = github_unauth.rest.rate_limit.get()
    authorization_unauth = response_unauth.headers.get("authorization")
    print(authorization_unauth)  # None, as expected

    github_token = GitHub(TokenAuthStrategy("MY_TOKEN"))
    response_token = github_token.rest.rate_limit.get()
    authorization_token = response_token.headers.get("authorization")
    print(authorization_token)  # Also None, but should be `token MY TOKEN`


if __name__ == "__main__":
    multiple_clients()

```

## Additional context

I suspect it's because `ContextVar` is used to retain the HTTPX client:

https://github.com/yanyongyu/githubkit/blob/8a381cba793b1f0a3d4ea0aee816336f40f7331f/githubkit/core.py#L147-L152

---

However, what I can't explain is that the **same code works correctly in [version 0.10.7](https://github.com/yanyongyu/githubkit/releases/tag/v0.10.7)**, while `ContextVar` were already there. Maybe a side effect of https://github.com/yanyongyu/githubkit/commit/d005552425a75e2312fec5ba77f446fed1063d29?


Discussion / hints:
I can reproduce. I will look into this problem.
The problem also happens when `http_cache=False`, and the clients used to request api are not the same.

It seems the `TokenAuthStrategy` auth flow is not called.
it is caused by namespace caching in https://github.com/yanyongyu/githubkit/blob/8a381cba793b1f0a3d4ea0aee816336f40f7331f/githubkit/versions/rest.py#L54-L55

The cache is used without considering the github instance 😂 
Oh, that's a nasty one! Glad you were able to track it down 😉 

Fix patch:
diff --git a/codegen/templates/rest/__init__.py.jinja b/codegen/templates/rest/__init__.py.jinja
index fe0e53023..70e7680de 100644
--- a/codegen/templates/rest/__init__.py.jinja
+++ b/codegen/templates/rest/__init__.py.jinja
@@ -2,6 +2,7 @@
 
 """{{ header() }}"""
 
+from weakref import ref
 from typing import TYPE_CHECKING
 from functools import cached_property
 
@@ -14,7 +15,16 @@ if TYPE_CHECKING:
 
 class RestNamespace:
     def __init__(self, github: "GitHubCore"):
-        self._github = github
+        self._github_ref = ref(github)
+
+    @property
+    def _github(self) -> "GitHubCore":
+        if g := self._github_ref():
+            return g
+        raise RuntimeError(
+            "GitHub client has already been collected. "
+            "Do not use this namespace after the client has been collected."
+        )
 
     {% for tag in tags %}
     @cached_property
diff --git a/codegen/templates/rest/client.py.jinja b/codegen/templates/rest/client.py.jinja
index 639aee2dd..0d2acdf9b 100644
--- a/codegen/templates/rest/client.py.jinja
+++ b/codegen/templates/rest/client.py.jinja
@@ -8,6 +8,7 @@
 
 from __future__ import annotations
 
+from weakref import ref
 from typing_extensions import Annotated
 from typing import TYPE_CHECKING, Dict, Literal, Optional, overload
 
@@ -56,7 +57,16 @@ class {{ pascal_case(tag) }}Client:
     _REST_API_VERSION = "{{ rest_api_version }}"
 
     def __init__(self, github: GitHubCore):
-        self._github = github
+        self._github_ref = ref(github)
+
+    @property
+    def _github(self) -> GitHubCore:
+        if g := self._github_ref():
+            return g
+        raise RuntimeError(
+            "GitHub client has already been collected. "
+            "Do not use this client after the client has been collected."
+        )
 
     {% for endpoint in endpoints %}
     {% if endpoint.request_body and endpoint.request_body.allowed_models %}
diff --git a/codegen/templates/versions/rest.py.jinja b/codegen/templates/versions/rest.py.jinja
index 2a5f5f7c5..ccb726c32 100644
--- a/codegen/templates/versions/rest.py.jinja
+++ b/codegen/templates/versions/rest.py.jinja
@@ -3,6 +3,7 @@
 """{{ header() }}"""
 
 import importlib
+from weakref import WeakKeyDictionary, ref
 from typing import TYPE_CHECKING, Any, Dict, Literal, overload
 
 from . import VERSIONS, LATEST_VERSION, VERSION_TYPE
@@ -22,7 +23,7 @@ else:
     _VersionProxy = object
 
 class RestVersionSwitcher(_VersionProxy):
-    _cached_namespaces: Dict[VERSION_TYPE, Any] = {}
+    _cached_namespaces: "WeakKeyDictionary[GitHubCore, Dict[VERSION_TYPE, Any]]" = WeakKeyDictionary()
 
     if not TYPE_CHECKING:
         def __getattr__(self, name: str) -> Any:
@@ -34,7 +35,16 @@ class RestVersionSwitcher(_VersionProxy):
             return getattr(namespace, name)
 
     def __init__(self, github: "GitHubCore"):
-        self._github = github
+        self._github_ref = ref(github)
+
+    @property
+    def _github(self) -> "GitHubCore":
+        if g := self._github_ref():
+            return g
+        raise RuntimeError(
+            "GitHub client has already been collected. "
+            "Do not use the namespace after the client has been collected."
+        )
 
     {% for version, module in versions.items() %}
     @overload
@@ -47,9 +57,11 @@ class RestVersionSwitcher(_VersionProxy):
         ...
 
     def __call__(self, version: VERSION_TYPE = LATEST_VERSION) -> Any:
-        if version in self._cached_namespaces:
-            return self._cached_namespaces[version]
+        g = self._github
+        cache = self._cached_namespaces.setdefault(g, {})
+        if version in cache:
+            return cache[version]
         module = importlib.import_module(f".{VERSIONS[version]}.rest", __package__)
-        namespace = module.RestNamespace(self._github)
-        self._cached_namespaces[version] = namespace
+        namespace = module.RestNamespace(g)
+        cache[version] = namespace
         return namespace
diff --git a/githubkit/versions/rest.py b/githubkit/versions/rest.py
index 2f69f36ea..5c927c63b 100644
--- a/githubkit/versions/rest.py
+++ b/githubkit/versions/rest.py
@@ -8,6 +8,7 @@
 """
 
 import importlib
+from weakref import WeakKeyDictionary, ref
 from typing import TYPE_CHECKING, Any, Dict, Literal, overload
 
 from . import VERSIONS, VERSION_TYPE, LATEST_VERSION
@@ -27,7 +28,9 @@ class _VersionProxy(V20221128RestNamespace):
 
 
 class RestVersionSwitcher(_VersionProxy):
-    _cached_namespaces: Dict[VERSION_TYPE, Any] = {}
+    _cached_namespaces: "WeakKeyDictionary[GitHubCore, Dict[VERSION_TYPE, Any]]" = (
+        WeakKeyDictionary()
+    )
 
     if not TYPE_CHECKING:
 
@@ -40,7 +43,16 @@ def __getattr__(self, name: str) -> Any:
             return getattr(namespace, name)
 
     def __init__(self, github: "GitHubCore"):
-        self._github = github
+        self._github_ref = ref(github)
+
+    @property
+    def _github(self) -> "GitHubCore":
+        if g := self._github_ref():
+            return g
+        raise RuntimeError(
+            "GitHub client has already been collected. "
+            "Do not use the namespace after the client has been collected."
+        )
 
     @overload
     def __call__(self, version: Literal["2022-11-28"]) -> "V20221128RestNamespace":
@@ -51,9 +63,11 @@ def __call__(self) -> "V20221128RestNamespace":
         ...
 
     def __call__(self, version: VERSION_TYPE = LATEST_VERSION) -> Any:
-        if version in self._cached_namespaces:
-            return self._cached_namespaces[version]
+        g = self._github
+        cache = self._cached_namespaces.setdefault(g, {})
+        if version in cache:
+            return cache[version]
         module = importlib.import_module(f".{VERSIONS[version]}.rest", __package__)
-        namespace = module.RestNamespace(self._github)
-        self._cached_namespaces[version] = namespace
+        namespace = module.RestNamespace(g)
+        cache[version] = namespace
         return namespace
diff --git a/githubkit/versions/v2022_11_28/rest/__init__.py b/githubkit/versions/v2022_11_28/rest/__init__.py
index fc78ee1d2..ed7013312 100644
--- a/githubkit/versions/v2022_11_28/rest/__init__.py
+++ b/githubkit/versions/v2022_11_28/rest/__init__.py
@@ -7,6 +7,7 @@
 See https://github.com/github/rest-api-description for more information.
 """
 
+from weakref import ref
 from typing import TYPE_CHECKING
 from functools import cached_property
 
@@ -52,7 +53,16 @@
 
 class RestNamespace:
     def __init__(self, github: "GitHubCore"):
-        self._github = github
+        self._github_ref = ref(github)
+
+    @property
+    def _github(self) -> "GitHubCore":
+        if g := self._github_ref():
+            return g
+        raise RuntimeError(
+            "GitHub client has already been collected. "
+            "Do not use this namespace after the client has been collected."
+        )
 
     @cached_property
     def meta(self) -> "MetaClient":
diff --git a/githubkit/versions/v2022_11_28/rest/actions.py b/githubkit/versions/v2022_11_28/rest/actions.py
index 6034e9f88..604017001 100644
--- a/githubkit/versions/v2022_11_28/rest/actions.py
+++ b/githubkit/versions/v2022_11_28/rest/actions.py
@@ -10,6 +10,7 @@
 
 from __future__ import annotations
 
+from weakref import ref
 from typing_extensions import Annotated
 from typing import TYPE_CHECKING, Dict, Literal, Optional, overload
 
@@ -121,7 +122,16 @@ class ActionsClient:
     _REST_API_VERSION = "2022-11-28"
 
     def __init__(self, github: GitHubCore):
-        self._github = github
+        self._github_ref = ref(github)
+
+    @property
+    def _github(self) -> GitHubCore:
+        if g := self._github_ref():
+            return g
+        raise RuntimeError(
+            "GitHub client has already been collected. "
+            "Do not use this client after the client has been collected."
+        )
 
     def get_actions_cache_usage_for_org(
         self,
diff --git a/githubkit/versions/v2022_11_28/rest/activity.py b/githubkit/versions/v2022_11_28/rest/activity.py
index a07580c7c..5fba7e37b 100644
--- a/githubkit/versions/v2022_11_28/rest/activity.py
+++ b/githubkit/versions/v2022_11_28/rest/activity.py
@@ -10,6 +10,7 @@
 
 from __future__ import annotations
 
+from weakref import ref
 from typing_extensions import Annotated
 from typing import TYPE_CHECKING, Dict, Literal, Optional, overload
 
@@ -54,7 +55,16 @@ class ActivityClient:
     _REST_API_VERSION = "2022-11-28"
 
     def __init__(self, github: GitHubCore):
-        self._github = github
+        self._github_ref = ref(github)
+
+    @property
+    def _github(self) -> GitHubCore:
+        if g := self._github_ref():
+            return g
+        raise RuntimeError(
+            "GitHub client has already been collected. "
+            "Do not use this client after the client has been collected."
+        )
 
     def list_public_events(
         self,
diff --git a/githubkit/versions/v2022_11_28/rest/apps.py b/githubkit/versions/v2022_11_28/rest/apps.py
index 91b67bc5b..af69cdb52 100644
--- a/githubkit/versions/v2022_11_28/rest/apps.py
+++ b/githubkit/versions/v2022_11_28/rest/apps.py
@@ -10,6 +10,7 @@
 
 from __future__ import annotations
 
+from weakref import ref
 from typing_extensions import Annotated
 from typing import TYPE_CHECKING, Dict, Literal, Optional, overload
 
@@ -62,7 +63,16 @@ class AppsClient:
     _REST_API_VERSION = "2022-11-28"
 
     def __init__(self, github: GitHubCore):
-        self._github = github
+        self._github_ref = ref(github)
+
+    @property
+    def _github(self) -> GitHubCore:
+        if g := self._github_ref():
+            return g
+        raise RuntimeError(
+            "GitHub client has already been collected. "
+            "Do not use this client after the client has been collected."
+        )
 
     def get_authenticated(
         self,
diff --git a/githubkit/versions/v2022_11_28/rest/billing.py b/githubkit/versions/v2022_11_28/rest/billing.py
index 5835f8f02..ae9017792 100644
--- a/githubkit/versions/v2022_11_28/rest/billing.py
+++ b/githubkit/versions/v2022_11_28/rest/billing.py
@@ -10,6 +10,7 @@
 
 from __future__ import annotations
 
+from weakref import ref
 from typing_extensions import Annotated
 from typing import TYPE_CHECKING, Dict, Literal, Optional, overload
 
@@ -30,7 +31,16 @@ class BillingClient:
     _REST_API_VERSION = "2022-11-28"
 
     def __init__(self, github: GitHubCore):
-        self._github = github
+        self._github_ref = ref(github)
+
+    @property
+    def _github(self) -> GitHubCore:
+        if g := self._github_ref():
+            return g
+        raise RuntimeError(
+            "GitHub client has already been collected. "
+            "Do not use this client after the client has been collected."
+        )
 
     def get_github_actions_billing_org(
         self,
diff --git a/githubkit/versions/v2022_11_28/rest/checks.py b/githubkit/versions/v2022_11_28/rest/checks.py
index 85df07b33..a140da01a 100644
--- a/githubkit/versions/v2022_11_28/rest/checks.py
+++ b/githubkit/versions/v2022_11_28/rest/checks.py
@@ -10,6 +10,7 @@
 
 from __future__ import annotations
 
+from weakref import ref
 from typing_extensions import Annotated
 from typing import TYPE_CHECKING, Dict, Literal, Optional, overload
 
@@ -57,7 +58,16 @@ class ChecksClient:
     _REST_API_VERSION = "2022-11-28"
 
     def __init__(self, github: GitHubCore):
-        self._github = github
+        self._github_ref = ref(github)
+
+    @property
+    def _github(self) -> GitHubCore:
+        if g := self._github_ref():
+            return g
+        raise RuntimeError(
+            "GitHub client has already been collected. "
+            "Do not use this client after the client has been collected."
+        )
 
     @overload
     def create(
diff --git a/githubkit/versions/v2022_11_28/rest/classroom.py b/githubkit/versions/v2022_11_28/rest/classroom.py
index e41eaf72f..96c273362 100644
--- a/githubkit/versions/v2022_11_28/rest/classroom.py
+++ b/githubkit/versions/v2022_11_28/rest/classroom.py
@@ -10,6 +10,7 @@
 
 from __future__ import annotations
 
+from weakref import ref
 from typing_extensions import Annotated
 from typing import TYPE_CHECKING, Dict, Literal, Optional, overload
 
@@ -41,7 +42,16 @@ class ClassroomClient:
     _REST_API_VERSION = "2022-11-28"
 
     def __init__(self, github: GitHubCore):
-        self._github = github
+        self._github_ref = ref(github)
+
+    @property
+    def _github(self) -> GitHubCore:
+        if g := self._github_ref():
+            return g
+        raise RuntimeError(
+            "GitHub client has already been collected. "
+            "Do not use this client after the client has been collected."
+        )
 
     def get_an_assignment(
         self,
diff --git a/githubkit/versions/v2022_11_28/rest/code_scanning.py b/githubkit/versions/v2022_11_28/rest/code_scanning.py
index 4d53d8594..dd44686f1 100644
--- a/githubkit/versions/v2022_11_28/rest/code_scanning.py
+++ b/githubkit/versions/v2022_11_28/rest/code_scanning.py
@@ -10,6 +10,7 @@
 
 from __future__ import annotations
 
+from weakref import ref
 from typing_extensions import Annotated
 from typing import TYPE_CHECKING, Dict, Literal, Optional, overload
 
@@ -52,7 +53,16 @@ class CodeScanningClient:
     _REST_API_VERSION = "2022-11-28"
 
     def __init__(self, github: GitHubCore):
-        self._github = github
+        self._github_ref = ref(github)
+
+    @property
+    def _github(self) -> GitHubCore:
+        if g := self._github_ref():
+            return g
+        raise RuntimeError(
+            "GitHub client has already been collected. "
+            "Do not use this client after the client has been collected."
+        )
 
     def list_alerts_for_org(
         self,
diff --git a/githubkit/versions/v2022_11_28/rest/codes_of_conduct.py b/githubkit/versions/v2022_11_28/rest/codes_of_conduct.py
index 4c625ec56..aa1c99003 100644
--- a/githubkit/versions/v2022_11_28/rest/codes_of_conduct.py
+++ b/githubkit/versions/v2022_11_28/rest/codes_of_conduct.py
@@ -10,6 +10,7 @@
 
 from __future__ import annotations
 
+from weakref import ref
 from typing_extensions import Annotated
 from typing import TYPE_CHECKING, Dict, Literal, Optional, overload
 
@@ -32,7 +33,16 @@ class CodesOfConductClient:
     _REST_API_VERSION = "2022-11-28"
 
     def __init__(self, github: GitHubCore):
-        self._github = github
+        self._github_ref = ref(github)
+
+    @property
+    def _github(self) -> GitHubCore:
+        if g := self._github_ref():
+            return g
+        raise RuntimeError(
+            "GitHub client has already been collected. "
+            "Do not use this client after the client has been collected."
+        )
 
     def get_all_codes_of_conduct(
         self,
diff --git a/githubkit/versions/v2022_11_28/rest/codespaces.py b/githubkit/versions/v2022_11_28/rest/codespaces.py
index de646d052..79888024e 100644
--- a/githubkit/versions/v2022_11_28/rest/codespaces.py
+++ b/githubkit/versions/v2022_11_28/rest/codespaces.py
@@ -10,6 +10,7 @@
 
 from __future__ import annotations
 
+from weakref import ref
 from typing_extensions import Annotated
 from typing import TYPE_CHECKING, Dict, Literal, Optional, overload
 
@@ -76,7 +77,16 @@ class CodespacesClient:
     _REST_API_VERSION = "2022-11-28"
 
     def __init__(self, github: GitHubCore):
-        self._github = github
+        self._github_ref = ref(github)
+
+    @property
+    def _github(self) -> GitHubCore:
+        if g := self._github_ref():
+            return g
+        raise RuntimeError(
+            "GitHub client has already been collected. "
+            "Do not use this client after the client has been collected."
+        )
 
     def list_in_organization(
         self,
diff --git a/githubkit/versions/v2022_11_28/rest/copilot.py b/githubkit/versions/v2022_11_28/rest/copilot.py
index 820f709eb..e6292a4d9 100644
--- a/githubkit/versions/v2022_11_28/rest/copilot.py
+++ b/githubkit/versions/v2022_11_28/rest/copilot.py
@@ -10,6 +10,7 @@
 
 from __future__ import annotations
 
+from weakref import ref
 from typing_extensions import Annotated
 from typing import TYPE_CHECKING, Dict, Literal, Optional, overload
 
@@ -48,7 +49,16 @@ class CopilotClient:
     _REST_API_VERSION = "2022-11-28"
 
     def __init__(self, github: GitHubCore):
-        self._github = github
+        self._github_ref = ref(github)
+
+    @property
+    def _github(self) -> GitHubCore:
+        if g := self._github_ref():
+            return g
+        raise RuntimeError(
+            "GitHub client has already been collected. "
+            "Do not use this client after the client has been collected."
+        )
 
     def get_copilot_organization_details(
         self,
diff --git a/githubkit/versions/v2022_11_28/rest/dependabot.py b/githubkit/versions/v2022_11_28/rest/dependabot.py
index 6c022967f..7979ca9a5 100644
--- a/githubkit/versions/v2022_11_28/rest/dependabot.py
+++ b/githubkit/versions/v2022_11_28/rest/dependabot.py
@@ -10,6 +10,7 @@
 
 from __future__ import annotations
 
+from weakref import ref
 from typing_extensions import Annotated
 from typing import TYPE_CHECKING, Dict, Literal, Optional, overload
 
@@ -50,7 +51,16 @@ class DependabotClient:
     _REST_API_VERSION = "2022-11-28"
 
     def __init__(self, github: GitHubCore):
-        self._github = github
+        self._github_ref = ref(github)
+
+    @property
+    def _github(self) -> GitHubCore:
+        if g := self._github_ref():
+            return g
+        raise RuntimeError(
+            "GitHub client has already been collected. "
+            "Do not use this client after the client has been collected."
+        )
 
     def list_alerts_for_enterprise(
         self,
diff --git a/githubkit/versions/v2022_11_28/rest/dependency_graph.py b/githubkit/versions/v2022_11_28/rest/dependency_graph.py
index 37341f5d2..df9918e1a 100644
--- a/githubkit/versions/v2022_11_28/rest/dependency_graph.py
+++ b/githubkit/versions/v2022_11_28/rest/dependency_graph.py
@@ -10,6 +10,7 @@
 
 from __future__ import annotations
 
+from weakref import ref
 from typing_extensions import Annotated
 from typing import TYPE_CHECKING, Dict, Literal, Optional, overload
 
@@ -46,7 +47,16 @@ class DependencyGraphClient:
     _REST_API_VERSION = "2022-11-28"
 
     def __init__(self, github: GitHubCore):
-        self._github = github
+        self._github_ref = ref(github)
+
+    @property
+    def _github(self) -> GitHubCore:
+        if g := self._github_ref():
+            return g
+        raise RuntimeError(
+            "GitHub client has already been collected. "
+            "Do not use this client after the client has been collected."
+        )
 
     def diff_range(
         self,
diff --git a/githubkit/versions/v2022_11_28/rest/emojis.py b/githubkit/versions/v2022_11_28/rest/emojis.py
index 0449464ef..277267b14 100644
--- a/githubkit/versions/v2022_11_28/rest/emojis.py
+++ b/githubkit/versions/v2022_11_28/rest/emojis.py
@@ -10,6 +10,7 @@
 
 from __future__ import annotations
 
+from weakref import ref
 from typing_extensions import Annotated
 from typing import TYPE_CHECKING, Dict, Literal, Optional, overload
 
@@ -30,7 +31,16 @@ class EmojisClient:
     _REST_API_VERSION = "2022-11-28"
 
     def __init__(self, github: GitHubCore):
-        self._github = github
+        self._github_ref = ref(github)
+
+    @property
+    def _github(self) -> GitHubCore:
+        if g := self._github_ref():
+            return g
+        raise RuntimeError(
+            "GitHub client has already been collected. "
+            "Do not use this client after the client has been collected."
+        )
 
     def get(
         self,
diff --git a/githubkit/versions/v2022_11_28/rest/gists.py b/githubkit/versions/v2022_11_28/rest/gists.py
index 27305bffd..0c387d9ba 100644
--- a/githubkit/versions/v2022_11_28/rest/gists.py
+++ b/githubkit/versions/v2022_11_28/rest/gists.py
@@ -10,6 +10,7 @@
 
 from __future__ import annotations
 
+from weakref import ref
 from typing_extensions import Annotated
 from typing import TYPE_CHECKING, Dict, Literal, Optional, overload
 
@@ -43,7 +44,16 @@ class GistsClient:
     _REST_API_VERSION = "2022-11-28"
 
     def __init__(self, github: GitHubCore):
-        self._github = github
+        self._github_ref = ref(github)
+
+    @property
+    def _github(self) -> GitHubCore:
+        if g := self._github_ref():
+            return g
+        raise RuntimeError(
+            "GitHub client has already been collected. "
+            "Do not use this client after the client has been collected."
+        )
 
     def list(
         self,
diff --git a/githubkit/versions/v2022_11_28/rest/git.py b/githubkit/versions/v2022_11_28/rest/git.py
index ae58f17d6..7e575d262 100644
--- a/githubkit/versions/v2022_11_28/rest/git.py
+++ b/githubkit/versions/v2022_11_28/rest/git.py
@@ -10,6 +10,7 @@
 
 from __future__ import annotations
 
+from weakref import ref
 from typing_extensions import Annotated
 from typing import TYPE_CHECKING, Dict, Literal, Optional, overload
 
@@ -46,7 +47,16 @@ class GitClient:
     _REST_API_VERSION = "2022-11-28"
 
     def __init__(self, github: GitHubCore):
-        self._github = github
+        self._github_ref = ref(github)
+
+    @property
+    def _github(self) -> GitHubCore:
+        if g := self._github_ref():
+            return g
+        raise RuntimeError(
+            "GitHub client has already been collected. "
+            "Do not use this client after the client has been collected."
+        )
 
     @overload
     def create_blob(
diff --git a/githubkit/versions/v2022_11_28/rest/gitignore.py b/githubkit/versions/v2022_11_28/rest/gitignore.py
index da8632aca..739cb49d3 100644
--- a/githubkit/versions/v2022_11_28/rest/gitignore.py
+++ b/githubkit/versions/v2022_11_28/rest/gitignore.py
@@ -10,6 +10,7 @@
 
 from __future__ import annotations
 
+from weakref import ref
 from typing_extensions import Annotated
 from typing import TYPE_CHECKING, Dict, Literal, Optional, overload
 
@@ -32,7 +33,16 @@ class GitignoreClient:
     _REST_API_VERSION = "2022-11-28"
 
     def __init__(self, github: GitHubCore):
-        self._github = github
+        self._github_ref = ref(github)
+
+    @property
+    def _github(self) -> GitHubCore:
+        if g := self._github_ref():
+            return g
+        raise RuntimeError(
+            "GitHub client has already been collected. "
+            "Do not use this client after the client has been collected."
+        )
 
     def get_all_templates(
         self,
diff --git a/githubkit/versions/v2022_11_28/rest/interactions.py b/githubkit/versions/v2022_11_28/rest/interactions.py
index 0e6a8c0a5..b1757f98c 100644
--- a/githubkit/versions/v2022_11_28/rest/interactions.py
+++ b/githubkit/versions/v2022_11_28/rest/interactions.py
@@ -10,6 +10,7 @@
 
 from __future__ import annotations
 
+from weakref import ref
 from typing_extensions import Annotated
 from typing import TYPE_CHECKING, Dict, Literal, Optional, overload
 
@@ -40,7 +41,16 @@ class InteractionsClient:
     _REST_API_VERSION = "2022-11-28"
 
     def __init__(self, github: GitHubCore):
-        self._github = github
+        self._github_ref = ref(github)
+
+    @property
+    def _github(self) -> GitHubCore:
+        if g := self._github_ref():
+            return g
+        raise RuntimeError(
+            "GitHub client has already been collected. "
+            "Do not use this client after the client has been collected."
+        )
 
     def get_restrictions_for_org(
         self,
diff --git a/githubkit/versions/v2022_11_28/rest/issues.py b/githubkit/versions/v2022_11_28/rest/issues.py
index db56a1fd4..3f470ee1f 100644
--- a/githubkit/versions/v2022_11_28/rest/issues.py
+++ b/githubkit/versions/v2022_11_28/rest/issues.py
@@ -10,6 +10,7 @@
 
 from __future__ import annotations
 
+from weakref import ref
 from typing_extensions import Annotated
 from typing import TYPE_CHECKING, Dict, Literal, Optional, overload
 
@@ -89,7 +90,16 @@ class IssuesClient:
     _REST_API_VERSION = "2022-11-28"
 
     def __init__(self, github: GitHubCore):
-        self._github = github
+        self._github_ref = ref(github)
+
+    @property
+    def _github(self) -> GitHubCore:
+        if g := self._github_ref():
+            return g
+        raise RuntimeError(
+            "GitHub client has already been collected. "
+            "Do not use this client after the client has been collected."
+        )
 
     def list(
         self,
diff --git a/githubkit/versions/v2022_11_28/rest/licenses.py b/githubkit/versions/v2022_11_28/rest/licenses.py
index f707f4015..29fc7c29a 100644
--- a/githubkit/versions/v2022_11_28/rest/licenses.py
+++ b/githubkit/versions/v2022_11_28/rest/licenses.py
@@ -10,6 +10,7 @@
 
 from __future__ import annotations
 
+from weakref import ref
 from typing_extensions import Annotated
 from typing import TYPE_CHECKING, Dict, Literal, Optional, overload
 
@@ -34,7 +35,16 @@ class LicensesClient:
     _REST_API_VERSION = "2022-11-28"
 
     def __init__(self, github: GitHubCore):
-        self._github = github
+        self._github_ref = ref(github)
+
+    @property
+    def _github(self) -> GitHubCore:
+        if g := self._github_ref():
+            return g
+        raise RuntimeError(
+            "GitHub client has already been collected. "
+            "Do not use this client after the client has been collected."
+        )
 
     def get_all_commonly_used(
         self,
diff --git a/githubkit/versions/v2022_11_28/rest/markdown.py b/githubkit/versions/v2022_11_28/rest/markdown.py
index 0e3f505b0..c0420cc2e 100644
--- a/githubkit/versions/v2022_11_28/rest/markdown.py
+++ b/githubkit/versions/v2022_11_28/rest/markdown.py
@@ -10,6 +10,7 @@
 
 from __future__ import annotations
 
+from weakref import ref
 from typing_extensions import Annotated
 from typing import TYPE_CHECKING, Dict, Literal, Optional, overload
 
@@ -34,7 +35,16 @@ class MarkdownClient:
     _REST_API_VERSION = "2022-11-28"
 
     def __init__(self, github: GitHubCore):
-        self._github = github
+        self._github_ref = ref(github)
+
+    @property
+    def _github(self) -> GitHubCore:
+        if g := self._github_ref():
+            return g
+        raise RuntimeError(
+            "GitHub client has already been collected. "
+            "Do not use this client after the client has been collected."
+        )
 
     @overload
     def render(
diff --git a/githubkit/versions/v2022_11_28/rest/meta.py b/githubkit/versions/v2022_11_28/rest/meta.py
index 77a7a2c01..303a4b4eb 100644
--- a/githubkit/versions/v2022_11_28/rest/meta.py
+++ b/githubkit/versions/v2022_11_28/rest/meta.py
@@ -10,6 +10,7 @@
 
 from __future__ import annotations
 
+from weakref import ref
 from typing_extensions import Annotated
 from typing import TYPE_CHECKING, Dict, Literal, Optional, overload
 
@@ -35,7 +36,16 @@ class MetaClient:
     _REST_API_VERSION = "2022-11-28"
 
     def __init__(self, github: GitHubCore):
-        self._github = github
+        self._github_ref = ref(github)
+
+    @property
+    def _github(self) -> GitHubCore:
+        if g := self._github_ref():
+            return g
+        raise RuntimeError(
+            "GitHub client has already been collected. "
+            "Do not use this client after the client has been collected."
+        )
 
     def root(
         self,
diff --git a/githubkit/versions/v2022_11_28/rest/migrations.py b/githubkit/versions/v2022_11_28/rest/migrations.py
index 98bb71036..7a72233f2 100644
--- a/githubkit/versions/v2022_11_28/rest/migrations.py
+++ b/githubkit/versions/v2022_11_28/rest/migrations.py
@@ -10,6 +10,7 @@
 
 from __future__ import annotations
 
+from weakref import ref
 from typing_extensions import Annotated
 from typing import TYPE_CHECKING, Dict, Literal, Optional, overload
 
@@ -48,7 +49,16 @@ class MigrationsClient:
     _REST_API_VERSION = "2022-11-28"
 
     def __init__(self, github: GitHubCore):
-        self._github = github
+        self._github_ref = ref(github)
+
+    @property
+    def _github(self) -> GitHubCore:
+        if g := self._github_ref():
+            return g
+        raise RuntimeError(
+            "GitHub client has already been collected. "
+            "Do not use this client after the client has been collected."
+        )
 
     def list_for_org(
         self,
diff --git a/githubkit/versions/v2022_11_28/rest/oidc.py b/githubkit/versions/v2022_11_28/rest/oidc.py
index f6dd3555b..4b0b1acf1 100644
--- a/githubkit/versions/v2022_11_28/rest/oidc.py
+++ b/githubkit/versions/v2022_11_28/rest/oidc.py
@@ -10,6 +10,7 @@
 
 from __future__ import annotations
 
+from weakref import ref
 from typing_extensions import Annotated
 from typing import TYPE_CHECKING, Dict, Literal, Optional, overload
 
@@ -33,7 +34,16 @@ class OidcClient:
     _REST_API_VERSION = "2022-11-28"
 
     def __init__(self, github: GitHubCore):
-        self._github = github
+        self._github_ref = ref(github)
+
+    @property
+    def _github(self) -> GitHubCore:
+        if g := self._github_ref():
+            return g
+        raise RuntimeError(
+            "GitHub client has already been collected. "
+            "Do not use this client after the client has been collected."
+        )
 
     def get_oidc_custom_sub_template_for_org(
         self,
diff --git a/githubkit/versions/v2022_11_28/rest/orgs.py b/githubkit/versions/v2022_11_28/rest/orgs.py
index c6007a5a2..a56bf6c58 100644
--- a/githubkit/versions/v2022_11_28/rest/orgs.py
+++ b/githubkit/versions/v2022_11_28/rest/orgs.py
@@ -10,6 +10,7 @@
 
 from __future__ import annotations
 
+from weakref import ref
 from typing_extensions import Annotated
 from typing import TYPE_CHECKING, Dict, Literal, Optional, overload
 
@@ -82,7 +83,16 @@ class OrgsClient:
     _REST_API_VERSION = "2022-11-28"
 
     def __init__(self, github: GitHubCore):
-        self._github = github
+        self._github_ref = ref(github)
+
+    @property
+    def _github(self) -> GitHubCore:
+        if g := self._github_ref():
+            return g
+        raise RuntimeError(
+            "GitHub client has already been collected. "
+            "Do not use this client after the client has been collected."
+        )
 
     def list(
         self,
diff --git a/githubkit/versions/v2022_11_28/rest/packages.py b/githubkit/versions/v2022_11_28/rest/packages.py
index 3da64242a..a540ae73f 100644
--- a/githubkit/versions/v2022_11_28/rest/packages.py
+++ b/githubkit/versions/v2022_11_28/rest/packages.py
@@ -10,6 +10,7 @@
 
 from __future__ import annotations
 
+from weakref import ref
 from typing_extensions import Annotated
 from typing import TYPE_CHECKING, Dict, Literal, Optional, overload
 
@@ -34,7 +35,16 @@ class PackagesClient:
     _REST_API_VERSION = "2022-11-28"
 
     def __init__(self, github: GitHubCore):
-        self._github = github
+        self._github_ref = ref(github)
+
+    @property
+    def _github(self) -> GitHubCore:
+        if g := self._github_ref():
+            return g
+        raise RuntimeError(
+            "GitHub client has already been collected. "
+            "Do not use this client after the client has been collected."
+        )
 
     def list_docker_migration_conflicting_packages_for_organization(
         self,
diff --git a/githubkit/versions/v2022_11_28/rest/projects.py b/githubkit/versions/v2022_11_28/rest/projects.py
index f6a85cf94..3e920bb67 100644
--- a/githubkit/versions/v2022_11_28/rest/projects.py
+++ b/githubkit/versions/v2022_11_28/rest/projects.py
@@ -10,6 +10,7 @@
 
 from __future__ import annotations
 
+from weakref import ref
 from typing_extensions import Annotated
 from typing import TYPE_CHECKING, Dict, Literal, Optional, overload
 
@@ -56,7 +57,16 @@ class ProjectsClient:
     _REST_API_VERSION = "2022-11-28"
 
     def __init__(self, github: GitHubCore):
-        self._github = github
+        self._github_ref = ref(github)
+
+    @property
+    def _github(self) -> GitHubCore:
+        if g := self._github_ref():
+            return g
+        raise RuntimeError(
+            "GitHub client has already been collected. "
+            "Do not use this client after the client has been collected."
+        )
 
     def list_for_org(
         self,
diff --git a/githubkit/versions/v2022_11_28/rest/pulls.py b/githubkit/versions/v2022_11_28/rest/pulls.py
index 4403bbdf3..007895fb2 100644
--- a/githubkit/versions/v2022_11_28/rest/pulls.py
+++ b/githubkit/versions/v2022_11_28/rest/pulls.py
@@ -10,6 +10,7 @@
 
 from __future__ import annotations
 
+from weakref import ref
 from typing_extensions import Annotated
 from typing import TYPE_CHECKING, Dict, Literal, Optional, overload
 
@@ -63,7 +64,16 @@ class PullsClient:
     _REST_API_VERSION = "2022-11-28"
 
     def __init__(self, github: GitHubCore):
-        self._github = github
+        self._github_ref = ref(github)
+
+    @property
+    def _github(self) -> GitHubCore:
+        if g := self._github_ref():
+            return g
+        raise RuntimeError(
+            "GitHub client has already been collected. "
+            "Do not use this client after the client has been collected."
+        )
 
     def list(
         self,
diff --git a/githubkit/versions/v2022_11_28/rest/rate_limit.py b/githubkit/versions/v2022_11_28/rest/rate_limit.py
index 86a5f8e11..fa1dcc9e4 100644
--- a/githubkit/versions/v2022_11_28/rest/rate_limit.py
+++ b/githubkit/versions/v2022_11_28/rest/rate_limit.py
@@ -10,6 +10,7 @@
 
 from __future__ import annotations
 
+from weakref import ref
 from typing_extensions import Annotated
 from typing import TYPE_CHECKING, Dict, Literal, Optional, overload
 
@@ -30,7 +31,16 @@ class RateLimitClient:
     _REST_API_VERSION = "2022-11-28"
 
     def __init__(self, github: GitHubCore):
-        self._github = github
+        self._github_ref = ref(github)
+
+    @property
+    def _github(self) -> GitHubCore:
+        if g := self._github_ref():
+            return g
+        raise RuntimeError(
+            "GitHub client has already been collected. "
+            "Do not use this client after the client has been collected."
+        )
 
     def get(
         self,
diff --git a/githubkit/versions/v2022_11_28/rest/reactions.py b/githubkit/versions/v2022_11_28/rest/reactions.py
index b12db5598..649fb8c05 100644
--- a/githubkit/versions/v2022_11_28/rest/reactions.py
+++ b/githubkit/versions/v2022_11_28/rest/reactions.py
@@ -10,6 +10,7 @@
 
 from __future__ import annotations
 
+from weakref import ref
 from typing_extensions import Annotated
 from typing import TYPE_CHECKING, Dict, Literal, Optional, overload
 
@@ -45,7 +46,16 @@ class ReactionsClient:
     _REST_API_VERSION = "2022-11-28"
 
     def __init__(self, github: GitHubCore):
-        self._github = github
+        self._github_ref = ref(github)
+
+    @property
+    def _github(self) -> GitHubCore:
+        if g := self._github_ref():
+            return g
+        raise RuntimeError(
+            "GitHub client has already been collected. "
+            "Do not use this client after the client has been collected."
+        )
 
     def list_for_team_discussion_comment_in_org(
         self,
diff --git a/githubkit/versions/v2022_11_28/rest/repos.py b/githubkit/versions/v2022_11_28/rest/repos.py
index 80d20305d..a20c25eb4 100644
--- a/githubkit/versions/v2022_11_28/rest/repos.py
+++ b/githubkit/versions/v2022_11_28/rest/repos.py
@@ -10,6 +10,7 @@
 
 from __future__ import annotations
 
+from weakref import ref
 from typing_extensions import Annotated
 from typing import TYPE_CHECKING, Dict, Literal, Optional, overload
 
@@ -227,7 +228,16 @@ class ReposClient:
     _REST_API_VERSION = "2022-11-28"
 
     def __init__(self, github: GitHubCore):
-        self._github = github
+        self._github_ref = ref(github)
+
+    @property
+    def _github(self) -> GitHubCore:
+        if g := self._github_ref():
+            return g
+        raise RuntimeError(
+            "GitHub client has already been collected. "
+            "Do not use this client after the client has been collected."
+        )
 
     def list_for_org(
         self,
diff --git a/githubkit/versions/v2022_11_28/rest/search.py b/githubkit/versions/v2022_11_28/rest/search.py
index c0b800be9..cd8b911fb 100644
--- a/githubkit/versions/v2022_11_28/rest/search.py
+++ b/githubkit/versions/v2022_11_28/rest/search.py
@@ -10,6 +10,7 @@
 
 from __future__ import annotations
 
+from weakref import ref
 from typing_extensions import Annotated
 from typing import TYPE_CHECKING, Dict, Literal, Optional, overload
 
@@ -42,7 +43,16 @@ class SearchClient:
     _REST_API_VERSION = "2022-11-28"
 
     def __init__(self, github: GitHubCore):
-        self._github = github
+        self._github_ref = ref(github)
+
+    @property
+    def _github(self) -> GitHubCore:
+        if g := self._github_ref():
+            return g
+        raise RuntimeError(
+            "GitHub client has already been collected. "
+            "Do not use this client after the client has been collected."
+        )
 
     def code(
         self,
diff --git a/githubkit/versions/v2022_11_28/rest/secret_scanning.py b/githubkit/versions/v2022_11_28/rest/secret_scanning.py
index 35ecc864d..c9ac2556c 100644
--- a/githubkit/versions/v2022_11_28/rest/secret_scanning.py
+++ b/githubkit/versions/v2022_11_28/rest/secret_scanning.py
@@ -10,6 +10,7 @@
 
 from __future__ import annotations
 
+from weakref import ref
 from typing_extensions import Annotated
 from typing import TYPE_CHECKING, Dict, Literal, Optional, overload
 
@@ -39,7 +40,16 @@ class SecretScanningClient:
     _REST_API_VERSION = "2022-11-28"
 
     def __init__(self, github: GitHubCore):
-        self._github = github
+        self._github_ref = ref(github)
+
+    @property
+    def _github(self) -> GitHubCore:
+        if g := self._github_ref():
+            return g
+        raise RuntimeError(
+            "GitHub client has already been collected. "
+            "Do not use this client after the client has been collected."
+        )
 
     def list_alerts_for_enterprise(
         self,
diff --git a/githubkit/versions/v2022_11_28/rest/security_advisories.py b/githubkit/versions/v2022_11_28/rest/security_advisories.py
index 9ca6ac0a9..5b7f1795e 100644
--- a/githubkit/versions/v2022_11_28/rest/security_advisories.py
+++ b/githubkit/versions/v2022_11_28/rest/security_advisories.py
@@ -10,6 +10,7 @@
 
 from __future__ import annotations
 
+from weakref import ref
 from typing_extensions import Annotated
 from typing import TYPE_CHECKING, Dict, Literal, Optional, overload
 
@@ -49,7 +50,16 @@ class SecurityAdvisoriesClient:
     _REST_API_VERSION = "2022-11-28"
 
     def __init__(self, github: GitHubCore):
-        self._github = github
+        self._github_ref = ref(github)
+
+    @property
+    def _github(self) -> GitHubCore:
+        if g := self._github_ref():
+            return g
+        raise RuntimeError(
+            "GitHub client has already been collected. "
+            "Do not use this client after the client has been collected."
+        )
 
     def list_global_advisories(
         self,
diff --git a/githubkit/versions/v2022_11_28/rest/teams.py b/githubkit/versions/v2022_11_28/rest/teams.py
index f61448a87..65e73a37c 100644
--- a/githubkit/versions/v2022_11_28/rest/teams.py
+++ b/githubkit/versions/v2022_11_28/rest/teams.py
@@ -10,6 +10,7 @@
 
 from __future__ import annotations
 
+from weakref import ref
 from typing_extensions import Annotated
 from typing import TYPE_CHECKING, Dict, Literal, Optional, overload
 
@@ -64,7 +65,16 @@ class TeamsClient:
     _REST_API_VERSION = "2022-11-28"
 
     def __init__(self, github: GitHubCore):
-        self._github = github
+        self._github_ref = ref(github)
+
+    @property
+    def _github(self) -> GitHubCore:
+        if g := self._github_ref():
+            return g
+        raise RuntimeError(
+            "GitHub client has already been collected. "
+            "Do not use this client after the client has been collected."
+        )
 
     def list(
         self,
diff --git a/githubkit/versions/v2022_11_28/rest/users.py b/githubkit/versions/v2022_11_28/rest/users.py
index 3d8ac176c..e20a4628b 100644
--- a/githubkit/versions/v2022_11_28/rest/users.py
+++ b/githubkit/versions/v2022_11_28/rest/users.py
@@ -10,6 +10,7 @@
 
 from __future__ import annotations
 
+from weakref import ref
 from typing_extensions import Annotated
 from typing import TYPE_CHECKING, Dict, Literal, Optional, overload
 
@@ -56,7 +57,16 @@ class UsersClient:
     _REST_API_VERSION = "2022-11-28"
 
     def __init__(self, github: GitHubCore):
-        self._github = github
+        self._github_ref = ref(github)
+
+    @property
+    def _github(self) -> GitHubCore:
+        if g := self._github_ref():
+            return g
+        raise RuntimeError(
+            "GitHub client has already been collected. "
+            "Do not use this client after the client has been collected."
+        )
 
     def get_authenticated(
         self,


Test patch:
diff --git a/tests/test_rest/test_cache.py b/tests/test_rest/test_cache.py
new file mode 100644
index 000000000..64cd216ef
--- /dev/null
+++ b/tests/test_rest/test_cache.py
@@ -0,0 +1,10 @@
+from githubkit import GitHub
+from githubkit.versions import LATEST_VERSION
+
+
+def test_cache(g: GitHub):
+    assert g.rest(LATEST_VERSION) is g.rest(LATEST_VERSION)
+    assert g.rest(LATEST_VERSION).repos is g.rest(LATEST_VERSION).repos
+
+    assert g.rest is g.rest
+    assert g.rest.repos is g.rest.repos