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

Switch to more recent 'files' API from importlib.resources #147

Closed
Closed
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
17 changes: 17 additions & 0 deletions certifi/compat.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
"""
Fallback for Python prior to 3.9 where importlib_resources
is not available.
Does not support modules where __file__ is not defined on
the module.
"""

import os


def read_text(_module, _path):
with open(where(), "r", encoding="ascii") as data:
return data.read()


def where():
return os.path.join(os.path.dirname(__file__), "cacert.pem")
52 changes: 5 additions & 47 deletions certifi/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,55 +6,13 @@

This module returns the installation location of cacert.pem or its contents.
"""
import os

try:
from importlib.resources import path as get_path, read_text

_CACERT_CTX = None
_CACERT_PATH = None

def where():
# This is slightly terrible, but we want to delay extracting the file
# in cases where we're inside of a zipimport situation until someone
# actually calls where(), but we don't want to re-extract the file
# on every call of where(), so we'll do it once then store it in a
# global variable.
global _CACERT_CTX
global _CACERT_PATH
if _CACERT_PATH is None:
# This is slightly janky, the importlib.resources API wants you to
# manage the cleanup of this file, so it doesn't actually return a
# path, it returns a context manager that will give you the path
# when you enter it and will do any cleanup when you leave it. In
# the common case of not needing a temporary file, it will just
# return the file system location and the __exit__() is a no-op.
#
# We also have to hold onto the actual context manager, because
# it will do the cleanup whenever it gets garbage collected, so
# we will also store that at the global level as well.
_CACERT_CTX = get_path("certifi", "cacert.pem")
_CACERT_PATH = str(_CACERT_CTX.__enter__())

return _CACERT_PATH


except ImportError:
# This fallback will work for Python versions prior to 3.7 that lack the
# importlib.resources module but relies on the existing `where` function
# so won't address issues with environments like PyOxidizer that don't set
# __file__ on modules.
def read_text(_module, _path, encoding="ascii"):
with open(where(), "r", encoding=encoding) as data:
return data.read()

# If we don't have importlib.resources, then we will just do the old logic
# of assuming we're on the filesystem and munge the path directly.
def where():
f = os.path.dirname(__file__)

return os.path.join(f, "cacert.pem")
try:
from .resources import where, read_text
except Exception:
from .compat import where, read_text # noqa: F401


def contents():
return read_text("certifi", "cacert.pem", encoding="ascii")
return read_text("certifi", "cacert.pem")
28 changes: 28 additions & 0 deletions certifi/resources.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import atexit
import functools

try:
from importlib import resources
except ImportError:
import importlib_resources as resources


# ensure 'files' API is present
resources.files
read_text = resources.read_text


def as_file(path):
"""
Ensure the path is a file on the file system for the duration
of the interpreter run.
"""
ctx = resources.as_file(path)
tmp_copy = ctx.__enter__()
atexit.register(tmp_copy.__exit__, None, None, None)
return tmp_copy


@functools.lru_cache()
def where():
return as_file(resources.files('certifi') / 'cacert.pem')