Skip to content

Commit

Permalink
Use __all__ and __dir__ to hide non-public API.
Browse files Browse the repository at this point in the history
Closes popsim-consortium#324.

Note that non-public symbols are still accessible when referred to
explicitly by name. But here we use the module-level __dir__()
function to define which symbols are seen when using dir(<module-name>).
This is a Python >= 3.7 only feature, but the function is just ignored
on Python 3.6.
  • Loading branch information
grahamgower committed Jun 24, 2021
1 parent 8ec4796 commit 9e0a277
Show file tree
Hide file tree
Showing 4 changed files with 132 additions and 2 deletions.
34 changes: 32 additions & 2 deletions demes/__init__.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
# flake8: noqa: F401

__version__ = "undefined"
try:
from . import _version
Expand Down Expand Up @@ -31,3 +29,35 @@
dump_all,
)
from .ms import from_ms

__all__ = [
"Builder",
"Epoch",
"AsymmetricMigration",
"Pulse",
"Deme",
"Graph",
"Split",
"Branch",
"Merge",
"Admix",
"load_asdict",
"loads_asdict",
"load",
"loads",
"load_all",
"dump",
"dumps",
"dump_all",
"from_ms",
]


# Override the symbols that are returned when calling dir(<module-name>).
# https://www.python.org/dev/peps/pep-0562/
# We do this because the Python REPL and IPython notebooks ignore __all__
# when providing autocomplete suggestions. They instead rely on dir().
# By not showing internal symbols in the dir() output, we reduce the chance
# that users rely on non-public features.
def __dir__():
return sorted(__all__)
6 changes: 6 additions & 0 deletions demes/hypothesis_strategies.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,12 @@

import demes

__all__ = ["graphs"]


def __dir__():
return sorted(__all__)


def prec32(x):
"""truncate x to the nearest single-precision floating point number"""
Expand Down
4 changes: 4 additions & 0 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@ extend-exclude = docs/_build
# black-compatible settings
max-line-length = 88
extend-ignore = E203, W503
# There's no way to ignore specific warnings in the files themselves.
# "flake8: noqa: F401" on its own line will just ignore all warnings.
per-file-ignores =
tests/test_import_visibility.py:F403,F405

[mypy]
files = demes, tests
Expand Down
90 changes: 90 additions & 0 deletions tests/test_import_visibility.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import pathlib
import tempfile

import pytest
from demes import *


def test_builder():
b = Builder()
b.add_deme("A", epochs=[dict(start_size=100)])
b.resolve()


def test_dumps_and_loads():
b = Builder()
b.add_deme("A", epochs=[dict(start_size=100)])
graph1 = b.resolve()
dump_str = dumps(graph1)
graph2 = loads(dump_str)
graph1.assert_close(graph2)


def test_dump_and_load():
b = Builder()
b.add_deme("A", epochs=[dict(start_size=100)])
graph1 = b.resolve()
with tempfile.TemporaryDirectory() as tmpdir:
tmpfile = pathlib.Path(tmpdir) / "temp.yaml"
dump(graph1, tmpfile)
graph2 = load(tmpfile)
graph1.assert_close(graph2)


def test_public_symbols():
Builder
Epoch
AsymmetricMigration
Pulse
Deme
Graph
Split
Branch
Merge
Admix

load_asdict
loads_asdict
load
loads
load_all
dump
dumps
dump_all

from_ms


def test_nonpublic_symbols():
with pytest.raises(NameError):
demes
with pytest.raises(NameError):
load_dump
with pytest.raises(NameError):
ms
with pytest.raises(NameError):
prec32


def test_demes_dir():
import demes

dir_demes = set(dir(demes))
assert "load" in dir_demes
assert "dump" in dir_demes
assert "loads" in dir_demes
assert "dumps" in dir_demes

assert "demes" not in dir_demes
assert "load_dump" not in dir_demes
assert "ms" not in dir_demes
assert "graphs" not in dir_demes
assert "prec32" not in dir_demes


def test_hypothesis_strategy_dir():
import demes.hypothesis_strategies

dir_demes_hs = set(dir(demes.hypothesis_strategies))
assert "graphs" in dir_demes_hs
assert "prec32" not in dir_demes_hs

0 comments on commit 9e0a277

Please sign in to comment.