Repository: asdf-format/asdf

Issue:
Remove deprecated features
This issue is to track deprecated features that are scheduled for removal in asdf `4.0.0`:
- [ ] `asdf.util.human_list` (deprecated: #1688)
- [ ] `asdf.util.resolve_name` (deprecated: #1688)
- [ ] `asdf.util.minversion` (deprecated: #1688)
- [ ] `asdf.util.iter_subclasses` (deprecated: #1688)

Discussion:

Patch:
diff --git a/asdf/_asdf.py b/asdf/_asdf.py
index 41dac5be..3a5d4a3f 100644
--- a/asdf/_asdf.py
+++ b/asdf/_asdf.py
@@ -18,7 +18,6 @@ from ._block.manager import Manager as BlockManager
 from ._helpers import validate_version
 from .config import config_context, get_config
 from .exceptions import (
-    AsdfDeprecationWarning,
     AsdfManifestURIMismatchWarning,
     AsdfPackageVersionWarning,
     AsdfWarning,
@@ -31,21 +30,6 @@ from .tags.core import AsdfObject, ExtensionMetadata, HistoryEntry, Software
 from .util import NotSet
 
 
-def __getattr__(name):
-    if name == "SerializationContext":
-        warnings.warn(
-            "importing SerializationContext from asdf.asdf is deprecated. "
-            "Please import SerializationContext from asdf.extension",
-            AsdfDeprecationWarning,
-        )
-        from .extension._serialization_context import SerializationContext
-
-        return SerializationContext
-
-    msg = f"module {__name__!r} has no attribute {name!r}"
-    raise AttributeError(msg)
-
-
 def _get_asdf_library_info():
     """
     Get information about asdf to include in the asdf_library entry
@@ -72,9 +56,7 @@ class AsdfFile:
         uri=None,
         extensions=None,
         version=None,
-        ignore_version_mismatch=NotSet,
         ignore_unrecognized_tag=False,
-        ignore_implicit_conversion=NotSet,
         memmap=True,
         lazy_load=True,
         custom_schema=None,
@@ -100,22 +82,10 @@ class AsdfFile:
             The ASDF Standard version.  If not provided, defaults to the
             configured default version.  See `asdf.config.AsdfConfig.default_version`.
 
-        ignore_version_mismatch : bool, optional
-            Deprecated and unused. This setting does nothing since asdf 3.0.0
-            When `True`, do not raise warnings for mismatched schema versions.
-            Set to `True` by default.
-
         ignore_unrecognized_tag : bool, optional
             When `True`, do not raise warnings for unrecognized tags. Set to
             `False` by default.
 
-        ignore_implicit_conversion : bool
-            DEPRECATED
-            When `True`, do not raise warnings when types in the tree are
-            implicitly converted into a serializable object. The motivating
-            case for this is currently ``namedtuple``, which cannot be serialized
-            as-is.
-
         memmap : bool, optional
             When `True`, when reading files, attempt to memmap underlying data
             arrays when possible. Defaults to ``True``.
@@ -155,14 +125,7 @@ class AsdfFile:
         else:
             self._custom_schema = None
 
-        if ignore_version_mismatch is not NotSet:
-            warnings.warn(
-                "ignore_version_mismatch is deprecated and has done nothing since asdf 3.0.0",
-                AsdfDeprecationWarning,
-            )
-
         self._ignore_unrecognized_tag = ignore_unrecognized_tag
-        self._ignore_implicit_conversion = ignore_implicit_conversion
 
         # Context of a call to treeutil.walk_and_modify, needed in the AsdfFile
         # in case walk_and_modify is re-entered by extension code (via
@@ -177,12 +140,6 @@ class AsdfFile:
         self._closed = False
         self._external_asdf_by_uri = {}
         self._blocks = BlockManager(uri=uri, lazy_load=lazy_load, memmap=memmap)
-        # this message is passed into find_references to only warn if
-        # a reference was found
-        find_ref_warning_msg = (
-            "find_references during AsdfFile.__init__ is deprecated. "
-            "call AsdfFile.find_references after AsdfFile.__init__"
-        )
         if tree is None:
             # Bypassing the tree property here, to avoid validating
             # an empty tree.
@@ -196,21 +153,12 @@ class AsdfFile:
             self._blocks._uri = tree.uri
             # Set directly to self._tree (bypassing property), since
             # we can assume the other AsdfFile is already valid.
-            self._tree = tree.tree
-            self.find_references(_warning_msg=find_ref_warning_msg)
+            # Call "walk_and_modify" here to use it's logic for
+            # creating a "copy" of the other tree. This mimics what was previously
+            # a call to find_references (which we longer do in AsdfFile.__init__)
+            self._tree = treeutil.walk_and_modify(tree.tree, lambda o: o)
         else:
             self._tree = AsdfObject(tree)
-            try:
-                self.validate()
-            except ValidationError:
-                warnings.warn(
-                    "Validation during AsdfFile.__init__ is deprecated. "
-                    "Please use AsdfFile.validate to validate the tree",
-                    AsdfDeprecationWarning,
-                )
-                raise
-
-            self.find_references(_warning_msg=find_ref_warning_msg)
 
         self._comments = []
 
@@ -252,14 +200,6 @@ class AsdfFile:
         """
         return str(self._version)
 
-    @property
-    def version_map(self):
-        warnings.warn(
-            "AsdfFile.version_map is deprecated. Please use the extension_manager",
-            AsdfDeprecationWarning,
-        )
-        return versioning._get_version_map(self.version_string)
-
     @property
     def extensions(self):
         """
@@ -619,16 +559,7 @@ class AsdfFile:
 
     @tree.setter
     def tree(self, tree):
-        asdf_object = AsdfObject(tree)
-        # Only perform custom validation if the tree is not empty
-        try:
-            self._validate(asdf_object, custom=bool(tree))
-        except ValidationError:
-            warnings.warn(
-                "Validation on tree assignment is deprecated. Please use AsdfFile.validate", AsdfDeprecationWarning
-            )
-            raise
-        self._tree = asdf_object
+        self._tree = AsdfObject(tree)
 
     def keys(self):
         return self.tree.keys()
@@ -956,9 +887,6 @@ class AsdfFile:
                 # to select the correct tag for us.
                 tree = yamlutil.custom_tree_to_tagged_tree(AsdfObject(), self)
 
-            find_ref_warning_msg = "find_references during open is deprecated. call AsdfFile.find_references after open"
-            tree = reference.find_references(tree, self, _warning_msg=find_ref_warning_msg)
-
             if self.version <= versioning.FILL_DEFAULTS_MAX_VERSION and get_config().legacy_fill_schema_defaults:
                 schema.fill_defaults(tree, self, reading=True)
 
@@ -1325,49 +1253,22 @@ class AsdfFile:
                 if version is not None:
                     self.version = previous_version
 
-    def find_references(self, _warning_msg=False):
+    def find_references(self):
         """
         Finds all external "JSON References" in the tree and converts
         them to ``reference.Reference`` objects.
         """
         # Set directly to self._tree, since it doesn't need to be re-validated.
-        self._tree = reference.find_references(self._tree, self, _warning_msg=_warning_msg)
+        self._tree = reference.find_references(self._tree, self)
 
-    def resolve_references(self, **kwargs):
+    def resolve_references(self):
         """
         Finds all external "JSON References" in the tree, loads the
         external content, and places it directly in the tree.  Saving
         a ASDF file after this operation means it will have no
         external references, and will be completely self-contained.
         """
-        if len(kwargs):
-            warnings.warn("Passing kwargs to resolve_references is deprecated and does nothing", AsdfDeprecationWarning)
         self._tree = reference.resolve_references(self._tree, self)
-        try:
-            self.validate()
-        except ValidationError:
-            warnings.warn(
-                "Validation during resolve_references is deprecated. "
-                "Please use AsdfFile.validate after resolve_references to validate the resolved tree",
-                AsdfDeprecationWarning,
-            )
-            raise
-
-    def resolve_and_inline(self):
-        """
-        Resolves all external references and inlines all data.  This
-        produces something that, when saved, is a 100% valid YAML
-        file.
-        """
-        warnings.warn(
-            "resolve_and_inline is deprecated. "
-            "Use AsdfFile.resolve_references and all_array_storage=inline "
-            "during AsdfFile.write_to",
-            AsdfDeprecationWarning,
-        )
-        self.resolve_references()
-        for b in self._blocks.blocks:
-            self.set_array_storage(b.data, "inline")
 
     def fill_defaults(self):
         """
@@ -1607,7 +1508,6 @@ def open_asdf(
     mode=None,
     validate_checksums=False,
     extensions=None,
-    ignore_version_mismatch=NotSet,
     ignore_unrecognized_tag=False,
     _force_raw_types=False,
     memmap=True,
@@ -1643,11 +1543,6 @@ def open_asdf(
         Additional extensions to use when reading and writing the file.
         May be an `asdf.extension.Extension` or a `list` of extensions.
 
-    ignore_version_mismatch : bool, optional
-        Deprecated and unused. This setting does nothing since asdf 3.0.0
-        When `True`, do not raise warnings for mismatched schema versions.
-        Set to `True` by default.
-
     ignore_unrecognized_tag : bool, optional
         When `True`, do not raise warnings for unrecognized tags. Set to
         `False` by default.
@@ -1713,7 +1608,6 @@ def open_asdf(
             mode = "r"
 
     instance = AsdfFile(
-        ignore_version_mismatch=ignore_version_mismatch,
         ignore_unrecognized_tag=ignore_unrecognized_tag,
         memmap=memmap,
         lazy_load=lazy_load,
diff --git a/asdf/_convenience.py b/asdf/_convenience.py
index d84bcd52..a0f57dbf 100644
--- a/asdf/_convenience.py
+++ b/asdf/_convenience.py
@@ -18,7 +18,7 @@ def info(node_or_path, max_rows=DEFAULT_MAX_ROWS, max_cols=DEFAULT_MAX_COLS, sho
 
     Parameters
     ----------
-    node_or_path : str, pathlib.Path, asdf.asdf.AsdfFile, or any ASDF tree node
+    node_or_path : str, pathlib.Path, asdf.AsdfFile, or any ASDF tree node
         The tree or sub-tree to render.  Strings and Path objects
         will first be passed to asdf.open(...).
 
diff --git a/asdf/asdf.py b/asdf/asdf.py
deleted file mode 100644
index 8066ed95..00000000
--- a/asdf/asdf.py
+++ /dev/null
@@ -1,20 +0,0 @@
-import warnings
-
-from . import _asdf
-from .exceptions import AsdfDeprecationWarning
-
-warnings.warn(
-    "asdf.asdf is deprecated. Please use asdf.AsdfFile and asdf.open",
-    AsdfDeprecationWarning,
-)
-
-
-def __getattr__(name):
-    if hasattr(_asdf, name):
-        return getattr(_asdf, name)
-    warnings.warn(
-        "asdf.asdf is deprecated",
-        AsdfDeprecationWarning,
-    )
-    msg = f"module {__name__!r} has no attribute {name!r}"
-    raise AttributeError(msg)
diff --git a/asdf/extension/_converter.py b/asdf/extension/_converter.py
index 5e983562..feece7a2 100644
--- a/asdf/extension/_converter.py
+++ b/asdf/extension/_converter.py
@@ -26,7 +26,7 @@ class Converter(abc.ABC):
         tags : list of str
             List of active tags to choose from.  Guaranteed to match
             one of the tag patterns listed in the 'tags' property.
-        ctx : asdf.asdf.SerializationContext
+        ctx : asdf.extension.SerializationContext
             Context of the current serialization request.
 
     and return a str, the selected tag (should be one of tags) or
diff --git a/asdf/reference.py b/asdf/reference.py
index b2f5aad6..35a3182c 100644
--- a/asdf/reference.py
+++ b/asdf/reference.py
@@ -4,7 +4,6 @@ standard <http://tools.ietf.org/html/draft-pbryan-zyp-json-ref-03>`__
 and `JSON Pointer standard <http://tools.ietf.org/html/rfc6901>`__.
 """
 
-import warnings
 import weakref
 from collections.abc import Sequence
 from contextlib import suppress
@@ -12,7 +11,6 @@ from contextlib import suppress
 import numpy as np
 
 from . import generic_io, treeutil, util
-from .exceptions import AsdfDeprecationWarning
 from .util import _patched_urllib_parse
 
 __all__ = ["resolve_fragment", "Reference", "find_references", "resolve_references", "make_reference"]
@@ -105,7 +103,7 @@ class Reference:
         return item in self._get_target()
 
 
-def find_references(tree, ctx, _warning_msg=False):
+def find_references(tree, ctx):
     """
     Find all of the JSON references in the tree, and convert them into
     `Reference` objects.
@@ -113,12 +111,10 @@ def find_references(tree, ctx, _warning_msg=False):
 
     def do_find(tree):
         if isinstance(tree, dict) and "$ref" in tree:
-            if _warning_msg:
-                warnings.warn(_warning_msg, AsdfDeprecationWarning)
             return Reference(tree["$ref"], asdffile=ctx)
         return tree
 
-    return treeutil.walk_and_modify(tree, do_find, ignore_implicit_conversion=ctx._ignore_implicit_conversion)
+    return treeutil.walk_and_modify(tree, do_find)
 
 
 def resolve_references(tree, ctx, **kwargs):
@@ -134,7 +130,7 @@ def resolve_references(tree, ctx, **kwargs):
 
     tree = find_references(tree, ctx)
 
-    return treeutil.walk_and_modify(tree, do_resolve, ignore_implicit_conversion=ctx._ignore_implicit_conversion)
+    return treeutil.walk_and_modify(tree, do_resolve)
 
 
 def make_reference(asdffile, path):
diff --git a/asdf/schema.py b/asdf/schema.py
index c09bad79..479e7219 100644
--- a/asdf/schema.py
+++ b/asdf/schema.py
@@ -16,7 +16,7 @@ from asdf._jsonschema.exceptions import RefResolutionError, ValidationError
 
 from . import constants, generic_io, reference, tagged, treeutil, util, versioning, yamlutil
 from .config import get_config
-from .exceptions import AsdfDeprecationWarning, AsdfWarning
+from .exceptions import AsdfWarning
 from .util import _patched_urllib_parse
 
 YAML_SCHEMA_METASCHEMA_ID = "http://stsci.edu/schemas/yaml-schema/draft-01"
@@ -49,14 +49,8 @@ def _type_to_tag(type_):
     return None
 
 
-def _tag_to_uri(input_str):
-    if not input_str.startswith(constants.STSCI_SCHEMA_TAG_BASE):
-        return input_str
-    warnings.warn(
-        "Resolving by tag is deprecated. Use uris instead of tags",
-        AsdfDeprecationWarning,
-    )
-    return f"http://stsci.edu/schemas/asdf{input_str[len(constants.STSCI_SCHEMA_TAG_BASE):]}"
+def _default_resolver(uri):
+    return uri
 
 
 def validate_tag(validator, tag_pattern, instance, schema):
@@ -239,7 +233,7 @@ class _ValidationContext:
 
 @lru_cache
 def _create_validator(validators=YAML_VALIDATORS, visit_repeat_nodes=False):
-    meta_schema = _load_schema_cached(YAML_SCHEMA_METASCHEMA_ID, _tag_to_uri, False)
+    meta_schema = _load_schema_cached(YAML_SCHEMA_METASCHEMA_ID, None, False)
 
     type_checker = mvalidators.Draft4Validator.TYPE_CHECKER.redefine_many(
         {
@@ -339,9 +333,6 @@ def _load_schema(url):
 
 
 def _make_schema_loader(resolver):
-    if resolver is None:
-        resolver = _tag_to_uri
-
     def load_schema(url):
         # Check if this is a URI provided by the new
         # Mapping API:
@@ -415,9 +406,6 @@ def load_schema(url, resolver=None, resolve_references=False):
         If ``True``, resolve all ``$ref`` references.
 
     """
-    if resolver is None:
-        resolver = _tag_to_uri
-
     # We want to cache the work that went into constructing the schema, but returning
     # the same object is treacherous, because users who mutate the result will not
     # expect that they're changing the schema everywhere.
@@ -465,7 +453,7 @@ def _safe_resolve(resolver, json_id, uri):
 @lru_cache
 def _load_schema_cached(url, resolver, resolve_references):
     if resolver is None:
-        resolver = _tag_to_uri
+        resolver = _default_resolver
     loader = _make_schema_loader(resolver)
     schema, url = loader(url)
 
@@ -735,9 +723,9 @@ def check_schema(schema, validate_default=True):
         applicable_validators = methodcaller("items")
 
     meta_schema_id = schema.get("$schema", YAML_SCHEMA_METASCHEMA_ID)
-    meta_schema = _load_schema_cached(meta_schema_id, _tag_to_uri, False)
+    meta_schema = _load_schema_cached(meta_schema_id, None, False)
 
-    resolver = _make_jsonschema_refresolver(_tag_to_uri)
+    resolver = _make_jsonschema_refresolver(_default_resolver)
 
     cls = mvalidators.create(
         meta_schema=meta_schema,
diff --git a/asdf/stream.py b/asdf/stream.py
deleted file mode 100644
index b66952c6..00000000
--- a/asdf/stream.py
+++ /dev/null
@@ -1,9 +0,0 @@
-import warnings
-
-from .exceptions import AsdfDeprecationWarning
-from .tags.core.stream import Stream  # noqa: F401
-
-warnings.warn(
-    "asdf.stream is deprecated. Please use asdf.tags.core.stream",
-    AsdfDeprecationWarning,
-)
diff --git a/asdf/treeutil.py b/asdf/treeutil.py
index a76fd924..df013911 100644
--- a/asdf/treeutil.py
+++ b/asdf/treeutil.py
@@ -3,12 +3,9 @@ Utility functions for managing tree-like data structures.
 """
 
 import types
-import warnings
 from contextlib import contextmanager
 
 from . import lazy_nodes, tagged
-from .exceptions import AsdfDeprecationWarning, AsdfWarning
-from .util import NotSet
 
 __all__ = ["walk", "iter_tree", "walk_and_modify", "get_children", "is_container", "PendingValue", "RemoveNode"]
 
@@ -221,7 +218,7 @@ class _RemoveNode:
 RemoveNode = _RemoveNode()
 
 
-def walk_and_modify(top, callback, ignore_implicit_conversion=NotSet, postorder=True, _context=None):
+def walk_and_modify(top, callback, postorder=True, _context=None):
     """Modify a tree by walking it with a callback function.  It also has
     the effect of doing a deep copy.
 
@@ -253,24 +250,12 @@ def walk_and_modify(top, callback, ignore_implicit_conversion=NotSet, postorder=
         before their parents.  If `False`, the callable is invoked on the
         parents first.  Defaults to `True`.
 
-    ignore_implicit_conversion : bool
-        DEPRECATED
-        Controls whether warnings should be issued when implicitly converting a
-        given type instance in the tree into a serializable object. The primary
-        case for this is currently ``namedtuple``.
-
-        Defaults to `False`.
-
     Returns
     -------
     tree : object
         The modified tree.
 
     """
-    if ignore_implicit_conversion is NotSet:
-        ignore_implicit_conversion = False
-    else:
-        warnings.warn("ignore_implicit_conversion is deprecated", AsdfDeprecationWarning)
     callback_arity = callback.__code__.co_argcount
     if callback_arity < 1 or callback_arity > 2:
         msg = "Expected callback to accept one or two arguments"
@@ -360,25 +345,18 @@ def walk_and_modify(top, callback, ignore_implicit_conversion=NotSet, postorder=
         # to yield here.
         contents = [_recurse(value, json_id) for value in node]
 
-        try:
-            result = node.__class__(contents)
-            if isinstance(node, tagged.Tagged):
-                result._tag = node._tag
-        except TypeError:
-            # The derived class signature is different, so simply store the
-            # list representing the contents. Currently this is primarily
-            # intended to handle namedtuple and NamedTuple instances.
-            if not ignore_implicit_conversion:
-                warnings.warn(f"Failed to serialize instance of {type(node)}, converting to list instead", AsdfWarning)
-            result = contents
-            warnings.warn("implicit conversion is deprecated. Please instead use a Converter.", AsdfDeprecationWarning)
+        result = node.__class__(contents)
+        if isinstance(node, tagged.Tagged):
+            result._tag = node._tag
 
         return result
 
     def _handle_children(node, json_id):
         if isinstance(node, (dict, lazy_nodes.AsdfDictNode)):
             result = _handle_mapping(node, json_id)
-        elif isinstance(node, tuple):
+        # don't treat namedtuple instances as tuples
+        # see: https://github.com/python/cpython/issues/52044
+        elif isinstance(node, tuple) and not hasattr(node, "_fields"):
             result = _handle_immutable_sequence(node, json_id)
         elif isinstance(node, (list, lazy_nodes.AsdfListNode)):
             result = _handle_mutable_sequence(node, json_id)
diff --git a/asdf/util.py b/asdf/util.py
index b099213b..c03b458b 100644
--- a/asdf/util.py
+++ b/asdf/util.py
@@ -5,17 +5,12 @@ import math
 import re
 import struct
 import sys
-import types
-import warnings
 from functools import lru_cache
-from importlib import metadata
-from urllib.request import pathname2url
 
 import numpy as np
 import yaml
-from packaging.version import Version
 
-from . import constants, exceptions
+from . import constants
 
 # The standard library importlib.metadata returns duplicate entrypoints
 # for all python versions up to and including 3.11
@@ -23,9 +18,9 @@ from . import constants, exceptions
 # see PR https://github.com/asdf-format/asdf/pull/1260
 # see issue https://github.com/asdf-format/asdf/issues/1254
 if sys.version_info >= (3, 12):
-    from importlib.metadata import packages_distributions
+    pass
 else:
-    from importlib_metadata import packages_distributions
+    pass
 
 
 # We're importing our own copy of urllib.parse because
@@ -46,15 +41,10 @@ _patched_urllib_parse.uses_netloc.append("asdf")
 
 __all__ = [
     "load_yaml",
-    "human_list",
     "get_array_base",
     "get_base_uri",
-    "filepath_to_url",
-    "iter_subclasses",
     "calculate_padding",
-    "resolve_name",
     "NotSet",
-    "is_primitive",
     "uri_match",
     "get_class_name",
     "get_file_type",
@@ -106,35 +96,6 @@ def load_yaml(init, tagged=False):
     return content
 
 
-def human_list(line, separator="and"):
-    """
-    Formats a list for human readability.
-
-    Parameters
-    ----------
-    l : sequence
-        A sequence of strings
-
-    separator : string, optional
-        The word to use between the last two entries.  Default:
-        ``"and"``.
-
-    Returns
-    -------
-    formatted_list : string
-
-    Examples
-    --------
-    >>> human_list(["vanilla", "strawberry", "chocolate"], "or")  # doctest: +SKIP
-    'vanilla, strawberry or chocolate'
-    """
-    warnings.warn("asdf.util.human_list is deprecated", exceptions.AsdfDeprecationWarning)
-    if len(line) == 1:
-        return line[0]
-
-    return ", ".join(line[:-1]) + " " + separator + " " + line[-1]
-
-
 def get_array_base(arr):
     """
     For a given Numpy array, finds the base array that "owns" the
@@ -156,15 +117,6 @@ def get_base_uri(uri):
     return _patched_urllib_parse.urlunparse([*list(parts[:5]), ""])
 
 
-def filepath_to_url(path):
-    """
-    For a given local file path, return a file:// url.
-    """
-    msg = "asdf.util.filepath_to_url is deprecated. Please use pathlib.Path.as_uri"
-    warnings.warn(msg, exceptions.AsdfDeprecationWarning)
-    return _patched_urllib_parse.urljoin("file:", pathname2url(path))
-
-
 def _iter_subclasses(cls):
     """
     Returns all subclasses of a class.
@@ -174,14 +126,6 @@ def _iter_subclasses(cls):
         yield from _iter_subclasses(x)
 
 
-def iter_subclasses(cls):
-    """
-    Returns all subclasses of a class.
-    """
-    warnings.warn("asdf.util.iter_subclasses is deprecated", exceptions.AsdfDeprecationWarning)
-    yield from _iter_subclasses(cls)
-
-
 def calculate_padding(content_size, pad_blocks, block_size):
     """
     Calculates the amount of extra space to add to a block given the
@@ -313,72 +257,6 @@ class HashableDict(dict):
         return hash(frozenset(self.items()))
 
 
-def resolve_name(name):
-    """Resolve a name like ``module.object`` to an object and return it.
-
-    This ends up working like ``from module import object`` but is easier
-    to deal with than the `__import__` builtin and supports digging into
-    submodules.
-
-    Parameters
-    ----------
-
-    name : `str`
-        A dotted path to a Python object--that is, the name of a function,
-        class, or other object in a module with the full path to that module,
-        including parent modules, separated by dots.  Also known as the fully
-        qualified name of the object.
-
-    Examples
-    --------
-
-    >>> resolve_name('asdf.util.resolve_name')  # doctest: +SKIP
-    <function resolve_name at 0x...>
-
-    Raises
-    ------
-    `ImportError`
-        If the module or named object is not found.
-    """
-
-    warnings.warn(
-        "asdf.util.resolve_name is deprecated, see astropy.utils.resolve_name", exceptions.AsdfDeprecationWarning
-    )
-
-    # Note: On python 2 these must be str objects and not unicode
-    parts = [str(part) for part in name.split(".")]
-
-    if len(parts) == 1:
-        # No dots in the name--just a straight up module import
-        cursor = 1
-        attr_name = ""  # Must not be unicode on Python 2
-    else:
-        cursor = len(parts) - 1
-        attr_name = parts[-1]
-
-    module_name = parts[:cursor]
-
-    while cursor > 0:
-        try:
-            ret = __import__(str(".".join(module_name)), fromlist=[attr_name])
-            break
-        except ImportError:
-            if cursor == 0:
-                raise
-            cursor -= 1
-            module_name = parts[:cursor]
-            attr_name = parts[cursor]
-            ret = ""
-
-    for part in parts[cursor:]:
-        try:
-            ret = getattr(ret, part)
-        except AttributeError as err:
-            raise ImportError(name) from err
-
-    return ret
-
-
 def get_class_name(obj, instance=True):
     """
     Given a class or instance of a class, returns a string representing the
@@ -396,65 +274,6 @@ def get_class_name(obj, instance=True):
     return f"{typ.__module__}.{typ.__qualname__}"
 
 
-def minversion(module, version, inclusive=True):
-    """
-    Returns `True` if the specified Python module satisfies a minimum version
-    requirement, and `False` if not.
-
-    Copied from astropy.utils.misc.minversion to avoid dependency on astropy.
-
-    Parameters
-    ----------
-
-    module : module or `str`
-        An imported module of which to check the version, or the name of
-        that module (in which case an import of that module is attempted--
-        if this fails `False` is returned).
-
-    version : `str`
-        The version as a string that this module must have at a minimum (e.g.
-        ``'0.12'``).
-
-    inclusive : `bool`
-        The specified version meets the requirement inclusively (i.e. ``>=``)
-        as opposed to strictly greater than (default: `True`).
-    """
-
-    warnings.warn("asdf.util.minversion is deprecated, see astropy.utils.minversion", exceptions.AsdfDeprecationWarning)
-
-    if isinstance(module, types.ModuleType):
-        module_name = module.__name__
-        module_version = getattr(module, "__version__", None)
-    elif isinstance(module, str):
-        module_name = module
-        module_version = None
-        try:
-            with warnings.catch_warnings():
-                warnings.filterwarnings("ignore", "asdf.util.resolve_name", exceptions.AsdfDeprecationWarning)
-                module = resolve_name(module_name)
-        except ImportError:
-            return False
-    else:
-        msg = f"module argument must be an actual imported module, or the import name of the module; got {module!r}"
-        raise ValueError(msg)
-
-    if module_version is None:
-        try:
-            module_version = metadata.version(module_name)
-        except metadata.PackageNotFoundError:
-            # Maybe the distribution name is different from package name.
-            # Calling packages_distributions is costly so we do it only
-            # if necessary, as only a few packages don't have the same
-            # distribution name.
-            dist_names = packages_distributions()
-            module_version = metadata.version(dist_names[module_name][0])
-
-    if inclusive:
-        return Version(module_version) >= Version(version)
-
-    return Version(module_version) > Version(version)
-
-
 class _InheritDocstrings(type):
     """
     This metaclass makes methods of a class automatically have their
@@ -508,24 +327,6 @@ from None, which may for example be a value of interest in a search.
 NotSet = _NotSetType()
 
 
-def is_primitive(value):
-    """
-    Determine if a value is an instance of a "primitive" type.
-
-    Parameters
-    ----------
-    value : object
-        the value to test
-
-    Returns
-    -------
-    bool
-        True if the value is primitive, False otherwise
-    """
-    warnings.warn("asdf.util.is_primitive is deprecated", exceptions.AsdfDeprecationWarning)
-    return isinstance(value, (bool, int, float, complex, str)) or value is None
-
-
 def uri_match(pattern, uri):
     """
     Determine if a URI matches a URI pattern with possible
diff --git a/asdf/versioning.py b/asdf/versioning.py
index f6f06887..160c0f62 100644
--- a/asdf/versioning.py
+++ b/asdf/versioning.py
@@ -3,18 +3,15 @@ This module deals with things that change between different versions
 of the ASDF spec.
 """
 
-import warnings
 from functools import total_ordering
 
 import yaml
 from semantic_version import SimpleSpec, Version
 
-from .exceptions import AsdfDeprecationWarning
-
 _yaml_base_loader = yaml.CSafeLoader if getattr(yaml, "__with_libyaml__", None) else yaml.SafeLoader
 
 
-__all__ = ["AsdfVersion", "AsdfSpec", "AsdfVersionMixin", "split_tag_version", "join_tag_version"]
+__all__ = ["AsdfVersion", "AsdfVersionMixin", "split_tag_version", "join_tag_version"]
 
 
 def split_tag_version(tag):
@@ -33,34 +30,6 @@ def join_tag_version(name, version):
     return f"{name}-{version}"
 
 
-_version_map = {}
-
-
-def _get_version_map(version):
-    version_map = _version_map.get(version)
-
-    if version_map is None:
-        from .config import get_config
-
-        uri = f"http://stsci.edu/schemas/asdf/version_map-{version}"
-        # The following call to yaml.load is safe because we're
-        # using a loader that inherits from pyyaml's SafeLoader.
-        version_map = yaml.load(get_config().resource_manager[uri], Loader=_yaml_base_loader)  # noqa: S506
-
-        # Separate the core tags from the rest of the standard for convenience
-        version_map["core"] = {}
-        version_map["standard"] = {}
-        for tag_name, tag_version in version_map["tags"].items():
-            if tag_name.startswith("tag:stsci.edu:asdf/core"):
-                version_map["core"][tag_name] = tag_version
-            else:
-                version_map["standard"][tag_name] = tag_version
-
-        _version_map[version] = version_map
-
-    return version_map
-
-
 @total_ordering
 class AsdfVersionMixin:
     """This mix-in is required in order to impose the total ordering that we
@@ -114,43 +83,6 @@ class AsdfVersion(AsdfVersionMixin, Version):
         super().__init__(version)
 
 
-class AsdfSpec(SimpleSpec):
-    """
-    Deprecated.
-    """
-
-    def __init__(self, *args, **kwargs):
-        warnings.warn("AsdfSpec is deprecated.", AsdfDeprecationWarning)
-        super().__init__(*args, **kwargs)
-
-    def match(self, version):
-        if isinstance(version, (str, tuple, list)):
-            version = AsdfVersion(version)
-        return super().match(version)
-
-    def __iterate_versions(self, versions):
-        for v in versions:
-            yield AsdfVersion(v) if isinstance(v, (str, tuple, list)) else v
-
-    def select(self, versions):
-        return super().select(self.__iterate_versions(versions))
-
-    def filter(self, versions):
-        return super().filter(self.__iterate_versions(versions))
-
-    def __eq__(self, other):
-        """Equality between Spec and Version, string, or tuple, means match"""
-        if isinstance(other, SimpleSpec):
-            return super().__eq__(other)
-        return self.match(other)
-
-    def __ne__(self, other):
-        return not self.__eq__(other)
-
-    def __hash__(self):
-        return super().__hash__()
-
-
 supported_versions = [
     AsdfVersion("1.0.0"),
     AsdfVersion("1.1.0"),
diff --git a/asdf/yamlutil.py b/asdf/yamlutil.py
index 718bfecc..5615f4b8 100644
--- a/asdf/yamlutil.py
+++ b/asdf/yamlutil.py
@@ -318,7 +318,6 @@ def custom_tree_to_tagged_tree(tree, ctx, _serialization_context=None):
     return treeutil.walk_and_modify(
         tree,
         _walker,
-        ignore_implicit_conversion=ctx._ignore_implicit_conversion,
         # Walk the tree in preorder, so that extensions can return
         # container nodes with unserialized children.
         postorder=False,
@@ -362,7 +361,6 @@ def tagged_tree_to_custom_tree(tree, ctx, force_raw_types=False, _serialization_
     return treeutil.walk_and_modify(
         tree,
         _walker,
-        ignore_implicit_conversion=ctx._ignore_implicit_conversion,
         # Walk the tree in postorder, so that extensions receive
         # container nodes with children already deserialized.
         postorder=True,
diff --git a/changes/1852.removal.rst b/changes/1852.removal.rst
new file mode 100644
index 00000000..70b38712
--- /dev/null
+++ b/changes/1852.removal.rst
@@ -0,0 +1,1 @@
+Remove deprecated API. See docs for full details.
diff --git a/docs/asdf/whats_new.rst b/docs/asdf/whats_new.rst
index cad44093..8f9328cb 100644
--- a/docs/asdf/whats_new.rst
+++ b/docs/asdf/whats_new.rst
@@ -6,7 +6,93 @@
 What's New
 **********
 
-.. _whats_new_3.0.0:
+.. _whats_new_4.0.0:
+
+4.0.0
+=====
+
+Hi! Asdf 4.0.0 is a new major version including:
+
+- :ref:`removal of deprecated API <whats_new_4.0.0_removed>`
+- :ref:`changes to a few key defaults <whats_new_4.0.0_defaults>`
+
+.. _whats_new_4.0.0_removed:
+
+Removed API
+-----------
+
+- The ``copy_arrays`` argument for ``asdf.open`` and ``AsdfFile`` has been removed
+  and replaced by ``memmap`` (``memmap == not copy_arrays``).
+- ``ignore_version_mismatch`` has had no effect since asdf 3.0.0 and was removed.
+- the `asdf.util` submodule had several unused functions removed:
+  - ``filepath_to_url``, see ``pathlib.Path.as_uri`` as an alternative
+  - ``is_primitive``, use ``isinstance``
+  - ``iter_subclasses``, use ``object.__subclasses__``
+  - ``minversion``, see ``astropy.utils.minversion``
+  - ``resolve_name``, see ``astropy.utils.resolve_name``
+  - ``human_list``, use ``pprint`` or your own string formatting
+- ``versioning.AsdfSpec``, see `asdf.versioning.AsdfVersion` comparisons
+- ``asdf.testing.helpers.format_tag``, use your own string formatting
+- ``AsdfFile.version_map``, could have been removed with the legacy extension API
+- ``AsdfFile.resolve_and_inline``, use `AsdfFile.resolve_references` and ``all_array_storage=="inline"``
+- ``asdf.asdf``, the public items in this submodule are all in the top level `asdf` module
+- ``asdf.asdf.SerializationContext``, available at `asdf.extension.SerializationContext`
+- ``asdf.stream``, see `asdf.tags.core.Stream`
+- ``ignore_implicit_conversion`` has been removed (see :ref:`whats_new_4.0.0_implicit_conversion` below)
+- providing a tag uri within a schema ``$ref`` will no longer resolve to the schema uri associated with that tag
+
+.. _whats_new_4.0.0_defaults:
+
+New Defaults
+------------
+
+.. _whats_new_4.0.0_validation:
+
+Validation
+^^^^^^^^^^
+
+Several operations no longer automatically trigger tree validation.
+These changes were made to limit the number of times a tree is validated
+to allow incremental construction of trees and to improve performance.
+
+- providing a tree to ``AsdfFile.__init__`` no longer triggers validation
+- calling `AsdfFile.resolve_references` no longer triggers validation
+- assigning a new tree to `AsdfFile.tree` no longer triggers validation
+
+.. note::
+
+   Validation can be triggered with `AsdfFile.validate` and will
+   occur when writing to a file (or reading if ``AsdfConfig.validate_on_read``
+   is enabled).
+
+.. _whats_new_4.0.0_find_references:
+
+Find References
+^^^^^^^^^^^^^^^
+
+Similar to :ref:`whats_new_4.0.0_validation` several operations no longer
+automatically trigger `AsdfFile.find_references`:
+
+- `asdf.open` does not trigger `AsdfFile.find_references`
+- providing a tree (or `AsdfFile`) to ``AsdfFile.__init__`` no longer triggers `AsdfFile.find_references`
+
+.. note::
+
+   `AsdfFile.find_references` is only for JSON pointer references
+   which are most useful for external references. YAML anchors and
+   aliases are automatically resolved.
+
+.. _whats_new_4.0.0_implicit_conversion:
+
+Implicit Conversion
+^^^^^^^^^^^^^^^^^^^
+
+In older asdf versions ``namedtuple`` instances were automatically
+converted to lists when written to a file. When read back in the
+``namedtuple`` was not reconstructed and instead these objects were
+returned as lists. With asdf 4.0.0 this "implicit conversion" is
+no longer performed which allows extensions to implement converters
+for ``namedtuple`` instances.
 
 3.0.0
 =====
@@ -28,7 +114,7 @@ The following deprecated features are removed in asdf 3.0.0:
 
 Please see the links above or the :ref:`deprecations` for more details.
 
-.. _whats_new_3.0.0__ew_features:
+.. _whats_new_3.0.0__new_features:
 
 New features
 ------------


Test patch:
diff --git a/asdf/_tests/_regtests/test_1715.py b/asdf/_tests/_regtests/test_1715.py
index 18c140ea..5c6da8a3 100644
--- a/asdf/_tests/_regtests/test_1715.py
+++ b/asdf/_tests/_regtests/test_1715.py
@@ -1,5 +1,3 @@
-import pytest
-
 import asdf
 
 
@@ -22,7 +20,6 @@ def test_id_in_tree_breaks_ref(tmp_path):
     af["myref"] = {"$ref": "external.asdf#/thing"}
     af.write_to(main_fn)
 
-    with pytest.warns(asdf.exceptions.AsdfDeprecationWarning, match="find_references"):
-        with asdf.open(main_fn) as af:
-            af.resolve_references()
-            assert af["myref"] == 42
+    with asdf.open(main_fn) as af:
+        af.resolve_references()
+        assert af["myref"] == 42
diff --git a/asdf/_tests/test_api.py b/asdf/_tests/test_api.py
index 2b95734f..b3305951 100644
--- a/asdf/_tests/test_api.py
+++ b/asdf/_tests/test_api.py
@@ -4,7 +4,6 @@ import io
 import os
 import pathlib
 import sys
-import warnings
 
 import numpy as np
 import pytest
@@ -12,7 +11,7 @@ from numpy.testing import assert_array_equal
 
 import asdf
 from asdf import config_context, get_config, treeutil, versioning
-from asdf.exceptions import AsdfDeprecationWarning, AsdfPackageVersionWarning, ValidationError
+from asdf.exceptions import AsdfPackageVersionWarning, ValidationError
 from asdf.extension import ExtensionProxy
 from asdf.resource import ResourceMappingProxy
 from asdf.testing.helpers import roundtrip_object, yaml_to_asdf
@@ -261,9 +260,8 @@ def test_context_handler_resolve_and_inline(tmp_path):
     ff.write_to(str(tempname))
 
     with asdf.open(tempname) as newf:
-        with warnings.catch_warnings():
-            warnings.filterwarnings("ignore", "resolve_and_inline is deprecated", AsdfDeprecationWarning)
-            newf.resolve_and_inline()
+        newf.resolve_references()
+        newf.set_array_storage(newf["random"], "inline")
 
     with pytest.raises(OSError, match=r"Cannot access data from closed ASDF file"):
         newf.tree["random"][0]
diff --git a/asdf/_tests/test_asdf.py b/asdf/_tests/test_asdf.py
index 3945a658..3fa21ff3 100644
--- a/asdf/_tests/test_asdf.py
+++ b/asdf/_tests/test_asdf.py
@@ -6,7 +6,7 @@ from asdf import config_context
 from asdf._asdf import AsdfFile, open_asdf
 from asdf._entry_points import get_extensions
 from asdf._tests._helpers import assert_tree_match
-from asdf.exceptions import AsdfDeprecationWarning, AsdfWarning
+from asdf.exceptions import AsdfWarning
 from asdf.extension import ExtensionProxy
 from asdf.testing.helpers import yaml_to_asdf
 from asdf.versioning import AsdfVersion
@@ -102,14 +102,6 @@ def test_asdf_file_version():
         with pytest.raises(ValueError, match=r"ASDF Standard version .* is not supported by asdf==.*"):
             af.version = AsdfVersion("2.5.4")
 
-        af.version = "1.0.0"
-        with pytest.warns(AsdfDeprecationWarning, match="AsdfFile.version_map is deprecated"):
-            assert af.version_map["tags"]["tag:stsci.edu:asdf/core/asdf"] == "1.0.0"
-
-        af.version = "1.2.0"
-        with pytest.warns(AsdfDeprecationWarning, match="AsdfFile.version_map is deprecated"):
-            assert af.version_map["tags"]["tag:stsci.edu:asdf/core/asdf"] == "1.1.0"
-
 
 def test_asdf_file_extensions():
     af = AsdfFile()
diff --git a/asdf/_tests/test_deprecated.py b/asdf/_tests/test_deprecated.py
index 2a13151c..e69de29b 100644
--- a/asdf/_tests/test_deprecated.py
+++ b/asdf/_tests/test_deprecated.py
@@ -1,154 +0,0 @@
-import sys
-import warnings
-
-import numpy as np
-import pytest
-
-import asdf
-import asdf.testing.helpers
-from asdf.exceptions import AsdfDeprecationWarning, ValidationError
-
-
-def test_asdf_stream_deprecation():
-    with pytest.warns(AsdfDeprecationWarning, match="asdf.stream is deprecated"):
-        if "asdf.stream" in sys.modules:
-            del sys.modules["asdf.stream"]
-        import asdf.stream  # noqa: F401
-
-
-def test_asdf_asdf_SerializationContext_import_deprecation():
-    with warnings.catch_warnings():
-        warnings.filterwarnings(
-            "ignore",
-            category=AsdfDeprecationWarning,
-            message="asdf.asdf is deprecated. Please use asdf.AsdfFile and asdf.open",
-        )
-        warnings.filterwarnings(
-            "ignore",
-            category=AsdfDeprecationWarning,
-            message="asdf.asdf is deprecated",
-        )
-        with pytest.warns(AsdfDeprecationWarning, match="importing SerializationContext from asdf.asdf"):
-            from asdf.asdf import SerializationContext  # noqa: F401
-
-
-def test_asdf_util_human_list_deprecation():
-    with pytest.warns(AsdfDeprecationWarning, match="asdf.util.human_list is deprecated"):
-        asdf.util.human_list("a")
-
-
-def test_asdf_util_resolve_name_deprecation():
-    with pytest.warns(AsdfDeprecationWarning, match="asdf.util.resolve_name is deprecated"):
-        asdf.util.resolve_name("asdf.AsdfFile")
-
-
-def test_asdf_util_minversion_deprecation():
-    with pytest.warns(AsdfDeprecationWarning, match="asdf.util.minversion is deprecated"):
-        asdf.util.minversion("yaml", "3.1")
-
-
-def test_asdf_util_iter_subclasses_deprecation():
-    with pytest.warns(AsdfDeprecationWarning, match="asdf.util.iter_subclasses is deprecated"):
-        list(asdf.util.iter_subclasses(asdf.AsdfFile))
-
-
-def test_asdf_asdf_deprecation():
-    with pytest.warns(AsdfDeprecationWarning, match="asdf.asdf is deprecated"):
-        if "asdf.asdf" in sys.modules:
-            del sys.modules["asdf.asdf"]
-        import asdf.asdf  # noqa: F401
-
-
-def test_resolve_and_inline_deprecation():
-    with pytest.warns(AsdfDeprecationWarning, match="resolve_and_inline is deprecated"):
-        af = asdf.AsdfFile({"arr": np.arange(42)})
-        af.resolve_and_inline()
-
-
-def test_find_references_during_init_deprecation():
-    tree = {"a": 1, "b": {"$ref": "#"}}
-    with pytest.warns(AsdfDeprecationWarning, match="find_references during AsdfFile.__init__"):
-        asdf.AsdfFile(tree)
-
-
-def test_find_references_during_open_deprecation(tmp_path):
-    fn = tmp_path / "test.asdf"
-    af = asdf.AsdfFile()
-    af["a"] = 1
-    af["b"] = {"$ref": "#"}
-    af.write_to(fn)
-    with pytest.warns(AsdfDeprecationWarning, match="find_references during open"):
-        with asdf.open(fn) as af:
-            pass
-
-
-def test_asdf_util_is_primitive_deprecation():
-    with pytest.warns(AsdfDeprecationWarning, match="asdf.util.is_primitive is deprecated"):
-        asdf.util.is_primitive(1)
-
-
-def test_AsdfFile_tree_assignment_validation_deprecation():
-    af = asdf.AsdfFile()
-    with (
-        pytest.warns(AsdfDeprecationWarning, match="Validation on tree assignment is deprecated"),
-        pytest.raises(ValidationError),
-    ):
-        af.tree = {"history": 42}
-
-
-def test_AsdfFile_resolve_references_validation_deprecation():
-    af = asdf.AsdfFile()
-    af._tree["history"] = 42
-    with (
-        pytest.warns(AsdfDeprecationWarning, match="Validation during resolve_references is deprecated"),
-        pytest.raises(ValidationError),
-    ):
-        af.resolve_references()
-
-
-def test_AsdfFile_resolve_references_kwargs_deprecation():
-    af = asdf.AsdfFile()
-    with pytest.warns(AsdfDeprecationWarning, match="Passing kwargs to resolve_references is deprecated"):
-        af.resolve_references(foo=42)
-
-
-def test_AsdfFile_init_validation_deprecation():
-    with (
-        pytest.warns(AsdfDeprecationWarning, match="Validation during AsdfFile.__init__ is deprecated"),
-        pytest.raises(ValidationError),
-    ):
-        asdf.AsdfFile({"history": 42})
-
-
-def test_asdffile_version_map_deprecation():
-    af = asdf.AsdfFile()
-    with pytest.warns(AsdfDeprecationWarning, match="AsdfFile.version_map is deprecated"):
-        af.version_map
-
-
-def test_format_tag_deprecation():
-    with pytest.warns(AsdfDeprecationWarning, match="format_tag is deprecated"):
-        asdf.testing.helpers.format_tag("stsci.edu", "asdf", "1.0.0", "fits/fits")
-
-
-def test_asdf_util_filepath_to_url_deprecation(tmp_path):
-    with pytest.warns(AsdfDeprecationWarning, match="asdf.util.filepath_to_url is deprecated"):
-        asdf.util.filepath_to_url(str(tmp_path))
-
-
-@pytest.mark.parametrize("value", [True, False])
-def test_AsdfFile_ignore_implicit_conversion_deprecation(value):
-    with pytest.warns(AsdfDeprecationWarning, match="ignore_implicit_conversion is deprecated"):
-        asdf.AsdfFile({"a": 1}, ignore_implicit_conversion=value)
-
-
-@pytest.mark.parametrize("value", [True, False])
-def test_walk_and_modify_ignore_implicit_conversion_deprecation(value):
-    with pytest.warns(AsdfDeprecationWarning, match="ignore_implicit_conversion is deprecated"):
-        asdf.treeutil.walk_and_modify({}, lambda obj: obj, ignore_implicit_conversion=value)
-
-
-@pytest.mark.parametrize("value", [True, False])
-def test_ignore_version_mismatch_deprecation(value):
-    with pytest.warns(AsdfDeprecationWarning, match="ignore_version_mismatch is deprecated"):
-        asdf.AsdfFile({}, ignore_version_mismatch=value)
diff --git a/asdf/_tests/test_extension.py b/asdf/_tests/test_extension.py
index d9a16c54..d2e294b4 100644
--- a/asdf/_tests/test_extension.py
+++ b/asdf/_tests/test_extension.py
@@ -1,3 +1,4 @@
+import collections
 import fractions
 import sys
 
@@ -1047,3 +1048,33 @@ def test_extension_converter_by_class_path():
 
         converter = extension_manager.get_converter_for_type(mailbox.Mailbox)
         assert isinstance(converter.delegate, MailboxConverter)
+
+
+def test_named_tuple_extension():
+    Point = collections.namedtuple("Point", ["x", "y"])
+
+    class PointConverter:
+        tags = ["asdf://example.com/tags/point-1.0.0"]
+        types = [Point]
+
+        def to_yaml_tree(self, obj, tag, ctx):
+            return list(obj)
+
+        def from_yaml_tree(self, node, tag, ctx):
+            return Point(*node)
+
+    class PointExtension:
+        tags = PointConverter.tags
+        converters = [PointConverter()]
+        extension_uri = "asdf://example.com/extensions/point-1.0.0"
+
+    pt = Point(1, 2)
+
+    # without the extension we can't serialize this
+    with pytest.raises(AsdfSerializationError, match="is not serializable by asdf"):
+        roundtrip_object(pt)
+
+    with config_context() as cfg:
+        cfg.add_extension(PointExtension())
+        rpt = roundtrip_object(pt)
+        assert isinstance(rpt, Point)
diff --git a/asdf/_tests/test_file_format.py b/asdf/_tests/test_file_format.py
index 16f0aad4..c2d43365 100644
--- a/asdf/_tests/test_file_format.py
+++ b/asdf/_tests/test_file_format.py
@@ -99,7 +99,6 @@ def test_empty_file():
         assert len(ff._blocks.blocks) == 0
 
 
-@pytest.mark.filterwarnings("ignore::asdf.exceptions.AsdfDeprecationWarning")
 def test_not_asdf_file():
     buff = io.BytesIO(b"SIMPLE")
     buff.seek(0)
diff --git a/asdf/_tests/test_reference.py b/asdf/_tests/test_reference.py
index 8cbfb16b..f618865f 100644
--- a/asdf/_tests/test_reference.py
+++ b/asdf/_tests/test_reference.py
@@ -7,7 +7,6 @@ from numpy.testing import assert_array_equal
 
 import asdf
 from asdf import reference
-from asdf.exceptions import AsdfDeprecationWarning
 from asdf.tags.core import ndarray
 
 from ._helpers import assert_tree_match
@@ -80,8 +79,6 @@ def test_external_reference(tmp_path):
         assert_array_equal(ff.tree["internal"], exttree["cool_stuff"]["a"])
 
     with asdf.AsdfFile({}, uri=(tmp_path / "main.asdf").as_uri()) as ff:
-        # avoid passing tree to AsdfFile to avoid the deprecation warning, this can be updated
-        # when automatic find_references on AsdfFile.__init__ is removed
         ff.tree = tree
         ff.find_references()
         do_asserts(ff)
@@ -89,14 +86,12 @@ def test_external_reference(tmp_path):
         internal_path = os.path.join(str(tmp_path), "main.asdf")
         ff.write_to(internal_path)
 
-    with pytest.warns(AsdfDeprecationWarning, match="find_references during open"), asdf.open(internal_path) as ff:
-        # this can be updated to add a find_references call when the deprecated automatic
-        # find_references on open is removed
+    with asdf.open(internal_path) as ff:
+        ff.find_references()
         do_asserts(ff)
 
-    with pytest.warns(AsdfDeprecationWarning, match="find_references during open"), asdf.open(internal_path) as ff:
-        # this can be updated to add a find_references call when the deprecated automatic
-        # find_references on open is removed
+    with asdf.open(internal_path) as ff:
+        ff.find_references()
         assert len(ff._external_asdf_by_uri) == 0
         ff.resolve_references()
         assert len(ff._external_asdf_by_uri) == 2
@@ -124,16 +119,12 @@ def test_external_reference(tmp_path):
 def test_external_reference_invalid(tmp_path):
     tree = {"foo": {"$ref": "fail.asdf"}}
 
-    # avoid passing tree to AsdfFile to avoid the deprecation warning, this can be updated
-    # when automatic find_references on AsdfFile.__init__ is removed
     ff = asdf.AsdfFile()
     ff.tree = tree
     ff.find_references()
     with pytest.raises(ValueError, match=r"Resolved to relative URL"):
         ff.resolve_references()
 
-    # avoid passing tree to AsdfFile to avoid the deprecation warning, this can be updated
-    # when automatic find_references on AsdfFile.__init__ is removed
     ff = asdf.AsdfFile({}, uri="http://httpstat.us/404")
     ff.tree = tree
     ff.find_references()
@@ -141,8 +132,6 @@ def test_external_reference_invalid(tmp_path):
     with pytest.raises(IOError, match=msg):
         ff.resolve_references()
 
-    # avoid passing tree to AsdfFile to avoid the deprecation warning, this can be updated
-    # when automatic find_references on AsdfFile.__init__ is removed
     ff = asdf.AsdfFile({}, uri=(tmp_path / "main.asdf").as_uri())
     ff.tree = tree
     ff.find_references()
@@ -158,8 +147,6 @@ def test_external_reference_invalid_fragment(tmp_path):
 
     tree = {"foo": {"$ref": "external.asdf#/list_of_stuff/a"}}
 
-    # avoid passing tree to AsdfFile to avoid the deprecation warning, this can be updated
-    # when automatic find_references on AsdfFile.__init__ is removed
     with asdf.AsdfFile({}, uri=(tmp_path / "main.asdf").as_uri()) as ff:
         ff.tree = tree
         ff.find_references()
@@ -168,8 +155,6 @@ def test_external_reference_invalid_fragment(tmp_path):
 
     tree = {"foo": {"$ref": "external.asdf#/list_of_stuff/3"}}
 
-    # avoid passing tree to AsdfFile to avoid the deprecation warning, this can be updated
-    # when automatic find_references on AsdfFile.__init__ is removed
     with asdf.AsdfFile({}, uri=(tmp_path / "main.asdf").as_uri()) as ff:
         ff.tree = tree
         ff.find_references()
@@ -194,12 +179,8 @@ def test_make_reference(tmp_path):
 
         ff.write_to(os.path.join(str(tmp_path), "source.asdf"))
 
-    with (
-        pytest.warns(AsdfDeprecationWarning, match="find_references during open"),
-        asdf.open(os.path.join(str(tmp_path), "source.asdf")) as ff,
-    ):
-        # this can be updated to add a find_references call when the deprecated automatic
-        # find_references on open is removed
+    with (asdf.open(os.path.join(str(tmp_path), "source.asdf")) as ff,):
+        ff.find_references()
         assert ff.tree["ref"]._uri == "external.asdf#f~0o~0o~1/a"
 
 
@@ -208,8 +189,7 @@ def test_internal_reference(tmp_path):
 
     tree = {"foo": 2, "bar": {"$ref": "#"}}
 
-    with pytest.warns(AsdfDeprecationWarning, match="find_references during AsdfFile.__init__"):
-        ff = asdf.AsdfFile(tree)
+    ff = asdf.AsdfFile(tree)
     ff.find_references()
     assert isinstance(ff.tree["bar"], reference.Reference)
     ff.resolve_references()
diff --git a/asdf/_tests/test_schema.py b/asdf/_tests/test_schema.py
index a7273270..b8b8bef6 100644
--- a/asdf/_tests/test_schema.py
+++ b/asdf/_tests/test_schema.py
@@ -8,7 +8,7 @@ from numpy.testing import assert_array_equal
 
 import asdf
 from asdf import config_context, constants, get_config, schema, tagged, util, yamlutil
-from asdf.exceptions import AsdfConversionWarning, AsdfDeprecationWarning, AsdfWarning, ValidationError
+from asdf.exceptions import AsdfConversionWarning, AsdfWarning, ValidationError
 from asdf.extension import TagDefinition
 from asdf.testing.helpers import yaml_to_asdf
 
@@ -136,30 +136,6 @@ required: [foobar]
     schema.check_schema(schema_tree)
 
 
-def test_load_schema_with_full_tag(tmp_path):
-    schema_def = """
-%YAML 1.1
----
-$schema: "http://stsci.edu/schemas/asdf/asdf-schema-1.0.0"
-id: "http://stsci.edu/schemas/asdf/nugatory/nugatory-1.0.0"
-tag: "tag:stsci.edu:asdf/nugatory/nugatory-1.0.0"
-
-type: object
-properties:
-  foobar:
-      $ref: "tag:stsci.edu:asdf/core/ndarray-1.0.0"
-
-required: [foobar]
-...
-    """
-    schema_path = tmp_path / "nugatory.yaml"
-    schema_path.write_bytes(schema_def.encode())
-
-    with pytest.warns(AsdfDeprecationWarning, match="Resolving by tag is deprecated"):
-        schema_tree = schema.load_schema(str(schema_path), resolve_references=True)
-    schema.check_schema(schema_tree)
-
-
 def test_load_schema_with_file_url(tmp_path):
     schema_def = """
 %YAML 1.1
diff --git a/asdf/_tests/test_util.py b/asdf/_tests/test_util.py
index ca64a7a4..97923e79 100644
--- a/asdf/_tests/test_util.py
+++ b/asdf/_tests/test_util.py
@@ -1,23 +1,11 @@
 import contextlib
 import io
-import warnings
 
 import numpy as np
 import pytest
 
 import asdf
 from asdf import generic_io, util
-from asdf.exceptions import AsdfDeprecationWarning
-
-
-def test_is_primitive():
-    with warnings.catch_warnings():
-        warnings.filterwarnings("ignore", "asdf.util.is_primitive is deprecated", AsdfDeprecationWarning)
-        for value in [None, "foo", 1, 1.39, 1 + 1j, True]:
-            assert util.is_primitive(value) is True
-
-        for value in [[], (), {}, set()]:
-            assert util.is_primitive(value) is False
 
 
 def test_not_set():
@@ -104,25 +92,6 @@ def test_get_file_type(content, expected_type):
     assert fd.read() == content
 
 
-def test_minversion():
-    import numpy as np
-    import yaml
-
-    good_versions = ["1.16", "1.16.1", "1.16.0.dev", "1.16dev"]
-    bad_versions = ["100000", "100000.2rc1"]
-    with warnings.catch_warnings():
-        warnings.filterwarnings("ignore", "asdf.util.minversion", AsdfDeprecationWarning)
-        for version in good_versions:
-            assert util.minversion(np, version)
-            assert util.minversion("numpy", version)
-        for version in bad_versions:
-            assert not util.minversion(np, version)
-            assert not util.minversion("numpy", version)
-
-        assert util.minversion(yaml, "3.1")
-        assert util.minversion("yaml", "3.1")
-
-
 @pytest.mark.parametrize("input_type", ["filename", "binary_file", "generic_file"])
 @pytest.mark.parametrize("tagged", [True, False])
 def test_load_yaml(tmp_path, input_type, tagged):
diff --git a/asdf/_tests/test_versioning.py b/asdf/_tests/test_versioning.py
index 9f702a19..58e419a3 100644
--- a/asdf/_tests/test_versioning.py
+++ b/asdf/_tests/test_versioning.py
@@ -1,10 +1,6 @@
 from itertools import combinations
 
-import pytest
-
-from asdf.exceptions import AsdfDeprecationWarning
 from asdf.versioning import (
-    AsdfSpec,
     AsdfVersion,
     asdf_standard_development_version,
     default_version,
@@ -171,113 +167,3 @@ def test_version_and_tuple_inequality():
     assert (2, 0, 1) >= version
     assert (2, 1, 0) >= version
     assert (2, 1, 1) >= version
-
-
-def test_spec_version_match():
-    with pytest.warns(AsdfDeprecationWarning, match="AsdfSpec is deprecated"):
-        spec = AsdfSpec(">=1.1.0")
-
-    assert spec.match(AsdfVersion("1.1.0"))
-    assert spec.match(AsdfVersion("1.2.0"))
-    assert not spec.match(AsdfVersion("1.0.0"))
-    assert not spec.match(AsdfVersion("1.0.9"))
-
-
-def test_spec_version_select():
-    with pytest.warns(AsdfDeprecationWarning, match="AsdfSpec is deprecated"):
-        spec = AsdfSpec(">=1.1.0")
-
-    versions = [AsdfVersion(x) for x in ["1.0.0", "1.0.9", "1.1.0", "1.2.0"]]
-    assert spec.select(versions) == "1.2.0"
-    assert spec.select(versions[:-1]) == "1.1.0"
-    assert spec.select(versions[:-2]) is None
-
-
-def test_spec_version_filter():
-    with pytest.warns(AsdfDeprecationWarning, match="AsdfSpec is deprecated"):
-        spec = AsdfSpec(">=1.1.0")
-
-    versions = [AsdfVersion(x) for x in ["1.0.0", "1.0.9", "1.1.0", "1.2.0"]]
-    for x, y in zip(spec.filter(versions), ["1.1.0", "1.2.0"]):
-        assert x == y
-
-
-def test_spec_string_match():
-    with pytest.warns(AsdfDeprecationWarning, match="AsdfSpec is deprecated"):
-        spec = AsdfSpec(">=1.1.0")
-
-    assert spec.match("1.1.0")
-    assert spec.match("1.2.0")
-    assert not spec.match("1.0.0")
-    assert not spec.match("1.0.9")
-
-
-def test_spec_string_select():
-    with pytest.warns(AsdfDeprecationWarning, match="AsdfSpec is deprecated"):
-        spec = AsdfSpec(">=1.1.0")
-
-    versions = ["1.0.0", "1.0.9", "1.1.0", "1.2.0"]
-    assert spec.select(versions) == "1.2.0"
-    assert spec.select(versions[:-1]) == "1.1.0"
-    assert spec.select(versions[:-2]) is None
-
-
-def test_spec_string_filter():
-    with pytest.warns(AsdfDeprecationWarning, match="AsdfSpec is deprecated"):
-        spec = AsdfSpec(">=1.1.0")
-
-    versions = ["1.0.0", "1.0.9", "1.1.0", "1.2.0"]
-    for x, y in zip(spec.filter(versions), ["1.1.0", "1.2.0"]):
-        assert x == y
-
-
-def test_spec_tuple_match():
-    with pytest.warns(AsdfDeprecationWarning, match="AsdfSpec is deprecated"):
-        spec = AsdfSpec(">=1.1.0")
-
-    assert spec.match((1, 1, 0))
-    assert spec.match((1, 2, 0))
-    assert not spec.match((1, 0, 0))
-    assert not spec.match((1, 0, 9))
-
-
-def test_spec_tuple_select():
-    with pytest.warns(AsdfDeprecationWarning, match="AsdfSpec is deprecated"):
-        spec = AsdfSpec(">=1.1.0")
-
-    versions = [(1, 0, 0), (1, 0, 9), (1, 1, 0), (1, 2, 0)]
-    assert spec.select(versions) == "1.2.0"
-    assert spec.select(versions[:-1]) == "1.1.0"
-    assert spec.select(versions[:-2]) is None
-
-
-def test_spec_tuple_filter():
-    with pytest.warns(AsdfDeprecationWarning, match="AsdfSpec is deprecated"):
-        spec = AsdfSpec(">=1.1.0")
-
-    versions = [(1, 0, 0), (1, 0, 9), (1, 1, 0), (1, 2, 0)]
-    for x, y in zip(spec.filter(versions), ["1.1.0", "1.2.0"]):
-        assert x == y
-
-
-def test_spec_equal():
-    """Make sure that equality means match"""
-    with pytest.warns(AsdfDeprecationWarning, match="AsdfSpec is deprecated"):
-        spec = AsdfSpec(">=1.2.0")
-    version0 = AsdfVersion("1.1.0")
-    version1 = AsdfVersion("1.3.0")
-
-    assert spec != version0
-    assert version0 != spec
-    assert spec == version1
-    assert version1 == spec
-
-    assert spec != "1.1.0"
-    assert "1.1.0" != spec
-    assert spec == "1.3.0"
-    assert "1.3.0" == spec
-
-    assert spec != (1, 1, 0)
-    assert (1, 1, 0) != spec
-    assert spec == (1, 3, 0)
-    assert (1, 3, 0) == spec
diff --git a/asdf/_tests/test_yaml.py b/asdf/_tests/test_yaml.py
index 8c14abff..b02d4e2f 100644
--- a/asdf/_tests/test_yaml.py
+++ b/asdf/_tests/test_yaml.py
@@ -1,8 +1,7 @@
 import contextlib
 import io
 import re
-from collections import OrderedDict, namedtuple
-from typing import NamedTuple
+from collections import OrderedDict
 
 import numpy as np
 import pytest
@@ -10,7 +9,7 @@ import yaml
 
 import asdf
 from asdf import tagged, treeutil, yamlutil
-from asdf.exceptions import AsdfConversionWarning, AsdfDeprecationWarning, AsdfSerializationError, AsdfWarning
+from asdf.exceptions import AsdfConversionWarning, AsdfSerializationError
 from asdf.testing.helpers import yaml_to_asdf
 
 
@@ -108,14 +107,6 @@ def test_arbitrary_python_object():
         ff.write_to(buff)
 
 
-def run_tuple_test(input_tree, **kwargs):
-    content, tree = _roundtrip(input_tree, **kwargs)
-
-    assert b"tuple" not in content
-    assert isinstance(tree["val"], list)
-    return content, tree
-
-
 def test_python_tuple():
     """
     We don't want to store tuples as tuples, because that's not a
@@ -125,7 +116,10 @@ def test_python_tuple():
 
     input_tree = {"val": (1, 2, 3)}
 
-    run_tuple_test(input_tree)
+    content, tree = _roundtrip(input_tree)
+
+    assert b"tuple" not in content
+    assert isinstance(tree["val"], list)
 
 
 @contextlib.contextmanager
@@ -143,75 +137,6 @@ def multi_warn(category, matches):
         assert found, f"Did not raise {category} with message matching {match}"
 
 
-def test_named_tuple_collections():
-    """
-    Ensure that we are able to serialize a collections.namedtuple.
-    """
-
-    nt = namedtuple("TestNamedTuple1", ("one", "two", "three"))
-
-    input_tree = {"val": nt(1, 2, 3)}
-
-    with multi_warn(AsdfDeprecationWarning, ["ignore_implicit_conversion", "implicit conversion is deprecated"]):
-        run_tuple_test(input_tree, init_kwargs={"ignore_implicit_conversion": True})
-
-
-def test_named_tuple_typing():
-    """
-    Ensure that we are able to serialize a typing.NamedTuple.
-    """
-
-    class NT(NamedTuple):
-        one: int
-        two: int
-        three: int
-
-    input_tree = {"val": NT(1, 2, 3)}
-
-    with multi_warn(AsdfDeprecationWarning, ["ignore_implicit_conversion", "implicit conversion is deprecated"]):
-        run_tuple_test(input_tree, init_kwargs={"ignore_implicit_conversion": True})
-
-
-def test_named_tuple_collections_recursive():
-    nt = namedtuple("TestNamedTuple3", ("one", "two", "three"))
-
-    input_tree = {"val": nt(1, 2, np.ones(3))}
-
-    with multi_warn(AsdfDeprecationWarning, ["ignore_implicit_conversion", "implicit conversion is deprecated"]):
-        _, tree = run_tuple_test(input_tree, init_kwargs={"ignore_implicit_conversion": True})
-    assert (tree["val"][2] == np.ones(3)).all()
-
-
-def test_named_tuple_typing_recursive(tmp_path):
-    class NT(NamedTuple):
-        one: int
-        two: int
-        three: np.ndarray
-
-    input_tree = {"val": NT(1, 2, np.ones(3))}
-
-    with multi_warn(AsdfDeprecationWarning, ["ignore_implicit_conversion", "implicit conversion is deprecated"]):
-        _, tree = run_tuple_test(input_tree, init_kwargs={"ignore_implicit_conversion": True})
-    assert (tree["val"][2] == np.ones(3)).all()
-
-
-def test_implicit_conversion_warning():
-    nt = namedtuple("TestTupleWarning", ("one", "two", "three"))
-
-    tree = {"val": nt(1, 2, np.ones(3))}
-
-    with (
-        pytest.warns(AsdfWarning, match=r"Failed to serialize instance"),
-        pytest.warns(AsdfDeprecationWarning, match=r"implicit conversion is deprecated"),
-        asdf.AsdfFile(tree),
-    ):
-        pass
-
-    with multi_warn(AsdfDeprecationWarning, ["ignore_implicit_conversion", "implicit conversion is deprecated"]):
-        with asdf.AsdfFile(tree, ignore_implicit_conversion=True):
-            pass
-
-
 @pytest.mark.xfail(reason="pyyaml has a bug and does not support tuple keys")
 def test_python_tuple_key():
     """
diff --git a/asdf/testing/helpers.py b/asdf/testing/helpers.py
index 0e31dec6..3dc5fb84 100644
--- a/asdf/testing/helpers.py
+++ b/asdf/testing/helpers.py
@@ -2,29 +2,9 @@
 Helpers for writing unit tests of ASDF support.
 """
 
-import warnings
 from io import BytesIO
 
 import asdf
-from asdf.exceptions import AsdfDeprecationWarning
-from asdf.versioning import AsdfSpec
-
-
-def format_tag(organization, standard, version, tag_name):
-    """
-    Format a YAML tag.
-    """
-    warnings.warn("format_tag is deprecated", AsdfDeprecationWarning)
-
-    tag = f"tag:{organization}:{standard}/{tag_name}"
-
-    if version is None:
-        return tag
-
-    if isinstance(version, AsdfSpec):
-        version = str(version.spec)
-
-    return f"{tag}-{version}"
 
 
 def roundtrip_object(obj, version=None):
diff --git a/pytest_asdf/plugin.py b/pytest_asdf/plugin.py
index 6f5e60e7..d1f5b5de 100644
--- a/pytest_asdf/plugin.py
+++ b/pytest_asdf/plugin.py
@@ -1,7 +1,6 @@
 import io
 import os
 import pathlib
-import warnings
 from dataclasses import dataclass
 
 import numpy as np
@@ -41,12 +40,6 @@ def pytest_addoption(parser):
         type="bool",
         default=False,
     )
-    parser.addini(
-        "asdf_schema_ignore_version_mismatch",
-        "Set to true to disable warnings when missing explicit support for a tag",
-        type="string",
-        default="",
-    )
     parser.addoption("--asdf-tests", action="store_true", help="Enable ASDF schema tests")
 
 
@@ -282,14 +275,6 @@ def pytest_collect_file(file_path, parent):
     skip_examples = parent.config.getini("asdf_schema_skip_examples")
     validate_default = parent.config.getini("asdf_schema_validate_default")
     ignore_unrecognized_tag = parent.config.getini("asdf_schema_ignore_unrecognized_tag")
-    ignore_version_mismatch = parent.config.getini("asdf_schema_ignore_version_mismatch")
-    if ignore_version_mismatch != "":
-        from asdf.exceptions import AsdfDeprecationWarning
-
-        warnings.warn(
-            "asdf_schema_ignore_version_mismatch is deprecated and has done nothing since asdf 3.0.0",
-            AsdfDeprecationWarning,
-        )
 
     skip_tests = _parse_test_list(parent.config.getini("asdf_schema_skip_tests"))
     xfail_tests = _parse_test_list(parent.config.getini("asdf_schema_xfail_tests"))