Repository: digital-asset/dazl-client (language: python)

Issue:
python: Drop dazl's deprecated type hierarchy
python:
* Drop dazl's deprecated type hierarchy in `dazl.model.types` and `dazl.model.types_store`.
* Move some of `dazl.models.events` into `dazl.client.events`, as that code is only used there.

This is the first of a few steps to remove `dazl.models` altogether; _all_ of the types in this package will either move to `dazl.protocols` (as it pertains to the v8 API) or `dazl.client` (as it pertains to the v5 API). When the transition is complete and there are _no_ remaining references to `dazl.models`, a new compatibility layer will be reintroduced in `dazl.models` that merely imports `dazl.client` symbols so that existing imports do not break. This can only easily be done once `dazl.models` is ultimately removed, as right now `dazl.models` is imported in too many places and introducing the compatibility layer too early would result in circular dependencies.
python: Drop dazl's deprecated type hierarchy
python:
* Drop dazl's deprecated type hierarchy in `dazl.model.types` and `dazl.model.types_store`.
* Move some of `dazl.models.events` into `dazl.client.events`, as that code is only used there.

This is the first of a few steps to remove `dazl.models` altogether; _all_ of the types in this package will either move to `dazl.protocols` (as it pertains to the v8 API) or `dazl.client` (as it pertains to the v5 API). When the transition is complete and there are _no_ remaining references to `dazl.models`, a new compatibility layer will be reintroduced in `dazl.models` that merely imports `dazl.client` symbols so that existing imports do not break. This can only easily be done once `dazl.models` is ultimately removed, as right now `dazl.models` is imported in too many places and introducing the compatibility layer too early would result in circular dependencies.
python: Drop dazl's deprecated type hierarchy
python:
* Drop dazl's deprecated type hierarchy in `dazl.model.types` and `dazl.model.types_store`.
* Move some of `dazl.models.events` into `dazl.client.events`, as that code is only used there.

This is the first of a few steps to remove `dazl.models` altogether; _all_ of the types in this package will either move to `dazl.protocols` (as it pertains to the v8 API) or `dazl.client` (as it pertains to the v5 API). When the transition is complete and there are _no_ remaining references to `dazl.models`, a new compatibility layer will be reintroduced in `dazl.models` that merely imports `dazl.client` symbols so that existing imports do not break. This can only easily be done once `dazl.models` is ultimately removed, as right now `dazl.models` is imported in too many places and introducing the compatibility layer too early would result in circular dependencies.
python: Replace the contents of dazl/pretty on master with v7
Replace the contents of `dazl/pretty` on master with files from the v7 release branch.

This undoes some of the changes in #159, but also makes it so that `dazl/pretty` typechecks on master.

Depends on #211, which reintroduces the `dazl.model.types` symbols that `dazl/pretty` depends on in v7.


Discussion / hints:

Fix patch:
diff --git a/python/dazl/client/events.py b/python/dazl/client/events.py
index 46017d02..ac33e8c8 100644
--- a/python/dazl/client/events.py
+++ b/python/dazl/client/events.py
@@ -61,6 +61,8 @@ def handle(event: "BaseEvent") -> "T":
 def _template_reverse_globs(primary_only: bool, package_id: str, type_name: str) -> "Iterator[str]":
     """
     Return an iterator over strings that glob to a specified type.
+
+    This symbol is meant only for supporting the dazl v7 and is not a public API!
     """
     # support deprecated type identifiers for usages of this old API to preserve backwards
     # compatibility
diff --git a/python/dazl/client/ledger.py b/python/dazl/client/ledger.py
index 49d513ab..50c29488 100644
--- a/python/dazl/client/ledger.py
+++ b/python/dazl/client/ledger.py
@@ -5,12 +5,15 @@
 Types that describe the behavior of the ledger itself.
 """
 from dataclasses import dataclass
-from typing import Any
+from typing import TYPE_CHECKING
 import warnings
 
 from ..ledger.pkgloader_aio import PackageLoader
 from ..protocols.serializers import Serializer
 
+if TYPE_CHECKING:
+    from ..model.types_store import PackageStore
+
 __all__ = ["LedgerMetadata"]
 
 
@@ -24,7 +27,7 @@ class LedgerMetadata:
     package_loader: "PackageLoader"
     serializer: "Serializer"
     protocol_version: str
-    _store: Any
+    _store: "PackageStore"
 
     def __init__(self, ledger_id, package_loader, serializer, protocol_version, store):
         object.__setattr__(self, "ledger_id", ledger_id)
@@ -34,7 +37,7 @@ def __init__(self, ledger_id, package_loader, serializer, protocol_version, stor
         object.__setattr__(self, "_store", store)
 
     @property
-    def store(self) -> "Any":
+    def store(self) -> "PackageStore":
         if self._store is None:
             raise Exception("eager_package_fetch is disabled, which disables the PackageStore")
 
diff --git a/python/dazl/client/pkg_loader.py b/python/dazl/client/pkg_loader.py
index 6cfa666a..30f52857 100644
--- a/python/dazl/client/pkg_loader.py
+++ b/python/dazl/client/pkg_loader.py
@@ -3,8 +3,11 @@
 
 import warnings
 
-from dazl.ledger.pkgloader_aio_compat import PackageLoader, SyncPackageService
+from ..ledger.pkgloader_aio_compat import PackageLoader, SyncPackageService
 
-warnings.warn("dazl.client.pkg_loader is deprecated; use dazl.protocols.pkgloader_aio instead.")
+warnings.warn(
+    "dazl.client.pkg_loader is deprecated; use dazl.protocols.pkgloader_aio instead.",
+    DeprecationWarning,
+)
 
 __all__ = ["SyncPackageService", "PackageLoader"]
diff --git a/python/dazl/damlast/compat.py b/python/dazl/damlast/compat.py
new file mode 100644
index 00000000..70dd7968
--- /dev/null
+++ b/python/dazl/damlast/compat.py
@@ -0,0 +1,81 @@
+# Copyright (c) 2017-2021 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
+# SPDX-License-Identifier: Apache-2.0
+
+"""
+Compatibility methods
+---------------------
+
+These functions should only be used internally by code that is helping in the transition from the
+deprecated :class:`dazl.model.types.Type` hierarchy to :class:`dazl.damlast.daml_lf_1.Type`.
+These functions will be removed WITHOUT an intermediate deprecation warning, as they are considered
+internal only.
+"""
+from typing import TYPE_CHECKING, Tuple, Union
+import warnings
+
+from .daml_lf_1 import TypeConName
+
+if TYPE_CHECKING:
+    # avoid import cycles; `dazl.model` should depend on `dazl.damlast`, and not the other way
+    # around
+    with warnings.catch_warnings():
+        warnings.simplefilter("ignore", DeprecationWarning)
+        from ..model.types import Type as DeprecatedType, TypeReference as DeprecatedTypeReference
+
+
+__all__ = ["parse_template"]
+
+warnings.warn("dazl.damlast.compat is deprecated", DeprecationWarning)
+
+
+def parse_template(
+    template_id: "Union[str, DeprecatedType, TypeConName]",
+) -> "Tuple[TypeConName, DeprecatedTypeReference]":
+    """
+    Return both the "new-style" Type and the "old-style" Type for _either_ a string, "new-style"
+    Type, or "old-style" Type. This is used in places where we can't easily mark a symbol as
+    deprecated (particularly, constructors for types that are NOT to be deprecated as part of this
+    transition).
+    """
+    with warnings.catch_warnings():
+        warnings.simplefilter("ignore", DeprecationWarning)
+        from ..model.types import (
+            Type as DeprecatedType,
+            TypeReference as DeprecatedTypeReference,
+            UnresolvedTypeReference,
+            RecordType,
+        )
+    from ..damlast.lookup import parse_type_con_name
+
+    if isinstance(template_id, str):
+        name = parse_type_con_name(template_id, allow_deprecated_identifiers=True)
+        return name, DeprecatedTypeReference(name)
+
+    elif isinstance(template_id, TypeConName):
+        return template_id, DeprecatedTypeReference(template_id)
+
+    elif isinstance(template_id, DeprecatedType):
+        warnings.warn(
+            "usage of dazl.model.types.Type is deprecated; use TypeConName instead",
+            DeprecationWarning,
+            stacklevel=3,
+        )
+
+        if isinstance(template_id, DeprecatedTypeReference):
+            return template_id.con, template_id
+
+        elif isinstance(template_id, UnresolvedTypeReference):
+            name = parse_type_con_name(template_id.name, allow_deprecated_identifiers=True)
+            return name, DeprecatedTypeReference(name)
+
+        elif isinstance(template_id, RecordType):
+            if template_id.name is None:
+                raise ValueError(
+                    "template_id must point to a named record that corresponds to a template"
+                )
+
+            return template_id.name.con, DeprecatedTypeReference(template_id.name.con)
+        else:
+            raise ValueError("unknown dazl.model.types.Type implementation")
+    else:
+        raise ValueError("template_id must be a TypeConName")
diff --git a/python/dazl/damlast/lookup.py b/python/dazl/damlast/lookup.py
index 951dac3b..44ff7c19 100644
--- a/python/dazl/damlast/lookup.py
+++ b/python/dazl/damlast/lookup.py
@@ -27,6 +27,7 @@
     Tuple,
     Union,
 )
+import warnings
 
 from .daml_lf_1 import (
     Archive,
@@ -43,6 +44,7 @@
 )
 from .errors import NameNotFoundError, PackageNotFoundError
 from .protocols import SymbolLookup
+from .util import package_local_name, package_ref
 
 __all__ = [
     "EmptyLookup",
@@ -56,15 +58,24 @@
     "validate_template",
 ]
 
-
 STAR = PackageRef("*")
 
 
-def parse_type_con_name(val: str) -> TypeConName:
+def parse_type_con_name(val: str, allow_deprecated_identifiers: bool = False) -> TypeConName:
     """
     Parse the given string as a type constructor.
     """
-    pkg, name = validate_template(val)
+    if allow_deprecated_identifiers:
+        warnings.warn(
+            "parse_type_con_name(..., allow_deprecated_identifiers=True) will be removed in dazl v8",
+            DeprecationWarning,
+            stacklevel=2,
+        )
+    with warnings.catch_warnings():
+        warnings.simplefilter("ignore", DeprecationWarning)
+        pkg, name = validate_template(
+            val, allow_deprecated_identifiers=allow_deprecated_identifiers
+        )
     module_name, _, entity_name = name.rpartition(":")
     module_ref = ModuleRef(pkg, DottedName(module_name.split(".")))
     return TypeConName(module_ref, entity_name.split("."))
@@ -78,12 +89,17 @@ def empty_lookup_impl(ref: Any) -> NoReturn:
         raise NameNotFoundError(ref)
 
 
-def validate_template(template: Union[None, str, TypeConName]) -> Tuple[PackageRef, str]:
+def validate_template(
+    template: Union[None, str, TypeConName], allow_deprecated_identifiers: bool = False
+) -> Tuple[PackageRef, str]:
     """
     Return a module and type name component from something that can be interpreted as a template.
 
     :param template:
         Any object that can be interpreted as an identifier for a template.
+    :param allow_deprecated_identifiers:
+        Allow deprecated identifiers (:class:`UnresolvedTypeReference` and a period delimiter
+        instead of a colon between module names and entity names).
     :return:
         A tuple of package ID and ``Module.Name:EntityName`` (the package-scoped identifier for the
         type). The special value ``'*'`` is used if either the package ID, module name, or both
@@ -91,12 +107,23 @@ def validate_template(template: Union[None, str, TypeConName]) -> Tuple[PackageR
     :raise ValueError:
         If the object could not be interpreted as a thing referring to a template.
     """
-    from ..damlast.daml_lf_1 import TypeConName
-    from ..damlast.util import package_local_name, package_ref
-
     if template == "*" or template is None:
         return STAR, "*"
 
+    if allow_deprecated_identifiers:
+        warnings.warn(
+            "validate_template(..., allow_deprecated_identifiers=True) will be removed in dazl v8",
+            DeprecationWarning,
+            stacklevel=2,
+        )
+        with warnings.catch_warnings():
+            warnings.simplefilter("ignore", DeprecationWarning)
+            # noinspection PyDeprecation
+            from ..model.types import UnresolvedTypeReference
+
+        if isinstance(template, UnresolvedTypeReference):
+            template = template.name
+
     if isinstance(template, str):
         components = template.split(":")
         if len(components) == 3:
@@ -115,6 +142,11 @@ def validate_template(template: Union[None, str, TypeConName]) -> Tuple[PackageR
                 raise ValueError("string must be in the format PKG_REF:MOD:ENTITY or MOD:ENTITY")
             return (STAR, f"{m}:{e}") if e != "*" else (PackageRef(m), "*")
 
+        elif len(components) == 1 and allow_deprecated_identifiers:
+            # no colon whatsoever
+            m, _, e = components[0].rpartition(".")
+            return STAR, f"{m}:{e}"
+
         else:
             raise ValueError("string must be in the format PKG_REF:MOD:ENTITY or MOD:ENTITY")
 
diff --git a/python/dazl/damlast/types.py b/python/dazl/damlast/types.py
index 092fd650..4750c8a2 100644
--- a/python/dazl/damlast/types.py
+++ b/python/dazl/damlast/types.py
@@ -1,10 +1,25 @@
 # Copyright (c) 2017-2021 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
 # SPDX-License-Identifier: Apache-2.0
-from typing import Callable
+
+from typing import TYPE_CHECKING, Callable, Optional, no_type_check
+import warnings
 
 from ._base import T
 from .daml_lf_1 import PrimType, Type
 
+if TYPE_CHECKING:
+    from ..model.types import Type as OldType
+
+
+__all__ = ["var", "match_prim_type", "get_old_type"]
+
+
+def var(var_: str) -> "Type":
+    warnings.warn("dazl.damlast.types.var is deprecated; use dazl.damlast.daml_types.var instead.")
+    from .daml_types import var as _var
+
+    return _var(var_)
+
 
 def match_prim_type(
     prim_type: "Type.Prim",
@@ -71,3 +86,199 @@ def match_prim_type(
         return on_gen_map(prim_type.args[0], prim_type.args[1])
     else:
         raise ValueError(f"undefined PrimType: {prim_type}")
+
+
+def get_old_type(daml_type: "Type") -> "OldType":
+    warnings.warn(
+        "dazl.model.types.Type and get_old_type are deprecated; "
+        "use the types defined in dazl.damlast.daml_lf_1 instead.",
+        DeprecationWarning,
+        stacklevel=2,
+    )
+    with warnings.catch_warnings():
+        warnings.simplefilter("ignore", DeprecationWarning)
+        from ..model.types import UnsupportedType
+
+        return daml_type.Sum_match(
+            _old_type_var,
+            _old_type_con,
+            _old_type_prim,
+            _old_type_syn,
+            _old_forall_type,
+            lambda tuple_: UnsupportedType("Tuple"),
+            lambda nat: UnsupportedType("Nat"),
+            lambda _: UnsupportedType("Syn"),
+        )
+
+
+def _old_type_var(var_: "Type.Var") -> "OldType":
+    from ..model.types import TypeApp, TypeVariable
+
+    core_type = TypeVariable(var_.var)
+    return (
+        TypeApp(core_type, [get_old_type(var_) for var_ in var_.args]) if var_.args else core_type
+    )
+
+
+def _old_type_con(con: "Type.Con") -> "OldType":
+    from ..model.types import TypeApp, TypeReference
+
+    core_type = TypeReference(con.tycon)
+    return TypeApp(core_type, [get_old_type(var) for var in con.args]) if con.args else core_type
+
+
+def _old_type_prim(prim: "Type.Prim") -> "OldType":
+    from ..model.types import UnsupportedType
+
+    return match_prim_type(
+        prim,
+        _old_scalar_type_unit,
+        _old_scalar_type_bool,
+        _old_scalar_type_integer,
+        _old_scalar_type_decimal,
+        _old_scalar_type_text,
+        _old_scalar_type_datetime,
+        _old_scalar_type_reltime,
+        _old_scalar_type_party,
+        _old_list_type,
+        _old_update_type,
+        lambda *args: UnsupportedType("Scenario"),
+        _old_scalar_type_date,
+        _old_scalar_contract_id_type,
+        _old_optional_type,
+        lambda *args: UnsupportedType("Arrow"),
+        _old_textmap_type,
+        _old_scalar_type_numeric,
+        _old_scalar_type_any,
+        _old_scalar_type_type_rep,
+        _old_genmap_type,
+    )
+
+
+@no_type_check
+def _old_type_syn(tysyn: "Type.Syn") -> "OldType":
+    from ..model.types import TypeApp, TypeReference
+
+    core_type = tysyn.tysyn
+    return (
+        TypeApp(TypeReference(core_type), [get_old_type(arg) for arg in tysyn.args])
+        if tysyn.args
+        else core_type
+    )
+
+
+def _old_forall_type(forall: "Type.Forall") -> "OldType":
+    from ..model.types import ForAllType
+
+    return ForAllType(forall.vars, get_old_type(forall.body))
+
+
+def _old_scalar_type_unit() -> "OldType":
+    from ..model.types import SCALAR_TYPE_UNIT
+
+    return SCALAR_TYPE_UNIT
+
+
+def _old_scalar_type_bool() -> "OldType":
+    from ..model.types import SCALAR_TYPE_BOOL
+
+    return SCALAR_TYPE_BOOL
+
+
+def _old_scalar_type_integer() -> "OldType":
+    from ..model.types import SCALAR_TYPE_INTEGER
+
+    return SCALAR_TYPE_INTEGER
+
+
+def _old_scalar_type_decimal() -> "OldType":
+    from ..model.types import SCALAR_TYPE_DECIMAL
+
+    return SCALAR_TYPE_DECIMAL
+
+
+def _old_scalar_type_numeric(nat: int = 10) -> "OldType":
+    from ..model.types import SCALAR_TYPE_NUMERIC
+
+    return SCALAR_TYPE_NUMERIC
+
+
+def _old_scalar_type_text() -> "OldType":
+    from ..model.types import SCALAR_TYPE_TEXT
+
+    return SCALAR_TYPE_TEXT
+
+
+def _old_scalar_type_datetime() -> "OldType":
+    from ..model.types import SCALAR_TYPE_DATETIME
+
+    return SCALAR_TYPE_DATETIME
+
+
+def _old_scalar_type_reltime() -> "OldType":
+    from ..model.types import SCALAR_TYPE_RELTIME
+
+    return SCALAR_TYPE_RELTIME
+
+
+def _old_scalar_type_party() -> "OldType":
+    from ..model.types import SCALAR_TYPE_PARTY
+
+    return SCALAR_TYPE_PARTY
+
+
+def _old_scalar_type_any() -> "OldType":
+    from ..model.types import SCALAR_TYPE_ANY
+
+    return SCALAR_TYPE_ANY
+
+
+def _old_list_type(arg: "Type") -> "OldType":
+    from ..model.types import ListType
+
+    return ListType(get_old_type(arg))
+
+
+def _old_update_type(arg: "Type") -> "OldType":
+    from ..model.types import UpdateType
+
+    return UpdateType(get_old_type(arg))
+
+
+def _old_scalar_type_date() -> "OldType":
+    from ..model.types import SCALAR_TYPE_DATE
+
+    return SCALAR_TYPE_DATE
+
+
+def _old_scalar_contract_id_type(arg: "Type") -> "OldType":
+    from ..model.types import ContractIdType
+
+    return ContractIdType(get_old_type(arg))
+
+
+def _old_optional_type(arg: "Type") -> "OldType":
+    from ..model.types import OptionalType
+
+    return OptionalType(get_old_type(arg))
+
+
+def _old_textmap_type(value_type: "Type") -> "OldType":
+    from ..model.types import TextMapType
+
+    return TextMapType(get_old_type(value_type))
+
+
+def _old_scalar_type_type_rep(arg: "Optional[Type]" = None) -> "OldType":
+    from ..model.types import TypeRefType, UnsupportedType
+
+    if arg is not None:
+        return TypeRefType(get_old_type(arg))
+    else:
+        return UnsupportedType("TypeRef")
+
+
+def _old_genmap_type(key_type: "Type", value_type: "Type") -> "OldType":
+    from ..model.types import GenMapType
+
+    return GenMapType(get_old_type(key_type), get_old_type(value_type))
diff --git a/python/dazl/damlast/util.py b/python/dazl/damlast/util.py
index 856fb22f..d424ed67 100644
--- a/python/dazl/damlast/util.py
+++ b/python/dazl/damlast/util.py
@@ -1,7 +1,8 @@
 # Copyright (c) 2017-2021 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
 # SPDX-License-Identifier: Apache-2.0
 
-from typing import Optional, Sequence, Union
+from typing import TYPE_CHECKING, Mapping, Optional, Sequence, Union
+import warnings
 
 from .daml_lf_1 import (
     UNIT,
@@ -18,6 +19,30 @@
     _Name,
 )
 
+if TYPE_CHECKING:
+    from ..model.types import Type as OldType, TypeReference
+    from ..model.types_store import PackageStore
+
+
+def var(var: str) -> "Type":
+    warnings.warn("dazl.damlast.util.var is deprecated; use dazl.damlast.daml_types.var instead.")
+    from .daml_types import var as _var
+
+    return _var(var)
+
+
+def values_by_module(
+    store: "PackageStore",
+) -> "Mapping[ModuleRef, Mapping[Sequence[str], Union[Expr, OldType]]]":
+    from collections import defaultdict
+
+    d = defaultdict(defaultdict)
+    for vn, vv in store._value_types.items():
+        d[vn.module][vn.name] = vv
+    for vn, vv in store._data_types.items():
+        d[vn.module][vn.name] = vv
+    return d
+
 
 # noinspection PyShadowingBuiltins
 def unpack_arrow_type(type: "Optional[Type]") -> "Sequence[Type]":
@@ -59,6 +84,36 @@ def pack_arrow_type(types: "Sequence[Type]") -> "Optional[Type]":
     return t
 
 
+# noinspection PyShadowingBuiltins
+def arrow_type(input: "Type", output: "Type") -> "Type":
+    """
+    Convenience function for constructing a :class:`Type` of ``prim`` that is an abstraction taking
+    the input type and returning the output type.
+    """
+    warnings.warn(
+        "arrow_type is deprecated; Use dazl.damlast.daml_types.Arrow instead",
+        DeprecationWarning,
+        stacklevel=2,
+    )
+    from .daml_types import Arrow
+
+    return Arrow(input, output)
+
+
+def list_type(elem_type: "Type") -> "Type":
+    """
+    Convenience function for constructing a :class:`Type` of ``prim`` list.
+    """
+    warnings.warn(
+        "list_type is deprecated; Use dazl.damlast.daml_types.List instead",
+        DeprecationWarning,
+        stacklevel=2,
+    )
+    from .daml_types import List
+
+    return List(elem_type)
+
+
 def type_var_with_kind(var: str, type: Kind = Kind(star=UNIT)):
     return TypeVarWithKind(var, type)
 
@@ -70,10 +125,12 @@ def def_value(name: "Union[str, Sequence[str]]", daml_type: "Type", expr: "Expr"
     return DefValue(name_with_type, expr, True, False, None)
 
 
-def package_ref(obj: "Union[ModuleRef, _Name]") -> "PackageRef":
+def package_ref(obj: "Union[ModuleRef, _Name, TypeReference]") -> "PackageRef":
     """
     Return the package ID for a :class:`ModuleRef` or a DAML name.
     """
+    from ..model.types import TypeReference
+
     # TODO: Rewrite for dazl 8.0.0 when the internal structure of a ModuleRef is changed.
     if isinstance(obj, ModuleRef):
         # noinspection PyProtectedMember
@@ -81,14 +138,19 @@ def package_ref(obj: "Union[ModuleRef, _Name]") -> "PackageRef":
     elif isinstance(obj, _Name):
         # noinspection PyProtectedMember
         return obj._module._package_id
+    elif isinstance(obj, TypeReference):
+        # noinspection PyProtectedMember
+        return obj.con._module._package_id
     else:
         raise ValueError(f"Could not extract a package_ref from {obj!r}")
 
 
-def module_name(obj: "Union[ModuleRef, _Name]") -> "DottedName":
+def module_name(obj: "Union[ModuleRef, _Name, TypeReference]") -> "DottedName":
     """
     Return the module name of a :class:`ModuleRef` or a DAML name.
     """
+    from ..model.types import TypeReference
+
     # TODO: Rewrite for dazl 8.0.0 when the internal structure of ModuleRefs and _Name are changed.
     if isinstance(obj, ModuleRef):
         # noinspection PyProtectedMember
@@ -96,27 +158,40 @@ def module_name(obj: "Union[ModuleRef, _Name]") -> "DottedName":
     elif isinstance(obj, _Name):
         # noinspection PyProtectedMember
         return obj._module._module_name
+    elif isinstance(obj, TypeReference):
+        # noinspection PyProtectedMember
+        return obj.con._module._module_name
     else:
         raise ValueError(f"Could not extract a module_name from {obj!r}")
 
 
-def module_ref(obj: "_Name") -> "ModuleRef":
+def module_ref(obj: "Union[_Name, TypeReference]") -> "ModuleRef":
     """
     Return the :class:`ModuleRef` of a DAML name.
     """
+    from ..model.types import TypeReference
+
     # TODO: Rewrite for dazl 8.0.0 when the internal structure of ModuleRefs and _Name are changed.
     if isinstance(obj, _Name):
         # noinspection PyProtectedMember
         return obj._module
+    elif isinstance(obj, TypeReference):
+        # noinspection PyProtectedMember
+        return obj.con._module
     else:
         raise ValueError(f"Could not extract a module_ref from {obj!r}")
 
 
-def package_local_name(obj: "_Name") -> str:
+def package_local_name(obj: "Union[_Name, TypeReference]") -> str:
     """
     Return the name of a DAML object, assuming that the referent exists in the same package as the
     target (i.e., _not_ scoped with a package ID).
     """
+    from ..model.types import TypeReference
+
+    if isinstance(obj, TypeReference):
+        obj = obj.con
+
     # TODO: Rewrite for dazl 8.0.0 when the internal structure of a ModuleRef is changed.
     if isinstance(obj, _Name):
         # noinspection PyProtectedMember
@@ -125,15 +200,23 @@ def package_local_name(obj: "_Name") -> str:
         raise ValueError(f"Could not extract a package_local_name from {obj!r}")
 
 
-def module_local_name(obj: "_Name") -> str:
+def module_local_name(obj: "Union[_Name, TypeReference]") -> str:
     """
     Return the name of a DAML object, assuming that the referent exists in the same module as the
     target (i.e., in the same package and in the same module).
     """
+    from ..model.types import TypeReference
+
+    if isinstance(obj, TypeReference):
+        obj = obj.con
+
     # TODO: Rewrite for dazl 8.0.0 when the internal structure of ModuleRefs and _Name are changed.
     if isinstance(obj, _Name):
         # noinspection PyProtectedMember
         return ".".join(obj._name)
+    elif isinstance(obj, TypeReference):
+        # noinspection PyProtectedMember
+        return ".".join(obj.con._name)
     else:
         raise ValueError(f"Could not extract a module_local_name from {obj!r}")
 
diff --git a/python/dazl/ledger/pkgloader_aio.py b/python/dazl/ledger/pkgloader_aio.py
index eba003b3..e28fbe30 100644
--- a/python/dazl/ledger/pkgloader_aio.py
+++ b/python/dazl/ledger/pkgloader_aio.py
@@ -47,6 +47,8 @@ class PackageLoader:
     information.
     """
 
+    _allow_deprecated_identifiers = True
+
     def __init__(
         self,
         package_lookup: "MultiPackageLookup",
@@ -104,7 +106,9 @@ async def do_with_retry(self, fn: "Callable[[], T]") -> "T":
                     )
                     raise
 
-                pkg_id, name = validate_template(ex.ref)
+                pkg_id, name = validate_template(
+                    ex.ref, allow_deprecated_identifiers=self._allow_deprecated_identifiers
+                )
                 if pkg_id == "*":
                     # we don't know what package contains this type, so we have no
                     # choice but to look in all known packages
diff --git a/python/dazl/ledger/pkgloader_aio_compat.py b/python/dazl/ledger/pkgloader_aio_compat.py
index da6646e5..11ab2efa 100644
--- a/python/dazl/ledger/pkgloader_aio_compat.py
+++ b/python/dazl/ledger/pkgloader_aio_compat.py
@@ -52,9 +52,11 @@ class PackageLoader(NewPackageLoader):
     Backwards-compatibility shim for dazl.client.pkg_loader.PackageLoader that exposes the same
     historical API but also emits a deprecation warning on construction.
 
-    This shim will be removed in v9.
+    This shim will be removed in v8.
     """
 
+    _allow_deprecated_identifiers = False
+
     def __init__(
         self,
         package_lookup: "MultiPackageLookup",
diff --git a/python/dazl/model/__init__.py b/python/dazl/model/__init__.py
index b94e1672..2fef16dd 100644
--- a/python/dazl/model/__init__.py
+++ b/python/dazl/model/__init__.py
@@ -13,17 +13,19 @@
 .. automodule:: dazl.model.lookup
 .. automodule:: dazl.model.network
 .. automodule:: dazl.model.reading
+.. automodule:: dazl.model.types
+.. automodule:: dazl.model.types_store
 .. automodule:: dazl.model.writing
 
 """
 
 import warnings
 
-from . import core, ledger, lookup, network, reading, writing
+from . import core, ledger, lookup, network, reading, types, writing
 
 __all__ = ["core", "ledger", "lookup", "network", "reading", "writing"]
 
 warnings.warn(
-    "dazl.model is deprecated; these types have moved to either dazl.protocols or dazl.client.",
+    "dazl.model is deprecated; these types have moved to either dazl.ledger or dazl.client.",
     DeprecationWarning,
 )
diff --git a/python/dazl/model/ledger.py b/python/dazl/model/ledger.py
index f3de912f..00f0a081 100644
--- a/python/dazl/model/ledger.py
+++ b/python/dazl/model/ledger.py
@@ -4,6 +4,10 @@
 """
 This module has been relocated to ``dazl.client.ledger``.
 """
+import warnings
+
 from ..client.ledger import LedgerMetadata
 
 __all__ = ["LedgerMetadata"]
+
+warnings.warn("dazl.model.ledger is deprecated; use dazl.client.ledger instead", DeprecationWarning)
diff --git a/python/dazl/model/lookup.py b/python/dazl/model/lookup.py
index d51923a5..b96fb2df 100644
--- a/python/dazl/model/lookup.py
+++ b/python/dazl/model/lookup.py
@@ -2,31 +2,42 @@
 # SPDX-License-Identifier: Apache-2.0
 
 """
-This module has been relocated to ``dazl.client.events`` or ``dazl.damlast.lookup``.
+This module has been relocated to ``dazl.damlast.lookup``.
 """
 
 from typing import TYPE_CHECKING, Any, Iterator, Tuple, Union
 import warnings
 
+from ..damlast.daml_lf_1 import PackageRef
+from ..damlast.lookup import validate_template as _validate_template
+
 __all__ = ["validate_template", "template_reverse_globs"]
 
 if TYPE_CHECKING:
     from ..damlast.daml_lf_1 import PackageRef
+warnings.warn(
+    "dazl.model.lookup is deprecated; use dazl.damlast.lookup instead.", DeprecationWarning
+)
+
 
+def validate_template(template) -> "Tuple[PackageRef, str]":
+    """
+    Return a module and type name component from something that can be interpreted as a template.
 
-def validate_template(template: "Any") -> "Tuple[Union[str, PackageRef], str]":
-    from ..damlast.lookup import validate_template as validate_template_new
+    This function is deprecated and will be removed in dazl v8; please use
+    ``dazl.damlast.lookup.validate_template`` instead.
 
+    :param template:
+        Any object that can be interpreted as an identifier for a template.
+    """
     warnings.warn(
-        "validate_template is deprecated; use dazl.damlast.lookup.validate_template",
+        "dazl.model.lookup.validate_template is deprecated and will be removed in dazl v8; "
+        "use dazl.damlast.lookup.validate_template instead",
         DeprecationWarning,
         stacklevel=2,
     )
 
-    if template == "*" or template is None:
-        return "*", "*"
-
-    return validate_template_new(template)
+    return _validate_template(template, allow_deprecated_identifiers=True)
 
 
 def template_reverse_globs(primary_only: bool, package_id: str, type_name: str) -> "Iterator[str]":
diff --git a/python/dazl/model/types.py b/python/dazl/model/types.py
new file mode 100644
index 00000000..cd3d5c84
--- /dev/null
+++ b/python/dazl/model/types.py
@@ -0,0 +1,1004 @@
+# Copyright (c) 2017-2021 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
+# SPDX-License-Identifier: Apache-2.0
+
+"""
+Type system types
+------------------
+
+The :mod:`dazl.model.types` module contains the Python classes used to represent the DAML type
+system.
+
++----------------------+---------------------------+
+| DAML type            | Python type               |
++======================+===========================+
+| ``Bool``             | ``bool``                  |
++----------------------+---------------------------+
+| ``Int``              | ``int``                   |
++----------------------+---------------------------+
+| ``Decimal``          | ``decimal.Decimal``       |
++----------------------+---------------------------+
+| ``[a]``              | ``list``                  |
++----------------------+---------------------------+
+
+.. autoclass:: Type
+.. autoclass:: ScalarType
+.. autoclass:: ListType
+.. autoclass:: RecordType
+.. autoclass:: VariantType
+.. autoclass:: UnsupportedType
+"""
+from typing import (
+    TYPE_CHECKING,
+    AbstractSet,
+    Any,
+    Callable,
+    ClassVar,
+    Collection,
+    Dict,
+    Mapping,
+    NewType,
+    Optional,
+    Sequence,
+    Tuple,
+    TypeVar,
+    Union,
+)
+import warnings
+
+from .. import LOG
+from ..damlast.daml_lf_1 import DottedName, Expr, ModuleRef, PackageRef, TypeConName
+from ..prim import ContractData, Party
+from ..util.typing import safe_cast, safe_dict_cast, safe_optional_cast
+
+warnings.warn("The symbols in dazl.model.types are deprecated", DeprecationWarning, stacklevel=2)
+
+DottedNameish = Union[str, Sequence[str]]
+
+T_co = TypeVar("T_co", covariant=True)
+
+if TYPE_CHECKING:
+    from .types_store import PackageStore
+
+
+# Reference to a ledger ID.
+LedgerId = NewType("LedgerId", str)
+
+# Reference to a package via a package identifier. The identifier is the ascii7
+# lowercase hex-encoded hash of the package contents found in the DAML LF Archive.
+#
+# This is re-exported from the daml_lf_1 as PackageId for backwards compatibility, and will
+# be removed in dazl v8.
+PackageId = PackageRef
+
+# A set of PackageId.
+PackageIdSet = AbstractSet[PackageId]
+
+
+def type_ref(s: str) -> "TypeReference":
+    """
+    Convenience method for creating a fully-qualified :class:`TypeReference`. A few formats are
+    supported:
+
+    "packageId:module:entity"
+    "module:entity@pkgid"
+    "module.entity@pkgid"
+    """
+    warnings.warn(
+        "type_ref is deprecated; there is no replacement", DeprecationWarning, stacklevel=2
+    )
+    with warnings.catch_warnings():
+        warnings.simplefilter("ignore", DeprecationWarning)
+
+        # Attempt to parse in an older format still used by Navigator for display purposes.
+        me, is_at, pkgid = s.partition("@")
+        if is_at:
+            # encourage people to use the new format
+            m, has_colon, e = me.partition(":")
+            if not has_colon:
+                m, _, e = me.rpartition(".")
+
+            pkg_ref = PackageRef(pkgid)
+            module_name = DottedName(m.split("."))
+            entity_name = e.split(".")
+        else:
+            components = s.split(":")
+            if len(components) != 3:
+                raise ValueError(f"could not parse as a template reference: {s!r}")
+
+            pkg_ref = PackageRef(components[0])
+            module_name = DottedName(components[1].split("."))
+            entity_name = components[2].split(".")
+
+        return TypeReference(
+            con=TypeConName(module=ModuleRef(pkg_ref, module_name), name=entity_name)
+        )
+
+
+def dotted_name(obj: DottedNameish) -> "Sequence[str]":
+    """
+    Sanitize a string or a tuple of strings to a dotted name.
+
+    :param obj: A string or tuple of strings.
+    :return: A tuple of strings.
+    """
+    if obj is None:
+        raise ValueError("DottedName must be a non-None value")
+    if isinstance(obj, str):
+        return tuple(obj.split("."))
+    if isinstance(obj, Collection):
+        for item in obj:
+            if not isinstance(item, str):
+                raise ValueError("DottedName's components must all be strings")
+        return tuple(obj)
+    else:
+        raise ValueError("could not convert to a sequence of str: {obj!r}")
+
+
+class NamedArgumentList(tuple):
+    """
+    A simple tuple to storing (name, value) pairs.
+    """
+
+    @property
+    def names(self):
+        return set(name for name, _ in self)
+
+
+def type_dispatch_table(
+    on_type_ref: Callable[["TypeReference"], T_co],
+    on_type_var: Callable[["TypeVariable"], T_co],
+    on_type_app: Callable[["TypeApp"], T_co],
+    on_scalar: Callable[["ScalarType"], T_co],
+    on_contract_id: Callable[["ContractIdType"], T_co],
+    on_optional: Callable[["OptionalType"], T_co],
+    on_list: Callable[["ListType"], T_co],
+    on_text_map: "Callable[[TextMapType], T_co]",
+    on_record: Callable[["RecordType"], T_co],
+    on_variant: Callable[["VariantType"], T_co],
+    on_enum: "Callable[[EnumType], T_co]",
+    on_unsupported: Callable[["UnsupportedType"], T_co],
+) -> Callable[["Type"], T_co]:
+    def _impl(tt: Type):
+        if isinstance(tt, TypeReference):
+            return on_type_ref(tt)
+        elif isinstance(tt, TypeVariable):
+            return on_type_var(tt)
+        elif isinstance(tt, TypeApp):
+            return on_type_app(tt)
+        elif isinstance(tt, ScalarType):
+            return on_scalar(tt)
+        elif isinstance(tt, ContractIdType):
+            return on_contract_id(tt)
+        elif isinstance(tt, OptionalType):
+            return on_optional(tt)
+        elif isinstance(tt, ListType):
+            return on_list(tt)
+        elif isinstance(tt, TextMapType):
+            return on_text_map(tt)
+        elif isinstance(tt, RecordType):
+            return on_record(tt)
+        elif isinstance(tt, VariantType):
+            return on_variant(tt)
+        elif isinstance(tt, EnumType):
+            return on_enum(tt)
+        elif isinstance(tt, UnsupportedType):
+            return on_unsupported(tt)
+        else:
+            # note to maintainers: if you modify the Type hierarchy, you must also maintain this
+            # poor man's pattern match over the hierarchy
+            LOG.error("Incomplete implementation of type_match! (when handling %r)", tt)
+            raise Exception(f"unknown Type subclass: {tt!r}")
+
+    return _impl
+
+
+def scalar_type_dispatch_table(
+    on_unit: "Callable[[], T_co]",
+    on_bool: "Callable[[], T_co]",
+    on_text: "Callable[[], T_co]",
+    on_int: "Callable[[], T_co]",
+    on_decimal: "Callable[[], T_co]",
+    on_party: "Callable[[], T_co]",
+    on_date: "Callable[[], T_co]",
+    on_datetime: "Callable[[], T_co]",
+    on_timedelta: "Callable[[], T_co]",
+) -> "Callable[[ScalarType], T_co]":
+    def _impl(tt: ScalarType):
+        st = safe_cast(ScalarType, tt)
+        if st == SCALAR_TYPE_UNIT:
+            return on_unit()
+        elif tt == SCALAR_TYPE_BOOL:
+            return on_bool()
+        elif tt == SCALAR_TYPE_TEXT or tt == SCALAR_TYPE_CHAR:
+            return on_text()
+        elif tt == SCALAR_TYPE_INTEGER:
+            return on_int()
+        elif tt == SCALAR_TYPE_DECIMAL or tt == SCALAR_TYPE_NUMERIC:
+            return on_decimal()
+        elif tt == SCALAR_TYPE_PARTY:
+            return on_party()
+        elif tt == SCALAR_TYPE_DATE:
+            return on_date()
+        elif tt == SCALAR_TYPE_DATETIME:
+            return on_datetime()
+        elif tt == SCALAR_TYPE_RELTIME:
+            return on_timedelta()
+        else:
+            # note to maintainers: if you modify the set of ScalarType instances, you must also
+            # maintain this poor man's pattern match over the hierarchy
+            LOG.error(
+                "Incomplete implementation of scalar_type_dispatch_table! (when handling %r)", tt
+            )
+            raise Exception(f"unknown ScalarType: {tt!r}")
+
+    return _impl
+
+
+class Type:
+    """
+    A DAML-defined type.
+    """
+
+    def __init__(self):
+        warnings.warn(
+            "dazl.model.types.Type and its subclasses are deprecated. "
+            "Use dazl.damlast.daml_lf_1.Type instead.",
+            DeprecationWarning,
+            stacklevel=2,
+        )
+
+    def __str__(self):
+        from ..pretty import DAML_PRETTY_PRINTER
+
+        return DAML_PRETTY_PRINTER.visit_type(self)
+
+
+class TypeApp(Type):
+    """
+    An application of one or more types to an open type.
+
+    This is basically Type.Con in the Protobuf declaration.
+    """
+
+    def __init__(self, body: Type, arguments: Sequence[Type]):
+        with warnings.catch_warnings():
+            warnings.simplefilter("ignore", DeprecationWarning)
+            # noinspection PyDeprecation
+            super().__init__()
+
+        warnings.warn(
+            "dazl.model.types.TypeApp and its subclasses are deprecated. "
+            "Use dazl.damlast.daml_lf_1.Type instead.",
+            DeprecationWarning,
+            stacklevel=2,
+        )
+
+        if not isinstance(body, Type):
+            raise ValueError(f"a Type is required here (got {body} instead)")
+        if not arguments:
+            raise ValueError("at least one type argument is required in a TypeApp")
+        self.body = body
+        self.arguments = tuple(arguments)
+
+    def __repr__(self):
+        return f"<TypeApp(body={self.body}, arguments={self.arguments}>"
+
+
+class TypeVariable(Type):
+    """
+    An unbound type in a Type expression.
+    """
+
+    __slots__ = ("name",)
+
+    def __init__(self, name: str):
+        with warnings.catch_warnings():
+            warnings.simplefilter("ignore", DeprecationWarning)
+            # noinspection PyDeprecation
+            super().__init__()
+
+        warnings.warn(
+            "dazl.model.types.TypeVar is deprecated. " "Use dazl.damlast.daml_lf_1.Type instead.",
+            DeprecationWarning,
+            stacklevel=2,
+        )
+        self.name = safe_cast(str, name)
+
+    def __str__(self):
+        return self.name
+
+    def __repr__(self):
+        return f"TypeVariable({self.name})"
+
+    def __eq__(self, other):
+        return isinstance(other, TypeVariable) and self.name == other.name
+
+    def __hash__(self):
+        return hash(self.name)
+
+
+class TypeReference(Type):
+    def __init__(self, con: "TypeConName"):
+        with warnings.catch_warnings():
+            warnings.simplefilter("ignore", DeprecationWarning)
+            # noinspection PyDeprecation
+            super().__init__()
+        warnings.warn(
+            "TypeReference is deprecated; in most cases, use TypeConName instead",
+            DeprecationWarning,
+            stacklevel=2,
+        )
+        self.con = con
+
+    def __str__(self):
+        return str(self.con)
+
+    def __eq__(self, other):
+        return isinstance(other, TypeReference) and self.con == other.con
+
+    def __hash__(self):
+        return hash(self.con)
+
+
+class UnresolvedTypeReference(Type):
+    """
+    A reference that may or may not ultimately resolve to a type.
+    """
+
+    def __init__(self, name: str):
+        with warnings.catch_warnings():
+            warnings.simplefilter("ignore", DeprecationWarning)
+            # noinspection PyDeprecation
+            super().__init__()
+        warnings.warn(
+            "UnresolvedTypeReference is deprecated; in most cases, use TypeConName instead",
+            DeprecationWarning,
+            stacklevel=2,
+        )
+        self.name = safe_cast(str, name)
+
+    def __str__(self):
+        return self.name
+
+    def __repr__(self):
+        return f"<UnresolvedTypeReference({self.name!r})>"
+
+    def __eq__(self, other):
+        return isinstance(other, UnresolvedTypeReference) and self.name == other.name
+
+    def __hash__(self):
+        return hash((UnresolvedTypeReference, self.name))
+
+
+class ConcreteType(Type):
+    pass
+
+
+class ScalarType(ConcreteType):
+    """
+    A DAML-defined type that represents a simple scalar value. You should not need to ever
+    construct instances of this directly; all scalar types are builtins.
+    """
+
+    __slots__ = ("name",)
+    name: str
+    BUILTINS: "ClassVar[Sequence[ScalarType]]"
+
+    def __init__(self, name: str):
+        """
+        Construct an object that references a scalar DAML type.
+
+        :param name: The name of this type as it is known in DAML.
+        """
+        with warnings.catch_warnings():
+            warnings.simplefilter("ignore", DeprecationWarning)
+            # noinspection PyDeprecation
+            super().__init__()
+        warnings.warn(
+            "ScalarType is deprecated; in most cases, use dazl.damlast.daml_lf_1 types instead",
+            DeprecationWarning,
+            stacklevel=2,
+        )
+        self.name = safe_cast(str, name)
+
+    def __repr__(self):
+        """
+        Return the DAML name of this type.
+        """
+        return self.name
+
+    def __hash__(self):
+        return hash((ScalarType, self.name))
+
+    def __eq__(self, other):
+        return isinstance(other, ScalarType) and self.name == other.name
+
+
+class _BuiltInParameterizedType(ConcreteType):
+    """
+    Convenience class that encapsulates commonalities for the built-in types that have one type
+    parameter.
+    """
+
+    __slots__ = ("type_parameter",)
+
+    def __init__(self, type_parameter: Type):
+        with warnings.catch_warnings():
+            warnings.simplefilter("ignore", DeprecationWarning)
+            # noinspection PyDeprecation
+            super().__init__()
+
+        warnings.warn(
+            "ContractIdType, and ListType, TypeRefType are deprecated; use dazl.damlast.daml_lf_1 types instead",
+            DeprecationWarning,
+            stacklevel=2,
+        )
+        self.type_parameter = safe_cast(Type, type_parameter)
+
+    def __repr__(self):
+        py_type = type(self).__name__
+        return f"<{py_type}({self.type_parameter!r})>"
+
+
+class ContractIdType(_BuiltInParameterizedType):
+    pass
+
+
+class ListType(_BuiltInParameterizedType):
+    pass
+
+
+class TypeRefType(_BuiltInParameterizedType):
+    pass
+
+
+class TextMapType(ConcreteType):
+    """
+    A DAML-defined TextMap.
+
+    Instance attributes:
+
+    .. attribute: TextMapType.value_type
+
+        The type of values in this map.
+    """
+
+    __slots__ = ("value_type",)
+
+    def __init__(self, value_type: Type):
+        with warnings.catch_warnings():
+            warnings.simplefilter("ignore", DeprecationWarning)
+            # noinspection PyDeprecation
+            super().__init__()
+        warnings.warn(
+            "TextMapType is deprecated; use dazl.damlast.daml_lf_1 types instead",
+            DeprecationWarning,
+            stacklevel=2,
+        )
+
+        self.value_type = safe_cast(Type, value_type)
+
+    def __repr__(self):
+        py_type = type(self).__name__
+        return f"<{py_type}({self.value_type!r})>"
+
+
+class GenMapType(ConcreteType):
+    """
+    A DAML-defined GenMap.
+
+    Instance attributes:
+
+    .. attribute: TextMapType.key_type
+
+        The type of keys in this map.
+
+    .. attribute: TextMapType.value_type
+
+        The type of values in this map.
+    """
+
+    __slots__ = "key_type", "value_type"
+
+    def __init__(self, key_type: Type, value_type: Type):
+        with warnings.catch_warnings():
+            warnings.simplefilter("ignore", DeprecationWarning)
+            # noinspection PyDeprecation
+            super().__init__()
+        warnings.warn(
+            "GenMapType is deprecated; use dazl.damlast.daml_lf_1 types instead",
+            DeprecationWarning,
+            stacklevel=2,
+        )
+
+        self.key_type = safe_cast(Type, key_type)
+        self.value_type = safe_cast(Type, value_type)
+
+    def __repr__(self):
+        py_type = type(self).__name__
+        return f"<{py_type}({self.key_type!r}, {self.value_type!r})>"
+
+
+class OptionalType(_BuiltInParameterizedType):
+    """
+    A DAML-defined Optional.
+
+    Instance attributes:
+
+    .. attribute: OptionalType.type_parameter
+
+        The type of value in the Optional.
+    """
+
+
+class UpdateType(_BuiltInParameterizedType):
+    pass
+
+
+class ForAllType(Type):
+    def __init__(self, type_vars, body_type):
+        with warnings.catch_warnings():
+            warnings.simplefilter("ignore", DeprecationWarning)
+            # noinspection PyDeprecation
+            super().__init__()
+        warnings.warn(
+            "ForAllType is deprecated; there is no replacement", DeprecationWarning, stacklevel=2
+        )
+
+        self.type_vars = type_vars
+        self.body_type = body_type
+
+    def __repr__(self):
+        return f"<ForAllType({self.type_vars}, {self.body_type})>"
+
+
+class _CompositeDataType(ConcreteType):
+    """
+    Either a :class:`RecordType` (product type) or a :class:`VariantType` (sum type).
+    """
+
+    def __init__(
+        self,
+        named_args: "NamedArgumentList",
+        name: "Optional[TypeReference]",
+        type_args: "Sequence[TypeVariable]",
+    ):
+        with warnings.catch_warnings():
+            warnings.simplefilter("ignore", DeprecationWarning)
+            # noinspection PyDeprecation
+            super().__init__()
+        warnings.warn(
+            "RecordType and VariantType are deprecated; use dazl.damlast.daml_lf_1 types instead",
+            DeprecationWarning,
+            stacklevel=2,
+        )
+
+        if type(self) == _CompositeDataType:
+            raise Exception("_CompositeDataType cannot be constructed")
+        if not isinstance(named_args, NamedArgumentList):
+            raise TypeError("NamedArgumentList required here")
+        if name is not None and not isinstance(name, TypeReference):
+            raise TypeError("name must be a TypeReference or None")
+
+        self.named_args = named_args
+        self.name = name
+        self.type_args = tuple(type_args)
+
+    def field_type(self, name: str) -> Type:
+        for key, value_type in self.named_args:
+            if key == name:
+                return value_type
+
+        raise ValueError(f"field or constructor {name!r} not found in {self}")
+
+    def __repr__(self):
+        py_type = type(self).__name__
+
+        name = "(anonymous)" if self.name is None else self.name
+        full_name = "".join(f" {v}" for v in ((name,) + self.type_args))
+
+        return f"<{py_type}:{full_name} {self.named_args}>"
+
+
+class FunctionType(Type):
+    """
+    Representation of a DAML function signature.
+
+    Instances of this type aren't practically usable from this library. They are merely recorded in
+    order to faithfully pretty-print metadata.
+    """
+
+    def __init__(self, parameters: Sequence[Type], result: Type):
+        with warnings.catch_warnings():
+            warnings.simplefilter("ignore", DeprecationWarning)
+            # noinspection PyDeprecation
+            super().__init__()
+        warnings.warn(
+            "FunctionType is deprecated; there is no replacement", DeprecationWarning, stacklevel=2
+        )
+
+        self.parameters = tuple(parameters)
+        self.result = safe_cast(Type, result)
+
+    def __str__(self):
+        from io import StringIO
+
+        with StringIO() as buf:
+            for param in self.parameters:
+                buf.write(str(param))
+                buf.write(" -> ")
+            buf.write(str(self.result))
+            return buf.getvalue()
+
+    def __repr__(self):
+        return f"<FunctionType({self})>"
+
+
+class RecordType(_CompositeDataType):
+    def as_args_list(self):
+        return self.named_args
+
+
+class VariantType(_CompositeDataType):
+    def as_args_list(self):
+        return self.named_args
+
+    def _find_ctor(self, constructor_name: str) -> Type:
+        return self.field_type(constructor_name)
+
+
+class EnumType(ConcreteType):
+
+    __slots__ = ("constructors",)
+
+    def __init__(self, name: "Optional[TypeReference]", constructors: "Collection[str]"):
+        with warnings.catch_warnings():
+            warnings.simplefilter("ignore", DeprecationWarning)
+            # noinspection PyDeprecation
+            super().__init__()
+
+        warnings.warn(
+            "EnumType is deprecated; use dazl.damlast.daml_lf_1 types instead",
+            DeprecationWarning,
+            stacklevel=2,
+        )
+
+        self.name = name
+        self.constructors = constructors
+
+
+class UnsupportedType(Type):
+    """
+    A DAML type that is currently unparseable by the Python client library.
+    """
+
+    __slots__ = ("name",)
+
+    def __init__(self, name):
+        self.name = safe_cast(str, name)
+
+    def __repr__(self):
+        return f"<UnsupportedType({self.name})>"
+
+
+class Template:
+    """
+    Definition of a contract template.
+    """
+
+    def __init__(
+        self,
+        data_type: "RecordType",
+        key_type: "Optional[Type]",
+        choices: "Collection[TemplateChoice]",
+        observers: "Expr",
+        signatories: "Expr",
+        agreement: "Expr",
+        ensure: "Expr",
+    ):
+        if not isinstance(data_type, RecordType) or data_type.name is None:
+            raise ValueError(
+                f"data_type is required and must be a named record type " f"(got {data_type})"
+            )
+        self.data_type = safe_cast(RecordType, data_type)
+        self.key_type = safe_optional_cast(Type, key_type)
+        self.choices = choices
+        self._observers = observers
+        self._signatories = signatories
+        self._agreement = agreement
+        self._ensure = ensure
+
+
+class TemplateChoice:
+    __slots__ = ("name", "consuming", "data_type", "return_type", "_controllers")
+
+    def __init__(
+        self, name: str, consuming: bool, data_type: Type, return_type: Type, controllers: "Expr"
+    ):
+        self.name = name
+        self.consuming = consuming
+        self.data_type = data_type
+        self.return_type = return_type
+        self._controllers = controllers
+
+    @property
+    def type(self):
+        return self.data_type
+
+    def controllers(self, cdata: ContractData) -> Collection[Party]:
+        """
+        Return every :class:`Party` that can exercise this choice given the specified contract data.
+        """
+
+
+def as_commands(commands_ish, allow_callables=False):
+    """
+    Converts something that is either ``None``, a single :class:`Command`, or an iterable over
+    :class:`Command` objects to a ``list`` of :class:`Command`.
+
+    :param commands_ish:
+        Something that might be construed as either a :class:`Command` or an iterable over
+        :class:`Command`.
+    :param allow_callables:
+        If callables are encountered, invoke them and expect them to return something that can be
+        easily serialized to a :class:`Command`.
+    """
+    from .writing import Command
+
+    if commands_ish is None:
+        return ()
+    elif isinstance(commands_ish, Command):
+        return (commands_ish,)
+    elif allow_callables and callable(commands_ish):
+        return as_commands(commands_ish(), allow_callables=False)
+
+    # assume this is some kind of iterable structure, where everything needs to be a Command
+    cmds = []
+    for command in commands_ish:
+        if allow_callables and callable(command):
+            cmds.extend(as_commands(command(), allow_callables=False))
+        elif isinstance(command, Command):
+            cmds.append(command)
+        else:
+            raise TypeError(f"{command!r} is not a Command")
+    return tuple(cmds)
+
+
+def as_contract_id(cid, template_id=None):
+    """
+    Convert something that resembles a contract ID to a :class:`ContractId` or
+    :class:`RelativeContractRef`.
+    """
+    warnings.warn(
+        "as_contract_id is deprecated; there is no replacement", DeprecationWarning, stacklevel=2
+    )
+    with warnings.catch_warnings():
+        warnings.simplefilter("ignore", DeprecationWarning)
+        from .core import ContractId
+
+        if cid is None:
+            raise ValueError("cid is required")
+        elif isinstance(cid, str):
+            return ContractId(cid, template_id)
+        elif isinstance(cid, ContractId):
+            return cid
+
+        raise TypeError("Could not serialize an object to a contract ID: {!r}".format(cid))
+
+
+with warnings.catch_warnings():
+    warnings.simplefilter("ignore", DeprecationWarning)
+    SCALAR_TYPE_UNIT = ScalarType("Unit")
+    SCALAR_TYPE_BOOL = ScalarType("Bool")
+    SCALAR_TYPE_CHAR = ScalarType("Char")
+    SCALAR_TYPE_INTEGER = ScalarType("Integer")
+    SCALAR_TYPE_DECIMAL = ScalarType("Decimal")
+    SCALAR_TYPE_NUMERIC = ScalarType("Numeric")
+    SCALAR_TYPE_TEXT = ScalarType("Text")
+    SCALAR_TYPE_PARTY = ScalarType("Party")
+    SCALAR_TYPE_RELTIME = ScalarType("RelTime")
+    SCALAR_TYPE_DATE = ScalarType("Date")
+    SCALAR_TYPE_TIME = ScalarType("Time")
+    SCALAR_TYPE_ANY = ScalarType("Any")
+    SCALAR_TYPE_DATETIME = SCALAR_TYPE_TIME
+
+ScalarType.BUILTINS = [
+    SCALAR_TYPE_BOOL,
+    SCALAR_TYPE_CHAR,
+    SCALAR_TYPE_INTEGER,
+    SCALAR_TYPE_DECIMAL,
+    SCALAR_TYPE_NUMERIC,
+    SCALAR_TYPE_TEXT,
+    SCALAR_TYPE_PARTY,
+    SCALAR_TYPE_RELTIME,
+    SCALAR_TYPE_DATE,
+    SCALAR_TYPE_TIME,
+    SCALAR_TYPE_ANY,
+]
+
+
+class TypeEvaluationContext:
+    references: Dict[TypeConName, ConcreteType]
+    variables: Dict[TypeVariable, Type]
+    path: Sequence[Union[TypeReference, str]]
+
+    __slots__ = ("references", "variables", "path")
+
+    @classmethod
+    def from_store(cls, store: "PackageStore") -> "TypeEvaluationContext":
+        return cls(store.types(), {}, ())
+
+    def __init__(self, references: "Mapping[TypeConName, ConcreteType]", variables, path):
+        self.references = safe_dict_cast(TypeConName, ConcreteType, references)
+        self.variables = safe_dict_cast(TypeVariable, Type, variables)
+        self.path = path
+
+    def append_path(self, component: Union[TypeReference, str]) -> "TypeEvaluationContext":
+        return TypeEvaluationContext(
+            references=self.references,
+            variables=self.variables,
+            path=tuple((*self.path, component)),
+        )
+
+    def resolve_var(self, var: TypeVariable) -> Type:
+        return self.variables[var]
+
+    def with_vars(self, new_vars: "Dict[TypeVariable, Type]") -> "TypeEvaluationContext":
+        confirmed_new_vars = {}
+        for new_var, new_var_value in new_vars.items():
+            if new_var == new_var_value:
+                # TODO: Why do these cases happen?
+                continue
+            confirmed_new_vars[new_var] = new_var_value
+
+        return TypeEvaluationContext(
+            references=self.references,
+            variables={**self.variables, **confirmed_new_vars},
+            path=self.path,
+        )
+
+
+def type_evaluate_dispatch(
+    on_scalar: "Callable[[TypeEvaluationContext, ScalarType], T_co]",
+    on_contract_id: "Callable[[TypeEvaluationContext,  ContractIdType], T_co]",
+    on_optional: "Callable[[TypeEvaluationContext, OptionalType], T_co]",
+    on_list: "Callable[[TypeEvaluationContext, ListType], T_co]",
+    on_text_map: "Callable[[TypeEvaluationContext, TextMapType], T_co]",
+    on_record: "Callable[[TypeEvaluationContext, RecordType], T_co]",
+    on_variant: "Callable[[TypeEvaluationContext, VariantType], T_co]",
+    on_enum: "Callable[[TypeEvaluationContext, EnumType], T_co]",
+    on_unsupported: "Callable[[TypeEvaluationContext, UnsupportedType], T_co]",
+) -> "Callable[[TypeEvaluationContext, Type], T_co]":
+    """
+    Produce a function that defers handling of core types to the passed in functions.
+
+    The cases of :class:`TypeReference, :class:`TypeApp`, and :class:`TypeVariable` are handled
+    automatically. Note, though that ultimately type evaluation is only performed at one level
+    deep, and the produced function may need to be called multiple types at multiple depths of an
+    object or type hierarchy.
+    """
+
+    def _impl(context, tt):
+        resolve_depth = 0
+        while isinstance(tt, (TypeReference, TypeVariable, TypeApp)):
+            context, tt = single_reduce(context, tt)
+            resolve_depth += 1
+            if resolve_depth > 10:
+                raise Exception("hit our max resolve depth, which is probably not so great")
+
+        def error(_: Any) -> "T_co":
+            raise Exception()
+
+        context, tt = annotate_context(context, tt)
+
+        return type_dispatch_table(
+            error,
+            error,
+            error,
+            lambda st: on_scalar(context, st),
+            lambda ct: on_contract_id(context, ct),
+            lambda ot: on_optional(context, ot),
+            lambda lt: on_list(context, lt),
+            lambda mt: on_text_map(context, mt),
+            lambda rt: on_record(context, rt),
+            lambda vt: on_variant(context, vt),
+            lambda et: on_enum(context, et),
+            lambda ut: on_unsupported(context, tt),
+        )(tt)
+
+    return _impl
+
+
+def _type_evaluate_dispatch_error(_, __):
+    raise Exception()
+
+
+def type_evaluate_dispatch_default_error(
+    on_scalar: "Callable[[TypeEvaluationContext, ScalarType], T_co]" = _type_evaluate_dispatch_error,
+    on_contract_id: "Callable[[TypeEvaluationContext,  ContractIdType], T_co]" = _type_evaluate_dispatch_error,
+    on_optional: "Callable[[TypeEvaluationContext,  OptionalType], T_co]" = _type_evaluate_dispatch_error,
+    on_list: "Callable[[TypeEvaluationContext, ListType], T_co]" = _type_evaluate_dispatch_error,
+    on_text_map: "Callable[[TypeEvaluationContext, TextMapType], T_co]" = _type_evaluate_dispatch_error,
+    on_record: "Callable[[TypeEvaluationContext, RecordType], T_co]" = _type_evaluate_dispatch_error,
+    on_variant: "Callable[[TypeEvaluationContext, VariantType], T_co]" = _type_evaluate_dispatch_error,
+    on_enum: "Callable[[TypeEvaluationContext, EnumType], T_co]" = _type_evaluate_dispatch_error,
+    on_unsupported: "Callable[[TypeEvaluationContext, UnsupportedType], T_co]" = _type_evaluate_dispatch_error,
+):
+    return type_evaluate_dispatch(
+        on_scalar,
+        on_contract_id,
+        on_optional,
+        on_list,
+        on_text_map,
+        on_record,
+        on_variant,
+        on_enum,
+        on_unsupported,
+    )
+
+
+def single_reduce(context: TypeEvaluationContext, tt: Type) -> "Tuple[TypeEvaluationContext, Type]":
+    """
+    Apply a single substitution/reduction/unwrapping. The context may be augmented with additional
+    variables if a TypeApp is encountered.
+    """
+
+    def identity(t):
+        return context, t
+
+    def reduce_app(ta: TypeApp) -> "Tuple[TypeEvaluationContext, Type]":
+        body = context.references[ta.body.con] if isinstance(ta.body, TypeReference) else ta.body
+        if not isinstance(body, (RecordType, VariantType)):
+            raise Exception("Can't apply types to non-generic data structures")
+
+        return context.with_vars(dict(zip(body.type_args, ta.arguments))), ta.body
+
+    return type_dispatch_table(
+        lambda tr: (context, context.references[tr.con]),
+        lambda tv: (context, context.resolve_var(tv)),
+        reduce_app,
+        identity,
+        identity,
+        identity,
+        identity,
+        identity,
+        identity,
+        identity,
+        identity,
+        identity,
+    )(tt)
+
+
+def annotate_context(
+    context: TypeEvaluationContext, tt: Type
+) -> Tuple[TypeEvaluationContext, Type]:
+    def identity(t):
+        return context, t
+
+    def error(_: Any) -> Any:
+        raise Exception()
+
+    def annotate_path(t: Union[RecordType, VariantType]) -> Tuple[TypeEvaluationContext, Type]:
+        if t.name is None:
+            raise ValueError("cannot annotate a path with an anonymous type")
+
+        return context.append_path(t.name), t
+
+    return type_dispatch_table(
+        error,
+        error,
+        error,
+        identity,
+        identity,
+        identity,
+        identity,
+        identity,
+        annotate_path,
+        annotate_path,
+        identity,
+        identity,
+    )(tt)
+
+
+# types that can be used to refer to templates
+TemplateNameLike = Union[str, TypeReference, UnresolvedTypeReference, Template]
diff --git a/python/dazl/model/types_store.py b/python/dazl/model/types_store.py
new file mode 100644
index 00000000..69d0b0fb
--- /dev/null
+++ b/python/dazl/model/types_store.py
@@ -0,0 +1,528 @@
+# Copyright (c) 2017-2021 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
+# SPDX-License-Identifier: Apache-2.0
+
+from collections import defaultdict
+from dataclasses import dataclass
+from functools import reduce
+from operator import add
+import sys
+from threading import RLock
+from types import MappingProxyType
+from typing import (
+    Any,
+    Collection,
+    DefaultDict,
+    Dict,
+    Generic,
+    Iterable,
+    List,
+    Mapping,
+    Optional,
+    TypeVar,
+    Union,
+    no_type_check,
+)
+import warnings
+
+from ..damlast.daml_lf_1 import Archive, Expr, Package, PackageRef, TypeConName, ValName, _Name
+from ..damlast.lookup import validate_template
+from ..util.typing import safe_cast, safe_dict_cast
+
+if sys.version_info >= (3, 8):
+    from typing import Protocol, runtime_checkable
+else:
+    from typing_extensions import Protocol, runtime_checkable
+
+
+with warnings.catch_warnings():
+    warnings.simplefilter("ignore", DeprecationWarning)
+
+    from .types import (
+        ConcreteType,
+        PackageIdSet,
+        Template,
+        TemplateChoice,
+        Type,
+        TypeReference,
+        UnresolvedTypeReference,
+    )
+
+K = TypeVar("K", bound=_Name)
+T = TypeVar("T")
+S = TypeVar("S")
+
+warnings.warn(
+    "The types of dazl.model.types_store are deprecated", DeprecationWarning, stacklevel=2
+)
+
+
+class PackageStoreBuilder:
+    """
+    Convenience class for building up a :class:`PackageStore`.
+    """
+
+    def __init__(self):
+        warnings.warn(
+            "PackageStoreBuilder is deprecated; there is no direct replacement.",
+            DeprecationWarning,
+            stacklevel=2,
+        )
+
+        self._archives = list()  # type: List[Archive]
+        self._value_types = dict()  # type: Dict[ValName, Expr]
+        self._data_types = dict()  # type: Dict[TypeConName, Type]
+        self._templates = dict()  # type: Dict[TypeConName, Template]
+        self._expected_package_ids = None  # type: Optional[Collection[PackageRef]]
+
+    def add_archive(self, archive: "Archive") -> None:
+        self._archives.append(archive)
+
+    def add_type(self, name: "TypeConName", data_type: Type):
+        safe_cast(TypeConName, name)
+        self._data_types[name] = safe_cast(Type, data_type)
+
+    def add_value(self, name: "ValName", value: "Expr"):
+        safe_cast(ValName, name)
+        self._value_types[name] = safe_cast(Expr, value)
+
+    def add_template(self, template: Template):
+        name = template.data_type.name
+        if name is None:
+            raise ValueError("template cannot be defined with an anonymous type")
+        self._templates[name.con] = safe_cast(Template, template)
+
+    def add_expected_package_ids(self, expected_package_ids: "Collection[PackageRef]"):
+        self._expected_package_ids = expected_package_ids
+
+    def get_type(self, name: TypeConName) -> "Optional[Type]":
+        return self._data_types.get(name)
+
+    def build(self) -> "PackageStore":
+        return PackageStore(
+            self._archives,
+            self._value_types,
+            self._data_types,
+            self._templates,
+            self._expected_package_ids,
+        )
+
+
+class PackageStore:
+    """
+    A thread-safe store of type information.
+    """
+
+    @classmethod
+    def empty(cls):
+        """
+        Create an empty store.
+        """
+        warnings.warn(
+            "PackageStore is deprecated; use PackageLoader/SymbolLookup instead",
+            DeprecationWarning,
+            stacklevel=2,
+        )
+
+        return cls([], {}, {}, {}, None)
+
+    def __init__(
+        self,
+        archives: "Collection[Archive]",
+        value_types: "Dict[ValName, Expr]",
+        data_types: "Dict[TypeConName, Type]",
+        templates: "Dict[TypeConName, Template]",
+        expected_package_ids: "Optional[Collection[PackageRef]]" = None,
+    ):
+        warnings.warn(
+            "PackageStore is deprecated; use PackageLoader/SymbolLookup instead",
+            DeprecationWarning,
+            stacklevel=2,
+        )
+        self._lock = RLock()
+        self._archives = list(archives)
+        self._cache = PackageStoreCache(EMPTY_TYPE_CACHE, EMPTY_TYPE_CACHE, EMPTY_TYPE_CACHE)
+        self._value_types = safe_dict_cast(ValName, Expr, value_types)
+        self._data_types = safe_dict_cast(TypeConName, Type, data_types)
+        self._templates = safe_dict_cast(TypeConName, Template, templates)
+        self._expected_package_ids = expected_package_ids
+
+    def archives(self) -> "Collection[Archive]":
+        """
+        Return a copy of the collection of the set of loaded :class:`Archive`s.
+        """
+        warnings.warn(
+            "PackageStore.archives() is deprecated; use SymbolLookup.archives() instead",
+            DeprecationWarning,
+            stacklevel=2,
+        )
+
+        with self._lock:
+            return list(self._archives)
+
+    def packages(self) -> "Collection[Package]":
+        """
+        Return a copy of the collection of the set of loaded :class:`Package`s.
+        """
+        warnings.warn(
+            "PackageStore.packages() is deprecated; use SymbolLookup.archives() instead",
+            DeprecationWarning,
+            stacklevel=2,
+        )
+
+        with self._lock:
+            return [a.package for a in self._archives]
+
+    def package_ids(self) -> "Collection[PackageRef]":
+        """
+        Return a copy of the collection of the set of loaded :class:`Package`s.
+        """
+        warnings.warn(
+            "PackageStore.package_ids() is deprecated; use SymbolLookup.package_ids() instead",
+            DeprecationWarning,
+            stacklevel=2,
+        )
+
+        with self._lock:
+            return [a.hash for a in self._archives]
+
+    def expected_package_ids(self) -> "Optional[Collection[PackageRef]]":
+        """
+        Return package IDs that are expected to be found on the ledger.
+        """
+        warnings.warn(
+            "PackageStore.expected_package_ids() is deprecated; there is no replacement.",
+            DeprecationWarning,
+            stacklevel=2,
+        )
+        return self._expected_package_ids
+
+    def register_all(self, other_store: "PackageStore") -> "PackageStore":
+        """
+        Register all types declared in the other :class:`PackageStore`.
+
+        :param other_store: A package store to copy types, templates, and choices from.
+        :return: A reference to this object.
+        """
+        warnings.warn(
+            "PackageStore.register_all() is deprecated; use PackageLoader instead.",
+            DeprecationWarning,
+            stacklevel=2,
+        )
+
+        if self is not other_store:
+            with self._lock:
+                self._archives.extend(other_store._archives)
+                self._value_types.update(other_store._value_types)
+                self._data_types.update(other_store._data_types)
+                self._templates.update(other_store._templates)
+                self._cache = PackageStoreCache(
+                    TypeCache.build(self._value_types),
+                    TypeCache.build(self._data_types),
+                    TypeCache.build(self._templates),
+                )
+        return self
+
+    def resolve_value_reference(self, value_ref: "ValName") -> "Expr":
+        warnings.warn(
+            "PackageStore.resolve_value_reference() is deprecated; "
+            "use SymbolLookup.value() instead.",
+            DeprecationWarning,
+            stacklevel=2,
+        )
+
+        with self._lock:
+            return self._value_types[value_ref]
+
+    def resolve_type_reference(self, template_ref: "Union[TypeConName, TypeReference]") -> Type:
+        """
+        Resolve a type based on its reference.
+
+        :param template_ref:
+            A :class:`TypeReference` that refers to a type.
+        :return:
+            The :class:`Type` that is referred to by this type reference.
+        :raise KeyError:
+            If the :class:`TypeReference` does not have a corresponding value in this store.
+        """
+        warnings.warn(
+            "PackageStore.resolve_value_reference() is deprecated; "
+            "use SymbolLookup.data_type() instead.",
+            DeprecationWarning,
+            stacklevel=2,
+        )
+
+        if isinstance(template_ref, TypeReference):
+            con = template_ref.con
+        elif isinstance(template_ref, TypeConName):
+            con = template_ref
+        else:
+            raise ValueError("template_ref must either by a TypeConName or TypeReference")
+
+        with self._lock:
+            return self._data_types[con]
+
+    def types(self) -> "Mapping[TypeConName, ConcreteType]":
+        warnings.warn(
+            'PackageStore.types() is deprecated; use SymbolLookup.data_type("*") instead.',
+            DeprecationWarning,
+            stacklevel=2,
+        )
+
+        with self._lock:
+            return MappingProxyType(dict(self._data_types))  # type: ignore
+
+    def get_templates_for_packages(
+        self, package_ids: "Iterable[PackageRef]"
+    ) -> "Collection[Template]":
+        """
+        Return a collection of :class:`Template` instances from the given set of package IDs.
+
+        :param package_ids:
+            The set of package IDs to restrict templates to.
+        :return:
+            A collection of matching templates, or an empty collection if none match. This method
+            never returns ``None``.
+        """
+        warnings.warn(
+            "PackageStore.get_templates_for_packages() is deprecated; "
+            'use [SymbolLookup.templates(f"{pkg}:*") for pkg in package_ids] instead.',
+            DeprecationWarning,
+            stacklevel=2,
+        )
+
+        match = []  # type: List[Template]
+        for pkg_id in package_ids:
+            match.extend(self._cache.templates.lookup(pkg_id, "*"))
+        return match
+
+    def resolve_template(
+        self, template: "Union[None, str, TypeReference, UnresolvedTypeReference, TypeConName]"
+    ) -> Collection[Template]:
+        """
+        Return a collection of :class:`Template` instances that match the specified template name.
+
+        Some special parameters:
+         * If ``"*"`` is passed in, all templates are returned.
+         * If ``None`` is passed in, an empty collection is returned.
+
+        :param template:
+            A template name or a :class:`TypeReference`.
+        :return:
+            A collection of matching templates, or an empty collection if none match. This method
+            never returns ``None``.
+        """
+        warnings.warn(
+            "PackageStore.resolve_template() is deprecated; use SymbolLookup.template() instead.",
+            DeprecationWarning,
+            stacklevel=2,
+        )
+
+        if isinstance(template, Template):
+            # if we were given a Template for some strange reason, just simply return a single-item
+            # tuple of that given Template
+            return [template]
+
+        package_id, template_name = validate_template(template, allow_deprecated_identifiers=True)
+        return self._cache.templates.lookup(package_id, template_name)
+
+    def resolve_template_type(
+        self, template: "Union[None, str, TypeReference, UnresolvedTypeReference, TypeConName]"
+    ) -> Dict[TypeReference, Type]:
+        """
+        Return a collection of types that match for the template.
+
+        :param template:
+            A template name or a :class:`TypeReference`.
+        :return:
+            A dictionary of possible matches, or empty if there are no matches. This method never
+            returns ``None``.
+        """
+        warnings.warn(
+            "PackageStore.resolve_template() is deprecated; use SymbolLookup.template() instead.",
+            DeprecationWarning,
+            stacklevel=2,
+        )
+
+        with warnings.catch_warnings():
+            warnings.simplefilter("ignore", DeprecationWarning)
+
+            return {
+                template.data_type.name: template.data_type
+                for template in self.resolve_template(template)
+                if template.data_type.name is not None
+            }
+
+    def resolve_choice(self, template: Any, choice: str) -> Dict[TypeReference, TemplateChoice]:
+        """
+        Return all possible choices for the combination of template identifier and choice name.
+        If template is ``'*'`` or ``None``, all choices with the specified name are returned.
+        """
+        warnings.warn(
+            'PackageStore.resolve_choice() is deprecated; use SymbolLookup.template("*") instead, '
+            "and lookup the choice within the returned Template.",
+            DeprecationWarning,
+            stacklevel=2,
+        )
+
+        with warnings.catch_warnings():
+            warnings.simplefilter("ignore", DeprecationWarning)
+
+            matches = dict()  # type: Dict[TypeReference, TemplateChoice]
+            for t in self.resolve_template(template):
+                for c in t.choices:
+                    if c.name == choice and t.data_type.name is not None:
+                        matches[t.data_type.name] = c
+            return matches
+
+
+@runtime_checkable
+class PackageProvider(Protocol):
+    """
+    Interface to an object that can provide package information.
+    """
+
+    def __init__(self):
+        warnings.warn(
+            "PackageProvider is deprecated; use PackageLookup instead.",
+            DeprecationWarning,
+            stacklevel=2,
+        )
+
+    def get_package_ids(self) -> "PackageIdSet":
+        """
+        Return the current universe of package IDs.
+        """
+        raise NotImplementedError
+
+    def fetch_package(self, package_id: "PackageRef") -> bytes:
+        """
+        Retrieve the bytes that correspond to a package.
+        """
+        raise NotImplementedError
+
+    def get_all_packages(self) -> "Mapping[PackageRef, bytes]":
+        raise NotImplementedError
+
+
+class MemoryPackageProvider:
+    def __init__(self, mapping: "Mapping[PackageRef, bytes]"):
+        with warnings.catch_warnings():
+            warnings.simplefilter("ignore", DeprecationWarning)
+            super().__init__()
+        warnings.warn(
+            "MemoryPackageProvider is deprecated; there is no replacement.",
+            DeprecationWarning,
+            stacklevel=2,
+        )
+        self.mapping = mapping
+
+    def get_package_ids(self) -> "PackageIdSet":
+        return frozenset(self.mapping.keys())
+
+    # fetch_package is actually supposed to throw an error on missing packages, but people may
+    # be reliant on the old behavior; anyway this entire file is deprecated; changing the behavior
+    # of a soon-to-be-removed class seems unnecessary
+    @no_type_check
+    def fetch_package(self, package_id: "PackageRef") -> bytes:
+        return self.mapping.get(package_id)
+
+    def get_all_packages(self) -> "Mapping[PackageRef, bytes]":
+        with warnings.catch_warnings():
+            warnings.simplefilter("ignore", DeprecationWarning)
+            return {pkg_id: self.fetch_package(pkg_id) for pkg_id in self.get_package_ids()}
+
+
+@dataclass(frozen=True)
+class PackageStoreCache:
+    value_types: "TypeCache[ValName, Expr]"
+    data_types: "TypeCache[TypeConName, Type]"
+    templates: "TypeCache[TypeConName, Template]"
+
+
+@dataclass(frozen=True)
+class TypeCache(Generic[K, T]):
+    """
+    Immutable cache of type information.
+
+    Instance attributes:
+
+    .. attribute:: TypeCache.by_package_lookup
+
+        A mapping from package ID strings to another mapping, where keys are fully-qualified type
+        names for templates to a collection of matching Template instances. These collections are
+        never non-empty; they will only ever contain multiple entries if there is a collision in
+        dot-separated names.
+
+    .. attribute:: TypeCache.by_name_lookup
+
+        A mapping from fully-qualified type names (excluding package IDs) to ``Collection[T]``.
+        These subcollections will only contain multiple values if names are not unique with respect
+        to package IDs.
+    """
+
+    everything: "Collection[T]"
+    by_package_lookup: "Mapping[PackageRef, Mapping[str, Collection[T]]]"
+    by_name_lookup: "Mapping[str, Collection[T]]"
+
+    @classmethod
+    def build(cls, objects: "Mapping[K, T]") -> "TypeCache[K, T]":
+        from ..damlast.util import package_local_name, package_ref
+
+        everything = tuple(objects.values())
+        by_package_lookup: DefaultDict[PackageRef, DefaultDict[str, List[T]]] = defaultdict(
+            lambda: defaultdict(list)
+        )
+        by_name_lookup = defaultdict(list)
+
+        for k, v in objects.items():
+            package_id = package_ref(k)
+            module_entity_name = package_local_name(k)
+            # This is kept here for backwards compatibility, but its use should be discouraged
+            module_entity_name_deprecated = module_entity_name.replace(":", ".")
+            for valid_name in (module_entity_name, module_entity_name_deprecated):
+                by_package_lookup[package_id][valid_name].append(v)
+                by_name_lookup[valid_name].append(v)
+
+        return TypeCache(
+            everything,
+            _immutable_mmc(by_package_lookup),
+            MappingProxyType({k: tuple(v) for k, v in by_name_lookup.items()}),
+        )
+
+    def lookup(self, package_id: PackageRef, type_name: str) -> "Collection[T]":
+        """
+        Look up items based on package ID and type name. The values cannot be ``None``, but they
+        can be the special-meaning ``'*'`` value.
+        """
+        safe_cast(str, package_id)
+        safe_cast(str, type_name)
+
+        if package_id == "*":
+            if type_name == "*":
+                # *:* means return everything
+                return self.everything
+            else:
+                # *:Specific.Module:Entity means return all matches of the requested name, but
+                # under any package
+                return self.by_name_lookup.get(type_name, ())
+        elif type_name == "*":
+            # PKG_ID:* means return all matches in a specific package
+            return reduce(add, self.by_package_lookup.get(package_id, {}).values(), ())
+        else:
+            # PKG_ID:Specific.Module:Entity; we are looking for a very specific type
+            return self.by_package_lookup.get(package_id, {}).get(type_name, ())
+
+
+def _immutable_mmc(
+    mapping: "Mapping[S, Mapping[str, Collection[T]]]",
+) -> "Mapping[S, Mapping[str, Collection[T]]]":
+    """
+    Create an immutable copy of :class:`TemplateStoreCache` data structures.
+    """
+    return MappingProxyType(
+        {k1: MappingProxyType({k2: tuple(v) for k2, v in v1.items()}) for k1, v1 in mapping.items()}
+    )
+
+
+EMPTY_MAPPING = MappingProxyType({})  # type: ignore
+EMPTY_TYPE_CACHE = TypeCache((), EMPTY_MAPPING, EMPTY_MAPPING)  # type: ignore
diff --git a/python/dazl/protocols/events.py b/python/dazl/protocols/events.py
index 6601ac09..07dbacde 100644
--- a/python/dazl/protocols/events.py
+++ b/python/dazl/protocols/events.py
@@ -65,6 +65,7 @@
 
 if TYPE_CHECKING:
     from ..client.state import ContractContextualData
+    from ..model.types_store import PackageStore
 
 __all__ = [
     "BaseEvent",
@@ -96,9 +97,7 @@ class BaseEvent:
     time: "Optional[datetime]"
     ledger_id: str
     lookup: "SymbolLookup"
-
-    # TODO: Replace with PackageStore
-    package_store: "Any"
+    package_store: "PackageStore"
 
     def acs_find_active(self, template: "Union[str, TypeConName]", match=None):
         return self.client.find_active(template, match)
diff --git a/python/dazl/protocols/v1/grpc.py b/python/dazl/protocols/v1/grpc.py
index e1fad06f..9caf6444 100644
--- a/python/dazl/protocols/v1/grpc.py
+++ b/python/dazl/protocols/v1/grpc.py
@@ -7,20 +7,28 @@
 from asyncio import gather
 from datetime import datetime
 from threading import Event
-from typing import TYPE_CHECKING, AbstractSet, Iterable, Optional, Sequence
+from typing import TYPE_CHECKING, AbstractSet, Iterable, Mapping, Optional, Sequence
 import warnings
 
-from grpc import Channel, RpcError, insecure_channel, secure_channel, ssl_channel_credentials
+from grpc import (
+    Channel,
+    RpcError,
+    StatusCode,
+    insecure_channel,
+    secure_channel,
+    ssl_channel_credentials,
+)
 
 from ... import LOG
+from ..._gen.com.daml.ledger.api.v1.transaction_service_pb2 import GetTransactionTreesResponse
 from ...damlast.daml_lf_1 import PackageRef
+from ...damlast.parse import parse_archive_payload
 from ...prim import Party, to_party
+from ...protocols.errors import ConnectionTimeoutError, UserTerminateRequest
 from ...scheduler import Invoker, RunLevel
 from ...util.io import read_file_bytes
 from ...util.typing import safe_cast
 from .._base import LedgerClient, LedgerConnectionOptions, _LedgerConnection
-from ..errors import ConnectionTimeoutError, UserTerminateRequest
-from ..events import BaseEvent, ContractFilter, TransactionFilter
 from .pb_parse_event import (
     BaseEventDeserializationContext,
     serialize_acs_request,
@@ -29,24 +37,27 @@
     to_transaction_events,
 )
 
-if TYPE_CHECKING:
-    from ...client._conn_settings import HTTPConnectionSettings
-    from ...client.commands import CommandPayload
-    from ...client.ledger import LedgerMetadata
-
+with warnings.catch_warnings():
+    warnings.simplefilter("ignore", DeprecationWarning)
 
-class GRPCv1LedgerClient(LedgerClient):
-    def __init__(self, connection: "GRPCv1Connection", ledger: "LedgerMetadata", party: "Party"):
+    if TYPE_CHECKING:
+        from ...client.commands import CommandPayload
         from ...client.ledger import LedgerMetadata
+        from ...model.network import HTTPConnectionSettings
+        from ...model.reading import BaseEvent, ContractFilter, TransactionFilter
+        from ...model.types_store import PackageProvider, PackageStore
+
 
+class GRPCv1LedgerClient(LedgerClient):
+    def __init__(self, connection: "GRPCv1Connection", ledger: "LedgerMetadata", party: Party):
         self.connection = safe_cast(GRPCv1Connection, connection)
-        self.ledger = safe_cast(LedgerMetadata, ledger)
+        self.ledger = ledger
         self.party = to_party(party)
 
-    def commands(self, commands: "CommandPayload") -> None:
+    async def commands(self, commands: "CommandPayload") -> None:
         serializer = self.ledger.serializer
         request = serializer.serialize_command_request(commands)
-        return self.connection.invoker.run_in_executor(
+        return await self.connection.invoker.run_in_executor(
             lambda: self.connection.command_service.SubmitAndWait(request)
         )
 
@@ -56,8 +67,8 @@ async def active_contracts(self, contract_filter: "ContractFilter") -> "Sequence
             context = BaseEventDeserializationContext(
                 None,
                 self.connection.options.lookup,
-                self.party,
                 self.ledger._store,
+                self.party,
                 self.ledger.ledger_id,
             )
 
@@ -108,6 +119,7 @@ async def events(self, transaction_filter: "TransactionFilter") -> "Sequence[Bas
             lambda: self.connection.transaction_service.GetTransactions(request)
         )
 
+        tt_stream = None  # type: Optional[Iterable[GetTransactionTreesResponse]]
         if transaction_filter.templates is None:
             # Filtering by package must disable the ability to handle exercise nodes; we may want to
             # consider dropping client-side support for exercise events anyway because they are not
@@ -115,8 +127,6 @@ async def events(self, transaction_filter: "TransactionFilter") -> "Sequence[Bas
             tt_stream = await self.connection.invoker.run_in_executor(
                 lambda: self.connection.transaction_service.GetTransactionTrees(request)
             )
-        else:
-            tt_stream = None
 
         tx_stream = await tx_future
 
@@ -135,6 +145,22 @@ async def events_end(self) -> str:
         )
 
 
+def grpc_set_time(connection: "GRPCv1Connection", ledger_id: str, new_datetime: datetime) -> None:
+    from . import model as G
+
+    request = G.GetTimeRequest(ledger_id=ledger_id)
+    response = connection.time_service.GetTime(request)
+    ts = next(iter(response))
+
+    request = G.SetTimeRequest(
+        ledger_id=ledger_id,
+        current_time=ts.current_time,
+        new_time=datetime_to_timestamp(new_datetime),
+    )
+    connection.time_service.SetTime(request)
+    LOG.info("Time on the server changed by the local client to %s.", new_datetime)
+
+
 def grpc_upload_package(connection: "GRPCv1Connection", dar_contents: bytes) -> None:
     from . import model as G
 
@@ -200,29 +226,46 @@ def grpc_main_thread(connection: "GRPCv1Connection", ledger_id: str) -> "Iterabl
     from .pb_ser_command import ProtobufSerializer
 
     LOG.info("grpc_main_thread...")
+    with warnings.catch_warnings():
+
+        warnings.simplefilter("ignore", DeprecationWarning)
+        from ...model.types_store import PackageStore
+
+        store = PackageStore.empty()
 
-    package_provider = GRPCPackageProvider(connection, ledger_id)
+        package_provider = GRPCPackageProvider(connection, ledger_id)
+
+        if connection.options.eager_package_fetch:
+            grpc_package_sync(package_provider, store)
 
     with warnings.catch_warnings():
         warnings.simplefilter("ignore", DeprecationWarning)
-        yield LedgerMetadata(
-            ledger_id=ledger_id,
-            package_loader=PackageLoader(
-                package_lookup=connection.options.lookup,
-                conn=package_provider,
-                timeout=connection.options.connect_timeout,
-            ),
-            serializer=ProtobufSerializer(connection.options.lookup),
-            protocol_version="v1",
-            # TODO: Replace with PackageStore
-            store=object(),
+        package_loader = PackageLoader(
+            package_lookup=connection.options.lookup,
+            conn=package_provider,
+            timeout=connection.options.connect_timeout,
         )
 
+    yield LedgerMetadata(
+        ledger_id=ledger_id,
+        store=store,
+        package_loader=package_loader,
+        serializer=ProtobufSerializer(connection.options.lookup),
+        protocol_version="v1",
+    )
+
+    # poll for package updates once a second
     while not connection._closed.wait(1):
-        pass
+        try:
+            if connection.options.eager_package_fetch:
+                with warnings.catch_warnings():
+                    warnings.simplefilter("ignore", DeprecationWarning)
+                    grpc_package_sync(package_provider, store)
+        except Exception as ex:
+            if not isinstance(ex, RpcError) or ex.code() != StatusCode.CANCELLED:
+                LOG.warning("Package syncing raised an exception.", exc_info=True)
 
     LOG.debug("The gRPC monitor thread is now shutting down.")
-    yield None
 
 
 class GRPCPackageProvider:
@@ -250,6 +293,51 @@ def get_package_ids(self) -> "AbstractSet[PackageRef]":
     def fetch_package(self, package_id: "PackageRef") -> bytes:
         return self.package_bytes(package_id)
 
+    def get_all_packages(self) -> "Mapping[PackageRef, bytes]":
+        return {pkg_id: self.fetch_package(pkg_id) for pkg_id in self.get_package_ids()}
+
+
+def grpc_package_sync(package_provider: "PackageProvider", store: "PackageStore") -> "None":
+    warnings.warn(
+        "grpc_package_sync is deprecated; there is no replacement", DeprecationWarning, stacklevel=2
+    )
+
+    with warnings.catch_warnings():
+        warnings.simplefilter("ignore", DeprecationWarning)
+        from .pb_parse_metadata import find_dependencies, parse_daml_metadata_pb
+
+        LOG.debug("grpc_package_sync started...")
+
+    with warnings.catch_warnings():
+        warnings.simplefilter("ignore", DeprecationWarning)
+        all_package_ids = package_provider.get_package_ids()
+        loaded_package_ids = {a.hash for a in store.archives()}
+        expected_package_ids = store.expected_package_ids()
+
+        def should_load(p: str) -> bool:
+            # TODO: Filtering by expected package IDs may cause packages to never fully load due to
+            #  transitive dependencies; here we put the onus on the app writer to ensure that they
+            #  supply the complete graph, and we don't even warn them if there is an issue (but
+            #  we could only actually warn them if we parse the archive, which is the expensive
+            #  operation we're trying to avoid).
+            return (
+                expected_package_ids is None or p in expected_package_ids
+            ) and p not in loaded_package_ids
+
+        metadatas_pb = {}
+        for package_id in all_package_ids:
+            if should_load(package_id):
+                archive_payload = package_provider.fetch_package(package_id)
+                metadatas_pb[package_id] = parse_archive_payload(package_id, archive_payload)
+
+    with warnings.catch_warnings():
+        warnings.simplefilter("ignore", DeprecationWarning)
+        adr = find_dependencies(metadatas_pb, loaded_package_ids)
+        for package_id, archive_bytes in adr.sorted_archives.items():
+            store.register_all(parse_daml_metadata_pb(package_id, archive_bytes))
+
+        LOG.debug("grpc_package_sync ended.")
+
 
 def grpc_create_channel(settings: "HTTPConnectionSettings") -> Channel:
     target = f"{settings.host}:{settings.port}"
diff --git a/python/dazl/protocols/v1/pb_parse_event.py b/python/dazl/protocols/v1/pb_parse_event.py
index cddd6e21..78a517a5 100644
--- a/python/dazl/protocols/v1/pb_parse_event.py
+++ b/python/dazl/protocols/v1/pb_parse_event.py
@@ -6,7 +6,7 @@
 """
 from dataclasses import dataclass
 from datetime import datetime
-from typing import Any, Dict, Iterable, List, Optional, Sequence, Union
+from typing import TYPE_CHECKING, Any, Dict, Iterable, List, Optional, Sequence, Union
 import warnings
 
 from ... import LOG
@@ -41,6 +41,9 @@
 
 DECODER = ProtobufDecoder()
 
+if TYPE_CHECKING:
+    from ...model.types_store import PackageStore
+
 
 @dataclass(frozen=True)
 class BaseEventDeserializationContext:
@@ -50,9 +53,7 @@ class BaseEventDeserializationContext:
 
     client: "Any"
     lookup: "SymbolLookup"
-
-    # TODO: Replace with PackageStore
-    store: "Any"
+    store: "PackageStore"
     party: "Party"
     ledger_id: str
 
diff --git a/python/dazl/protocols/v1/pb_parse_metadata.py b/python/dazl/protocols/v1/pb_parse_metadata.py
new file mode 100644
index 00000000..69ddd0e9
--- /dev/null
+++ b/python/dazl/protocols/v1/pb_parse_metadata.py
@@ -0,0 +1,361 @@
+# Copyright (c) 2017-2021 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
+# SPDX-License-Identifier: Apache-2.0
+
+from collections import OrderedDict, defaultdict
+from dataclasses import dataclass
+from typing import (
+    TYPE_CHECKING,
+    AbstractSet,
+    Any,
+    Collection,
+    DefaultDict,
+    List,
+    Mapping,
+    Optional,
+    Set,
+    Union,
+)
+import warnings
+
+from toposort import toposort_flatten
+
+from ... import LOG
+from ..._gen.com.daml.daml_lf_dev.daml_lf_pb2 import ArchivePayload as G_ArchivePayload
+from ...damlast.daml_lf_1 import (
+    Archive,
+    DefDataType,
+    DottedName,
+    ModuleRef,
+    PackageRef,
+    TypeConName,
+    ValName,
+)
+
+with warnings.catch_warnings():
+    warnings.simplefilter("ignore", DeprecationWarning)
+    from ...damlast.types import get_old_type
+
+    if TYPE_CHECKING:
+        from ...model.types import EnumType, RecordType, ScalarType, VariantType
+        from ...model.types_store import PackageStore
+
+__all__ = [
+    "parse_archive_payload",
+    "ArchiveDependencyResult",
+    "find_dependencies",
+    "parse_daml_metadata_pb",
+]
+
+warnings.warn(
+    "The symbols in dazl.protocols.v1.pb_parse_metadata are deprecated",
+    DeprecationWarning,
+    stacklevel=2,
+)
+
+
+def parse_archive_payload(raw_bytes: bytes, package_id: "Optional[PackageRef]" = None):
+    """
+    Convert ``bytes`` into a :class:`G_ArchivePayload`.
+
+    Note that this function will temporarily increase Python's recursion limit to handle cases where
+    parsing a DAML-LF archive requires deeper recursion limits.
+    """
+    warnings.warn(
+        "Use dazl.damlast.parse.parse_archive_payload instead.", DeprecationWarning, stacklevel=2
+    )
+
+    from ...damlast.parse import parse_archive_payload as pav
+
+    return pav(package_id, raw_bytes)
+
+
+@dataclass(frozen=True)
+class ArchiveDependencyResult:
+    sorted_archives: "Mapping[PackageRef, G_ArchivePayload]"
+    unresolvable_archives: "Mapping[PackageRef, G_ArchivePayload]"
+
+
+# noinspection PyDeprecation
+def find_dependencies(
+    metadatas_pb: "Mapping[PackageRef, G_ArchivePayload]",
+    existing_package_ids: "Collection[PackageRef]",
+) -> "ArchiveDependencyResult":
+    """
+    Return a topologically-sorted list of dependencies for the package IDs.
+
+    :param metadatas_pb:
+        The collection of gRPC ArchivePayload to re-order, indexed by package ID.
+    :param existing_package_ids:
+        Collection of package IDs that are to be treated as pre-existing.
+    :return:
+        A topologically-sorted dictionary of package IDs to ArchivePayload objects. Iterations
+        through the dictionary are guaranteed to be in the correct order.
+    """
+    warnings.warn(
+        "find_dependencies is deprecated; there is no replacement.",
+        DeprecationWarning,
+        stacklevel=2,
+    )
+
+    dependencies = defaultdict(set)  # type: DefaultDict[PackageRef, Set[PackageRef]]
+    for package_id, archive_payload in metadatas_pb.items():
+        for module_pb in archive_payload.daml_lf_1.modules:
+            for data_type_pb in module_pb.data_types:
+                deps = None
+                if data_type_pb.HasField("record"):
+                    deps = find_dependencies_of_fwts(data_type_pb.record.fields)
+                elif data_type_pb.HasField("variant"):
+                    deps = find_dependencies_of_fwts(data_type_pb.variant.fields)
+                if deps is not None:
+                    deps = set(deps)
+                    deps.difference_update(existing_package_ids)
+                    dependencies[package_id].update(deps)
+
+    # identify any completely missing dependencies
+    # TODO: This doesn't handle transitive missing dependencies; this will be a problem once
+    #  DAML has proper dependency support
+    unresolvable_package_ids = []  # type: List[PackageRef]
+    for package_id, package_dependencies in dependencies.items():
+        if not package_dependencies.issubset(metadatas_pb):
+            unresolvable_package_ids.append(package_id)
+
+    m_pb = {}
+    sorted_package_ids = toposort_flatten(dependencies)
+
+    # packages with no dependencies or dependents can safely be added in the front
+    remainders = set(metadatas_pb) - set(unresolvable_package_ids) - set(sorted_package_ids)
+    sorted_package_ids[0:0] = sorted(remainders)
+
+    for package_id in sorted_package_ids:
+        if package_id not in unresolvable_package_ids:
+            required_package = metadatas_pb.get(package_id)
+            if required_package is not None:
+                m_pb[package_id] = required_package
+            else:
+                LOG.warning("Failed to find a package %r", package_id)
+
+    if unresolvable_package_ids:
+        # This isn't a major problem in the grand scheme of things because the caller continually
+        # retries all unknown packages, so if the missing dependent packages are eventually
+        # uploaded, we will end up back in this function and all of the packages will be parsed.
+        LOG.warning(
+            "Some package IDs could not be resolved, so packages that depend on these IDs "
+            "will be unavailable: %r",
+            unresolvable_package_ids,
+        )
+
+    return ArchiveDependencyResult(
+        sorted_archives=m_pb,
+        unresolvable_archives={pkg_id: metadatas_pb[pkg_id] for pkg_id in unresolvable_package_ids},
+    )
+
+
+# noinspection PyDeprecation
+def find_dependencies_of_fwts(fwts_pb) -> "AbstractSet[PackageRef]":
+    warnings.warn(
+        "find_dependencies_of_fwts is deprecated; there is no replacement.",
+        DeprecationWarning,
+        stacklevel=2,
+    )
+
+    with warnings.catch_warnings():
+        warnings.simplefilter("ignore", DeprecationWarning)
+
+        dependencies = set()  # type: Set[PackageRef]
+        for fwt in fwts_pb:
+            dependencies.update(find_dependencies_of_fwt(fwt))
+        return dependencies
+
+
+# noinspection PyDeprecation
+def find_dependencies_of_fwt(fwt_pb) -> "Collection[PackageRef]":
+    warnings.warn(
+        "find_dependencies_of_fwt is deprecated; there is no replacement.",
+        DeprecationWarning,
+        stacklevel=2,
+    )
+
+    with warnings.catch_warnings():
+        warnings.simplefilter("ignore", DeprecationWarning)
+
+        return find_dependencies_of_type(fwt_pb.type)
+
+
+# noinspection PyDeprecation
+def find_dependencies_of_type(type_pb) -> "Collection[PackageRef]":
+    warnings.warn(
+        "find_dependencies_of_type is deprecated; there is no replacement.",
+        DeprecationWarning,
+        stacklevel=2,
+    )
+
+    with warnings.catch_warnings():
+        warnings.simplefilter("ignore", DeprecationWarning)
+
+        dependencies = set()  # type: Set[PackageRef]
+        t = type_pb.WhichOneof("Sum")  # type: str
+        if t == "prim":
+            for arg in type_pb.prim.args:
+                dependencies.update(find_dependencies_of_type(arg))
+            return sorted(dependencies)
+        elif t == "con":
+            if type_pb.con.tycon.module.package_ref.WhichOneof("Sum") == "package_id":
+                dependencies.add(type_pb.con.tycon.module.package_ref.package_id)
+            for arg in type_pb.con.args:
+                dependencies.update(find_dependencies_of_type(arg))
+            return sorted(dependencies)
+        elif t == "var":
+            for arg in type_pb.var.args:
+                dependencies.update(find_dependencies_of_type(arg))
+            return sorted(dependencies)
+        elif t == "fun":
+            for arg in type_pb.fun.params:
+                dependencies.update(find_dependencies_of_type(arg))
+            dependencies.update(find_dependencies_of_type(type_pb.fun.result))
+            return dependencies
+        elif t == "forall":
+            return find_dependencies_of_type(type_pb.forall.body)
+        elif t == "tuple":
+            return find_dependencies_of_fwts(type_pb.tuple.fields)
+        elif t == "nat":
+            return ()
+        elif t == "interned":
+            # This isn't strictly correct, but we're catching this here to suppress errors
+            # ahead of removal of this deprecated function
+            return ()
+        else:
+            LOG.warning("Unknown DAML-LF Type: %s (when evaluating %s)", t, type_pb)
+            return ()
+
+
+def parse_daml_metadata_pb(package_id: "PackageRef", metadata_pb: Any) -> "PackageStore":
+    """
+    Parse the contents of the given DAML-LF archive.
+
+    :param package_id:
+        The package to associate "self"-modules to.
+    :param metadata_pb:
+        The Protobuf-generated Archive object.
+    :return:
+        A :class:`PackageStore` with additional entries resulting from the parse of this archive.
+    """
+    warnings.warn(
+        "parse_daml_metadata_pb and PackageStore are deprecated; "
+        "use dazl.damlast.parse_archive instead",
+        DeprecationWarning,
+        stacklevel=2,
+    )
+
+    LOG.debug("Parsing package ID: %r", package_id)
+
+    from ...damlast.pb_parse import ProtobufParser
+
+    parser = ProtobufParser(package_id)
+    package = parser.parse_Package(metadata_pb.daml_lf_1)
+
+    return _parse_daml_metadata_pb(Archive(package_id, package))
+
+
+# noinspection PyDeprecation
+def _parse_daml_metadata_pb(archive: "Archive") -> "PackageStore":
+    with warnings.catch_warnings():
+        warnings.simplefilter("ignore", DeprecationWarning)
+        from ...model.types import EnumType, RecordType, Template, TemplateChoice, VariantType
+        from ...model.types_store import PackageStoreBuilder
+
+        psb = PackageStoreBuilder()
+        psb.add_archive(archive)
+
+        for module in archive.package.modules:
+            current_module = ModuleRef(archive.hash, DottedName(module.name.segments))
+            for vv in module.values:
+                vt = ValName(current_module, vv.name_with_type.name)
+                psb.add_value(vt, vv.expr)
+
+            for dt in module.data_types:
+                tt = create_data_type(current_module, dt)
+                if isinstance(tt, (RecordType, VariantType, EnumType)) and tt.name is not None:
+                    psb.add_type(tt.name.con, tt)
+                else:
+                    LOG.warning("Unexpected non-complex type will be ignored: %r", tt)
+
+            for template_pb in module.templates:
+                con = TypeConName(current_module, template_pb.tycon.segments)
+                data_type = psb.get_type(con)
+                if isinstance(data_type, RecordType):
+                    psb.add_template(
+                        Template(
+                            data_type=data_type,
+                            key_type=get_old_type(template_pb.key.type)
+                            if template_pb.key is not None
+                            else None,
+                            choices=[
+                                TemplateChoice(
+                                    c.name,
+                                    c.consuming,
+                                    get_old_type(c.arg_binder.type),
+                                    get_old_type(c.ret_type),
+                                    c.controllers,
+                                )
+                                for c in template_pb.choices
+                            ],
+                            observers=template_pb.observers,
+                            signatories=template_pb.signatories,
+                            agreement=template_pb.agreement,
+                            ensure=template_pb.precond,
+                        )
+                    )
+                elif data_type is None:
+                    LOG.warning(
+                        "The template %s did not have a corresponding data definition; "
+                        "it will be ignored",
+                        con,
+                    )
+                else:
+                    LOG.warning(
+                        "The template %s was of type %s; only records are supported for templates",
+                        con,
+                        data_type,
+                    )
+
+        LOG.debug("Fully registered all types for package ID %r", archive.hash)
+        return psb.build()
+
+
+# noinspection PyDeprecation
+def create_data_type(
+    current_module_ref: "ModuleRef", dt: "DefDataType"
+) -> "Union[RecordType, VariantType, EnumType, ScalarType]":
+    warnings.warn(
+        "create_data_type is deprecated; there is no replacement.", DeprecationWarning, stacklevel=2
+    )
+    with warnings.catch_warnings():
+        warnings.simplefilter("ignore", DeprecationWarning)
+        from ...damlast.types import get_old_type
+        from ...model.types import (
+            SCALAR_TYPE_UNIT,
+            EnumType,
+            NamedArgumentList,
+            RecordType,
+            TypeReference,
+            TypeVariable,
+            VariantType,
+        )
+
+        type_vars = tuple(TypeVariable(type_var.var) for type_var in dt.params)
+        tt = TypeReference(con=TypeConName(current_module_ref, dt.name.segments))
+
+        if dt.record is not None:
+            d = OrderedDict()
+            for fwt in dt.record.fields:
+                d[fwt.field] = get_old_type(fwt.type)
+            return RecordType(NamedArgumentList(d.items()), tt, type_vars)
+        elif dt.variant is not None:
+            d = OrderedDict()
+            for fwt in dt.variant.fields:
+                d[fwt.field] = get_old_type(fwt.type)
+            return VariantType(NamedArgumentList(d.items()), tt, type_vars)
+        elif dt.enum is not None:
+            return EnumType(tt, dt.enum.constructors)
+        else:
+            return SCALAR_TYPE_UNIT
diff --git a/python/mypy.ini b/python/mypy.ini
index 045ba765..d497a483 100644
--- a/python/mypy.ini
+++ b/python/mypy.ini
@@ -9,5 +9,8 @@ ignore_missing_imports = True
 [mypy-semver.*]
 ignore_missing_imports = True
 
+[mypy-toposort.*]
+ignore_missing_imports = True
+
 [mypy-dazl._gen.*]
 ignore_errors = True


Test patch:
diff --git a/python/tests/unit/test_template_reverse_globs.py b/python/tests/unit/test_template_reverse_globs.py
new file mode 100644
index 00000000..315081fa
--- /dev/null
+++ b/python/tests/unit/test_template_reverse_globs.py
@@ -0,0 +1,46 @@
+# Copyright (c) 2017-2021 Digital Asset (Switzerland) GmbH and/or its affiliates. All rights reserved.
+# SPDX-License-Identifier: Apache-2.0
+
+# noinspection PyProtectedMember
+from dazl.client.events import _template_reverse_globs as template_reverse_globs
+
+
+def test_non_primary_simple_unknown_module():
+    expected = ["*:MyModule:MyTemplate", "*:*"]
+    actual = list(template_reverse_globs(False, "*", "MyModule:MyTemplate"))
+    assert expected == actual
+
+
+def test_primary_simple_unknown_module():
+    expected = ["*:MyModule:MyTemplate"]
+    actual = list(template_reverse_globs(True, "*", "MyModule:MyTemplate"))
+    assert expected == actual
+
+
+def test_non_primary_simple_unknown_module_deprecated_style():
+    expected = ["*:MyModule:MyTemplate", "*:MyModule.MyTemplate", "*:*"]
+    actual = list(template_reverse_globs(False, "*", "MyModule.MyTemplate"))
+    assert expected == actual
+
+
+def test_primary_simple_unknown_module_deprecated_style():
+    expected = ["*:MyModule:MyTemplate"]
+    actual = list(template_reverse_globs(True, "*", "MyModule.MyTemplate"))
+    assert expected == actual
+
+
+def test_non_primary_simple_known_module():
+    expected = [
+        "0000dead0000beef:MyModule:MyTemplate",
+        "0000dead0000beef:*",
+        "*:MyModule:MyTemplate",
+        "*:*",
+    ]
+    actual = list(template_reverse_globs(False, "0000dead0000beef", "MyModule:MyTemplate"))
+    assert expected == actual
+
+
+def test_primary_simple_known_module():
+    expected = ["0000dead0000beef:MyModule:MyTemplate"]
+    actual = list(template_reverse_globs(True, "0000dead0000beef", "MyModule:MyTemplate"))
+    assert expected == actual