Repository: OpenAssetIO/OpenAssetIO (language: python)

Issue:
Move `TraitsData` into `trait` namespace
## What

☝️ 

## Why

Given we have the `trait` namespace which no longer actually contains concrete traits, just aliases for collections of trait-related types, `TraitsData` really does belong there.

## Acceptance Criteria

- `TraitsData` moved to `trait::TraitsData`
- Deprecated alias to old location [if there is a _light weight_ way to flag this, then good to do (15 mins max or make issue)] 🕳️  🚫 🐇 
- Tests and other code to use updated location (no inadvertent use of deprecated alias 🙃 )

## Notes

- Add deprecation aliases in a separate commit so it can be reverted later to remove deprecation (and makes sure we're not inadvertently using the old version)

Move `TraitsData` into `trait` namespace
## What

☝️ 

## Why

Given we have the `trait` namespace which no longer actually contains concrete traits, just aliases for collections of trait-related types, `TraitsData` really does belong there.

## Acceptance Criteria

- `TraitsData` moved to `trait::TraitsData`
- Deprecated alias to old location [if there is a _light weight_ way to flag this, then good to do (15 mins max or make issue)] 🕳️  🚫 🐇 
- Tests and other code to use updated location (no inadvertent use of deprecated alias 🙃 )

## Notes

- Add deprecation aliases in a separate commit so it can be reverted later to remove deprecation (and makes sure we're not inadvertently using the old version)



Discussion / hints:

Fix patch:
diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md
index 70f9c9573..a07f1d8df 100644
--- a/RELEASE_NOTES.md
+++ b/RELEASE_NOTES.md
@@ -4,16 +4,12 @@ Release Notes
 v1.0.0-alpha.xx
 ---------------
 
-### New Features
+### Deprecated
 
-- Added new exception types, allowing all OpenAssetIO exceptions to
-  share a common base.
-  [#1071](https://github.com/OpenAssetIO/OpenAssetIO/issues/1071)
-
-- Exceptional conveniences now provide additional information such
-  as entityReference, index, access, and error type in the message
-  of the exception.
-  [#1073](https://github.com/OpenAssetIO/OpenAssetIO/issues/1073)
+- Moved `TraitsData` into the `trait` namespace along with its peer
+  container/type definitions. Aliases in the deprecated top-level
+  namespace will be removed in a future release.
+  [#1127](https://github.com/OpenAssetIO/OpenAssetIO/issues/1127)
 
 ### Breaking changes
 
@@ -49,16 +45,27 @@ v1.0.0-alpha.xx
   an empty optional in the success callback, rather than error.
   [#1100](https://github.com/OpenAssetIO/OpenAssetIO/issues/1100)
 
- - Moved `BatchElementError` and `BatchElementException` into the
+- Moved `BatchElementError` and `BatchElementException` into the
   `errors` namespace.
   [#1071](https://github.com/OpenAssetIO/OpenAssetIO/issues/1071)
   [#1073](https://github.com/OpenAssetIO/OpenAssetIO/issues/1071)
 
-  - Removed all subtypes of `BatchElementException`, in favour of a
+- Removed all subtypes of `BatchElementException`, in favour of a
   single exception type for exceptional type for batch errors in
   exceptional conveniences.
   [#1073](https://github.com/OpenAssetIO/OpenAssetIO/issues/1073)
 
+### New Features
+
+- Added new exception types, allowing all OpenAssetIO exceptions to
+  share a common base.
+  [#1071](https://github.com/OpenAssetIO/OpenAssetIO/issues/1071)
+
+- Exceptional conveniences now provide additional information such
+  as entityReference, index, access, and error type in the message
+  of the exception.
+  [#1073](https://github.com/OpenAssetIO/OpenAssetIO/issues/1073)
+
 ### Improvements
 
 - Improved error messaging when `defaultManagerForInterface` points to a
diff --git a/doc/doxygen/src/ForManagers.dox b/doc/doxygen/src/ForManagers.dox
index fdfea9ba6..e59749765 100644
--- a/doc/doxygen/src/ForManagers.dox
+++ b/doc/doxygen/src/ForManagers.dox
@@ -112,8 +112,8 @@
  *   "managementPolicy" to control which Host-side data types you wish
  *   to be involved in managing, and specify which property-holding
  *   traits you are capable of resolving. To request not to be involved
- *   in publishing, return an empty @fqref{TraitsData} "TraitsData"
- *   for any queries with a context with `kWrite` access.
+ *   in publishing, return an empty @fqref{trait.TraitsData}
+ *   "TraitsData" for any queries with a context with `kWrite` access.
  *
  * - Implement the methods grouped under the "Entity Reference Inspection"
  *   and "Entity Reference Resolution" sections of the
diff --git a/doc/doxygen/src/Glossary.dox b/doc/doxygen/src/Glossary.dox
index f449a9d92..d341a88e2 100644
--- a/doc/doxygen/src/Glossary.dox
+++ b/doc/doxygen/src/Glossary.dox
@@ -217,7 +217,7 @@
  *
  * Within OpenAssetIO, the term "locale" is used to define which part of
  * a @ref host "host's" implementation is making a particular call to
- * the API. A suitably configured @fqref{TraitsData} "TraitsData"
+ * the API. A suitably configured @fqref{trait.TraitsData} "TraitsData"
  * instance is supplied through the @fqref{Context.locale}
  * "locale" property of the @fqref{Context} "Context", which is in turn
  * supplied to most @ref ManagerInterface method calls.
diff --git a/examples/manager/SampleAssetManager/python/SampleAssetManager/SampleAssetManagerInterface.py b/examples/manager/SampleAssetManager/python/SampleAssetManager/SampleAssetManagerInterface.py
new file mode 100644
index 000000000..a1b11d0e2
--- /dev/null
+++ b/examples/manager/SampleAssetManager/python/SampleAssetManager/SampleAssetManagerInterface.py
@@ -0,0 +1,71 @@
+#
+#   Copyright 2013-2021 [The Foundry Visionmongers Ltd]
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+#
+"""
+A single-class module, providing the SampleAssetManagerInterface class.
+
+The manager currently ignores all entity types.
+"""
+
+# Note that it should always be light-weight to construct instances of
+# the this class. See the notes under the "Initialization" section of:
+#   https://openassetio.github.io/OpenAssetIO/classopenassetio_1_1manager_a_p_i_1_1_manager_interface_1_1_manager_interface.html#details (pylint: disable=line-too-long)
+# As such, any expensive module imports should be deferred.
+from openassetio import constants
+from openassetio.managerApi import ManagerInterface
+from openassetio.trait import TraitsData
+
+
+__all__ = [
+    "SampleAssetManagerInterface",
+]
+
+
+# TODO(TC): @pylint-disable
+# As we are building out the implementation vertically, we have known
+# fails for missing abstract methods.
+# pylint: disable=abstract-method
+
+
+class SampleAssetManagerInterface(ManagerInterface):
+    """
+    Binds the SampleAssetManager to the OpenAssetIO ManagerInterface.
+
+    This class contains none of the actual business logic implementing
+    asset management, just bindings to the OpenAssetIO interface
+    methods.
+    """
+
+    def identifier(self):
+        return "org.openassetio.examples.manager.sam"
+
+    def initialize(self, managerSettings, hostSession):
+        pass
+
+    def displayName(self):
+        return "Sample Asset Manager (SAM)"
+
+    def info(self):
+        # This hint allows the API middleware to short-circuit calls to
+        # `isEntityReferenceString` using string prefix comparisons. If
+        # your implementation's entity reference format supports this
+        # kind of matching, you should set this key. It allows for
+        # multi-threaded reference testing in C++ as it avoids the need
+        # to acquire the GIL and enter Python.
+        return {constants.kInfoKey_EntityReferencesMatchPrefix: "sam:///"}
+
+    def managementPolicy(self, traitSets, policyAccess, context, hostSession):
+        # pylint: disable=unused-argument
+        return [TraitsData() for _ in traitSets]
diff --git a/src/openassetio-core/CMakeLists.txt b/src/openassetio-core/CMakeLists.txt
index ede00dedf..b76677e6a 100644
--- a/src/openassetio-core/CMakeLists.txt
+++ b/src/openassetio-core/CMakeLists.txt
@@ -65,7 +65,6 @@ target_sources(
     openassetio-core
     PRIVATE
     src/Context.cpp
-    src/TraitsData.cpp
     src/errors/exceptionMessages.cpp
     src/hostApi/HostInterface.cpp
     src/hostApi/Manager.cpp
@@ -79,6 +78,7 @@ target_sources(
     src/managerApi/HostSession.cpp
     src/managerApi/ManagerInterface.cpp
     src/managerApi/EntityReferencePagerInterface.cpp
+    src/trait/TraitsData.cpp
 )
 
 # Public header dependency.
diff --git a/src/openassetio-core/include/openassetio/Context.hpp b/src/openassetio-core/include/openassetio/Context.hpp
index 3bbf00474..8b4646851 100644
--- a/src/openassetio-core/include/openassetio/Context.hpp
+++ b/src/openassetio-core/include/openassetio/Context.hpp
@@ -8,7 +8,7 @@
 #include <openassetio/export.h>
 #include <openassetio/typedefs.hpp>
 
-OPENASSETIO_FWD_DECLARE(TraitsData)
+OPENASSETIO_FWD_DECLARE(trait, TraitsData)
 OPENASSETIO_FWD_DECLARE(managerApi, ManagerStateBase)
 
 namespace openassetio {
@@ -50,13 +50,13 @@ class OPENASSETIO_CORE_EXPORT Context final {
    * than a 'Write node' from a comp tree could result in different
    * behavior.
    *
-   * The Locale uses a @fqref{TraitsData} "TraitsData" to describe in
+   * The Locale uses a @fqref{trait.TraitsData} "TraitsData" to describe in
    * more detail, what specific part of a @ref host is requesting an
    * action. In the case of a file browser for example, it may also
    * include information such as whether or not multi-selection is
    * required.
    */
-  TraitsDataPtr locale;
+  trait::TraitsDataPtr locale;
 
   /**
    * The opaque state token owned by the @ref manager, used to
@@ -73,11 +73,11 @@ class OPENASSETIO_CORE_EXPORT Context final {
    * @fqref{hostApi.Manager.createContext} "Manager.createContext"
    * should always be used instead.
    */
-  [[nodiscard]] static ContextPtr make(TraitsDataPtr locale = nullptr,
+  [[nodiscard]] static ContextPtr make(trait::TraitsDataPtr locale = nullptr,
                                        managerApi::ManagerStateBasePtr managerState = nullptr);
 
  private:
-  Context(TraitsDataPtr locale, managerApi::ManagerStateBasePtr managerState);
+  Context(trait::TraitsDataPtr locale, managerApi::ManagerStateBasePtr managerState);
 };
 }  // namespace OPENASSETIO_CORE_ABI_VERSION
 }  // namespace openassetio
diff --git a/src/openassetio-core/include/openassetio/TraitsData.hpp b/src/openassetio-core/include/openassetio/TraitsData.hpp
index b96fc16b2..cf0eb33b0 100644
--- a/src/openassetio-core/include/openassetio/TraitsData.hpp
+++ b/src/openassetio-core/include/openassetio/TraitsData.hpp
@@ -1,177 +1,16 @@
 // SPDX-License-Identifier: Apache-2.0
-// Copyright 2013-2022 The Foundry Visionmongers Ltd
-/**
- *  Provide the transport-level data container for trait sets and their
- *  property values.
- */
+// Copyright 2023 The Foundry Visionmongers Ltd
 #pragma once
 
-#include <memory>
-#include <unordered_set>
+// Deprecated: https://github.com/OpenAssetIO/OpenAssetIO/issues/1127
 
-#include <openassetio/export.h>
-#include <openassetio/trait/collection.hpp>
-#include <openassetio/trait/property.hpp>
+#include <openassetio/trait/TraitsData.hpp>
 
 namespace openassetio {
 inline namespace OPENASSETIO_CORE_ABI_VERSION {
-OPENASSETIO_DECLARE_PTR(TraitsData)
-
-/**
- * A transport-level container for data exchange between a @ref host and
- * a @ref manager.
- *
- * The @ref Specification system combines one or more @ref trait
- * "traits" into a @ref trait_set to classify concepts within the API.
- * Traits may define a number of simple-typed properties, allowing them
- * to be used to exchange data between interested parties.
- *
- * A key requirement of the traits system is to be fully run-time
- * extensible. Additional specifications and traits can be defined as
- * required by any particular API integration.
- *
- * This is accomplished by breaking the system into two components:
- *  - A simple, generic data container that holds a trait set and its
- *    properties.
- *  - Custom views on this container that provide strongly-typed access.
- *
- * TraitsData is the transport-layer container that holds a @ref
- * trait_set, and any values set for the properties of these traits.
- * It has no semantic understanding of the data, providing simple
- * "by name" set/get of traits and their properties.
- *
- * This allows easy serialization and exchange of this data between
- * languages and sub-systems using the low-level introspection
- * functionality provided by this class.
- *
- * As generic access to the container's data (based on
- * "well-known-strings") is inherently unstable. Instances of this class
- * should generally be wrapped in one of the specialized @ref
- * Specification "specification" or @ref trait derived "views" at
- * runtime by a @ref host or @ref manager to ensure consistent access to
- * the correct keys.
- *
- * Trait @ref trait::property::Key "property keys" are always strings.
- * Property values are strings, integers, floating point, or booleans.
- * Any of a trait's properties can be legitimately left unset - it is up
- * to the consumer (host or manager, depending on the API method) to
- * decide how this should be handled.
- *
- * @todo Add InfoDictionary trait property value type.
- *
- * @see trait::property
- * @see @ref entities_traits_and_specifications
- */
-class OPENASSETIO_CORE_EXPORT TraitsData final {
- public:
-  OPENASSETIO_ALIAS_PTR(TraitsData)
-
-  /**
-   * Construct an empty instance, with no traits.
-   */
-  [[nodiscard]] static TraitsDataPtr make();
-
-  /**
-   * Construct such that this instance has the given set of traits.
-   *
-   * @param traitSet The constituent traits IDs.
-   */
-  [[nodiscard]] static TraitsDataPtr make(const trait::TraitSet& traitSet);
-
-  /**
-   * Construct such that this instance is a deep copy of the other.
-   *
-   * @param other The instance to copy.
-   */
-  [[nodiscard]] static TraitsDataPtr make(const TraitsDataConstPtr& other);
-
-  /**
-   * Defaulted destructor.
-   */
-  ~TraitsData();
-
-  /**
-   * Return the trait IDs held by the instance.
-   */
-  [[nodiscard]] trait::TraitSet traitSet() const;
-
-  /**
-   * Return whether this instance has the given trait.
-   *
-   * @param traitId ID of trait to check for.
-   * @return `true` if trait is present, `false` otherwise.
-   */
-  [[nodiscard]] bool hasTrait(const trait::TraitId& traitId) const;
-
-  /**
-   * Add the specified trait to this instance.
-   *
-   * If this instance already has this trait, it is a no-op.
-   *
-   * @param traitId ID of the trait to add.
-   */
-  void addTrait(const trait::TraitId& traitId);
-
-  /**
-   * Add the specified traits to this instance.
-   *
-   * If this instance already has any of the supplied traits, they
-   * are skipped.
-   *
-   * @param traitSet A trait set with the traits to add.
-   */
-  void addTraits(const trait::TraitSet& traitSet);
-
-  /**
-   * Get the value of a given trait property, if the property has
-   * been set.
-   *
-   * @param[out] out Storage for result, only written to if the property
-   * is set.
-   * @param traitId ID of trait to query.
-   * @param propertyKey Key of trait's property to query.
-   * @return `true` if value was found, `false` if it is unset.
-   * @exception `std::out_of_range` if this instance does not have
-   * this trait.
-   */
-  bool getTraitProperty(trait::property::Value* out, const trait::TraitId& traitId,
-                        const trait::property::Key& propertyKey) const;
-
-  /**
-   * Set the value of given trait property.
-   *
-   * If the instance does not yet have this trait, it will be
-   * added by this call.
-   *
-   * @param traitId ID of trait to update.
-   * @param propertyKey Key of property to set.
-   * @param propertyValue Value to set.
-   */
-  void setTraitProperty(const trait::TraitId& traitId, const trait::property::Key& propertyKey,
-                        trait::property::Value propertyValue);
-
-  /**
-   * Returns the properties set for a given trait.
-   *
-   * If the trait has not been given to this instance, or the trait has
-   * no properties set, then it will return an empty set.
-   */
-  [[nodiscard]] trait::property::KeySet traitPropertyKeys(const trait::TraitId& traitId) const;
-
-  /**
-   * Compares instances based on their trait and property values.
-   *
-   * @param other The instance to compare to.
-   */
-  bool operator==(const TraitsData& other) const;
-
- private:
-  TraitsData();
-  explicit TraitsData(const trait::TraitSet& traitSet);
-  TraitsData(const TraitsData& other);
-
-  class Impl;
-  std::unique_ptr<Impl> impl_;
-};
+// 'typedef' allows attributes, 'using' does not
+[[deprecated("Moved to 'trait' namespace")]] typedef trait::TraitsData TraitsData;        // NOLINT
+[[deprecated("Moved to 'trait' namespace")]] typedef trait::TraitsDataPtr TraitsDataPtr;  // NOLINT
+// NOLINTEND
 }  // namespace OPENASSETIO_CORE_ABI_VERSION
 }  // namespace openassetio
diff --git a/src/openassetio-core/include/openassetio/errors/BatchElementError.hpp b/src/openassetio-core/include/openassetio/errors/BatchElementError.hpp
index a5b7194a4..9d12e1c18 100644
--- a/src/openassetio-core/include/openassetio/errors/BatchElementError.hpp
+++ b/src/openassetio-core/include/openassetio/errors/BatchElementError.hpp
@@ -112,7 +112,7 @@ class BatchElementError final {
 
     /**
      * Error code response from @ref glossary_preflight if the provided
-     * @fqref{TraitsData} "traits data" hint holds insufficient or
+     * @fqref{trait.TraitsData} "traits data" hint holds insufficient or
      * invalid information.
      *
      * This will occur when the manager requires information that the
diff --git a/src/openassetio-core/include/openassetio/hostApi/Manager.hpp b/src/openassetio-core/include/openassetio/hostApi/Manager.hpp
index 9fab91abd..77c3bf853 100644
--- a/src/openassetio-core/include/openassetio/hostApi/Manager.hpp
+++ b/src/openassetio-core/include/openassetio/hostApi/Manager.hpp
@@ -222,10 +222,10 @@ class OPENASSETIO_CORE_EXPORT Manager final {
    * you to adapt application logic or user-facing behaviour
    * accordingly.
    *
-   * Queries return a @fqref{TraitsData} "TraitsData" for each supplied
-   * @ref trait_set, imbued with traits that describe the manager's
-   * policy for entities with those traits, and which traits they are
-   * capable of resolving/storing data for.
+   * Queries return a @fqref{trait.TraitsData} "TraitsData" for each
+   * supplied @ref trait_set, imbued with traits that describe the
+   * manager's policy for entities with those traits, and which traits
+   * they are capable of resolving/storing data for.
    *
    * This is an opt-in mechanism, such that if result is empty, then
    * the manager does not handle entities with the supplied traits.
@@ -253,9 +253,10 @@ class OPENASSETIO_CORE_EXPORT Manager final {
    *
    * Along with the traits that describe the manager's desired
    * interaction pattern (ones with the `managementPolicy` usage
-   * metadata), the resulting @fqref{TraitsData} "TraitsData" will be
-   * imbued with (potentially a subset of) the requested traits, which
-   * the manager is capable of resolving/persisting the properties for.
+   * metadata), the resulting @fqref{trait.TraitsData} "TraitsData" will
+   * be imbued with (potentially a subset of) the requested traits,
+   * which the manager is capable of resolving/persisting the properties
+   * for.
    *
    * If a requested trait is not present, then the manager will never
    * return properties for that trait in @ref resolve, or be able to
@@ -271,8 +272,9 @@ class OPENASSETIO_CORE_EXPORT Manager final {
    * target="_blank">OpenAssetIO-MediaCreation</a> project provides
    * traits for common data types used in computer graphics and media
    * production. Use the concrete Trait/Specification classes provided
-   * by these projects to retrieve data from the supplied TraitsData
-   * instead of querying directly using string literals.
+   * by these projects to retrieve data from the supplied
+   * trait::TraitsData instead of querying directly using string
+   * literals.
    *
    * @warning The @p policyAccess that is supplied will be considered by
    * the manager. If it is set to read, then its response applies to
@@ -313,7 +315,7 @@ class OPENASSETIO_CORE_EXPORT Manager final {
    *  Creates a new Context for use with the manager.
    *
    *  The @fqref{Context.locale} "locale" will be initialized with an
-   *  empty @fqref{TraitsData} "TraitsData" instance.
+   *  empty @fqref{trait.TraitsData} "TraitsData" instance.
    *
    *  @warning Contexts should never be directly constructed, always
    *  use this method or @ref createChildContext to create a new one.
@@ -425,7 +427,7 @@ class OPENASSETIO_CORE_EXPORT Manager final {
     /**
      * Variant policy overloads, when used in a batch context, will be
      * exhaustive for all elements in the batch, a variant result
-     * containing either a @ref TraitsData or @ref
+     * containing either a @ref trait::TraitsData or @ref
      * errors.BatchElementError will be provided for each @ref
      * EntityReference provided as an argument to the operation.
      */
@@ -622,10 +624,10 @@ class OPENASSETIO_CORE_EXPORT Manager final {
   /**
    * Callback signature used for a successful entity resolution.
    */
-  using ResolveSuccessCallback = std::function<void(std::size_t, TraitsDataPtr)>;
+  using ResolveSuccessCallback = std::function<void(std::size_t, trait::TraitsDataPtr)>;
 
   /**
-   * Provides a @fqref{TraitsData} "TraitsData" populated with the
+   * Provides a @fqref{trait.TraitsData} "TraitsData" populated with the
    * available property data for the requested set of traits for each
    * given @ref entity_reference.
    *
@@ -704,7 +706,7 @@ class OPENASSETIO_CORE_EXPORT Manager final {
                const BatchElementErrorCallback& errorCallback);
 
   /**
-   * Provides a @fqref{TraitsData} "TraitsData" populated with the
+   * Provides a @fqref{trait.TraitsData} "TraitsData" populated with the
    * available data for the requested set of traits for the given @ref
    * entity_reference.
    *
@@ -736,13 +738,14 @@ class OPENASSETIO_CORE_EXPORT Manager final {
    *
    * @return Populated data.
    */
-  TraitsDataPtr resolve(const EntityReference& entityReference, const trait::TraitSet& traitSet,
-                        access::ResolveAccess resolveAccess, const ContextConstPtr& context,
-                        const BatchElementErrorPolicyTag::Exception& errorPolicyTag = {});
+  trait::TraitsDataPtr resolve(const EntityReference& entityReference,
+                               const trait::TraitSet& traitSet,
+                               access::ResolveAccess resolveAccess, const ContextConstPtr& context,
+                               const BatchElementErrorPolicyTag::Exception& errorPolicyTag = {});
 
   /**
-   * Provides either a populated @fqref{TraitsData} "TraitsData" or a
-   * @fqref{errors.BatchElementError} "BatchElementError".
+   * Provides either a populated @fqref{trait.TraitsData} "TraitsData"
+   * or a @fqref{errors.BatchElementError} "BatchElementError".
    *
    * If successful, the result is populated with the
    * available data for the requested set of traits for the given @ref
@@ -778,13 +781,13 @@ class OPENASSETIO_CORE_EXPORT Manager final {
    * @return Object containing either the populated data or an error
    * object.
    */
-  std::variant<errors::BatchElementError, TraitsDataPtr> resolve(
+  std::variant<errors::BatchElementError, trait::TraitsDataPtr> resolve(
       const EntityReference& entityReference, const trait::TraitSet& traitSet,
       access::ResolveAccess resolveAccess, const ContextConstPtr& context,
       const BatchElementErrorPolicyTag::Variant& errorPolicyTag);
 
   /**
-   * Provides a @fqref{TraitsData} "TraitsData" populated with the
+   * Provides a @fqref{trait.TraitsData} "TraitsData" populated with the
    * available data for the requested set of traits for each given @ref
    * entity_reference.
    *
@@ -817,15 +820,15 @@ class OPENASSETIO_CORE_EXPORT Manager final {
    *
    * @return List of populated data objects.
    */
-  std::vector<TraitsDataPtr> resolve(
+  std::vector<trait::TraitsDataPtr> resolve(
       const EntityReferences& entityReferences, const trait::TraitSet& traitSet,
       access::ResolveAccess resolveAccess, const ContextConstPtr& context,
       const BatchElementErrorPolicyTag::Exception& errorPolicyTag = {});
 
   /**
-   * Provides either a populated @fqref{TraitsData} "TraitsData" or a
-   * @fqref{errors.BatchElementError} "BatchElementError" for each given
-   * @ref entity_reference.
+   * Provides either a populated @fqref{trait.TraitsData} "TraitsData"
+   * or a @fqref{errors.BatchElementError} "BatchElementError" for each
+   * given @ref entity_reference.
    *
    * For successful references, the corresponding element of the result
    * is populated with the available data for the requested set of
@@ -862,7 +865,7 @@ class OPENASSETIO_CORE_EXPORT Manager final {
    * @return List of objects, each containing either the populated data
    * or an error.
    */
-  std::vector<std::variant<errors::BatchElementError, TraitsDataPtr>> resolve(
+  std::vector<std::variant<errors::BatchElementError, trait::TraitsDataPtr>> resolve(
       const EntityReferences& entityReferences, const trait::TraitSet& traitSet,
       access::ResolveAccess resolveAccess, const ContextConstPtr& context,
       const BatchElementErrorPolicyTag::Variant& errorPolicyTag);
@@ -1022,7 +1025,7 @@ class OPENASSETIO_CORE_EXPORT Manager final {
    * handles relationships of that type.
    */
   void getWithRelationship(const EntityReferences& entityReferences,
-                           const TraitsDataPtr& relationshipTraitsData,
+                           const trait::TraitsDataPtr& relationshipTraitsData,
                            access::RelationsAccess relationsAccess, const ContextConstPtr& context,
                            const RelationshipSuccessCallback& successCallback,
                            const BatchElementErrorCallback& errorCallback,
@@ -1135,8 +1138,8 @@ class OPENASSETIO_CORE_EXPORT Manager final {
    * @throws errors.InputValidationException if @p pageSize is zero.
    */
   void getWithRelationshipPaged(const EntityReferences& entityReferences,
-                                const TraitsDataPtr& relationshipTraitsData, size_t pageSize,
-                                access::RelationsAccess relationsAccess,
+                                const trait::TraitsDataPtr& relationshipTraitsData,
+                                size_t pageSize, access::RelationsAccess relationsAccess,
                                 const ContextConstPtr& context,
                                 const PagedRelationshipSuccessCallback& successCallback,
                                 const BatchElementErrorCallback& errorCallback,
@@ -1216,8 +1219,8 @@ class OPENASSETIO_CORE_EXPORT Manager final {
    * determined is beyond the scope of this layer of the API, and
    * functions exists in higher levels that combine browsing and
    * publishing etc... Here, we simply assert that there must be a
-   * meaningful reference given the @fqref{TraitsData} "TraitsData" of
-   * the entity that is being created or published.
+   * meaningful reference given the @fqref{trait.TraitsData}
+   * "TraitsData" of the entity that is being created or published.
    *
    * @note 'Meaningful' is best defined by the asset manager itself. For
    * example, in a system that versions each 'asset' by creating
@@ -1302,9 +1305,9 @@ class OPENASSETIO_CORE_EXPORT Manager final {
    * the Manager may even use this opportunity to switch to some
    * temporary working path or some such.
    *
-   * @note If the supplied @fqref{TraitsData} "trait data" is missing
-   * traits or properties required by the manager for any input entity
-   * reference, then that element will error. See @ref
+   * @note If the supplied @fqref{trait.TraitsData} "trait data" is
+   * missing traits or properties required by the manager for any input
+   * entity reference, then that element will error. See @ref
    * glossary_preflight "glossary entry" for details.
    *
    * @warning The working @ref entity_reference returned by this
@@ -1390,7 +1393,7 @@ class OPENASSETIO_CORE_EXPORT Manager final {
    * the publishing operation
    */
   EntityReference preflight(const EntityReference& entityReference,
-                            const TraitsDataPtr& traitsHint,
+                            const trait::TraitsDataPtr& traitsHint,
                             access::PublishingAccess publishingAccess,
                             const ContextConstPtr& context,
                             const BatchElementErrorPolicyTag::Exception& errorPolicyTag = {});
@@ -1439,7 +1442,7 @@ class OPENASSETIO_CORE_EXPORT Manager final {
    * entity.
    */
   std::variant<errors::BatchElementError, EntityReference> preflight(
-      const EntityReference& entityReference, const TraitsDataPtr& traitsHint,
+      const EntityReference& entityReference, const trait::TraitsDataPtr& traitsHint,
       access::PublishingAccess publishingAccess, const ContextConstPtr& context,
       const BatchElementErrorPolicyTag::Variant& errorPolicyTag);
 
@@ -1568,7 +1571,7 @@ class OPENASSETIO_CORE_EXPORT Manager final {
    * manager (either in response to delegation of UI/etc... or as a
    * return from another call), then it can be assumed that the
    * Manager will understand what it means for you to call `register`
-   * on this reference with the supplied @fqref{TraitsData}
+   * on this reference with the supplied @fqref{trait.TraitsData}
    * "TraitsData". The conceptual meaning of the call is:
    *
    * "I have this reference you gave me, and I would like to register
@@ -1584,7 +1587,7 @@ class OPENASSETIO_CORE_EXPORT Manager final {
    * `ShotSpecification` entity to without error. The resulting entity
    * reference should then reference the newly created Shot.
    *
-   * @note All supplied TraitsDatas should have the same trait
+   * @note All supplied trait::TraitsDatas should have the same trait
    * sets. If you wish to register different "types" of entity, they
    * need to be registered in separate calls.
    *
@@ -1635,7 +1638,7 @@ class OPENASSETIO_CORE_EXPORT Manager final {
    * Other exceptions may be raised for fatal runtime errors, for
    * example server communication failure.
    *
-   * @see @fqref{TraitsData} "TraitsData"
+   * @see @fqref{trait.TraitsData} "TraitsData"
    * @see @ref preflight
    */
   // NOLINTNEXTLINE(readability-identifier-naming)
@@ -1684,7 +1687,7 @@ class OPENASSETIO_CORE_EXPORT Manager final {
    */
   // NOLINTNEXTLINE(readability-identifier-naming)
   EntityReference register_(const EntityReference& entityReference,
-                            const TraitsDataPtr& entityTraitsData,
+                            const trait::TraitsDataPtr& entityTraitsData,
                             access::PublishingAccess publishingAccess,
                             const ContextConstPtr& context,
                             const BatchElementErrorPolicyTag::Exception& errorPolicyTag = {});
@@ -1732,10 +1735,9 @@ class OPENASSETIO_CORE_EXPORT Manager final {
    */
   // NOLINTNEXTLINE(readability-identifier-naming)
   std::variant<errors::BatchElementError, EntityReference> register_(
-      const EntityReference& entityReference, const TraitsDataPtr& entityTraitsData,
+      const EntityReference& entityReference, const trait::TraitsDataPtr& entityTraitsData,
       access::PublishingAccess publishingAccess, const ContextConstPtr& context,
       const BatchElementErrorPolicyTag::Variant& errorPolicyTag);
-
   /**
    * Register should be used to 'publish' new entities either when
    * originating new data within the application process, or
diff --git a/src/openassetio-core/include/openassetio/managerApi/ManagerInterface.hpp b/src/openassetio-core/include/openassetio/managerApi/ManagerInterface.hpp
index f015eeb06..082a429a5 100644
--- a/src/openassetio-core/include/openassetio/managerApi/ManagerInterface.hpp
+++ b/src/openassetio-core/include/openassetio/managerApi/ManagerInterface.hpp
@@ -377,10 +377,10 @@ class OPENASSETIO_CORE_EXPORT ManagerInterface {
    * traits (fear not, there is a simple and established failure mode
    * for this situation).
    *
-   * This method must return a @fqref{TraitsData} "TraitsData" for each
-   * requested @ref trait_set. The implementation of this method should
-   * carefully consider the given @fqref{access.PolicyAccess} "access",
-   * and imbue suitable traits in the result to define:
+   * This method must return a @fqref{trait.TraitsData} "TraitsData" for
+   * each requested @ref trait_set. The implementation of this method
+   * should carefully consider the given @fqref{access.PolicyAccess}
+   * "access", and imbue suitable traits in the result to define:
    *
    *   - Whether and how that kind of entity is managed (traits with the
    *    `managementPolicy` usage metadata)
@@ -716,12 +716,12 @@ class OPENASSETIO_CORE_EXPORT ManagerInterface {
   /**
    * Callback signature used for a successful entity resolution.
    */
-  using ResolveSuccessCallback = std::function<void(std::size_t, TraitsDataPtr)>;
+  using ResolveSuccessCallback = std::function<void(std::size_t, trait::TraitsDataPtr)>;
 
   /**
-   * Provides the host with a @fqref{TraitsData} "TraitsData" populated
-   * with the available data for the properties of the requested set of
-   * traits for each given @ref entity_reference.
+   * Provides the host with a @fqref{trait.TraitsData} "TraitsData"
+   * populated with the available data for the properties of the
+   * requested set of traits for each given @ref entity_reference.
    *
    * This call should block until all resolutions are complete and
    * callbacks have been called. Callbacks must be called on the
@@ -977,7 +977,7 @@ class OPENASSETIO_CORE_EXPORT ManagerInterface {
    * capable of handling queries for those relationships in this method.
    */
   virtual void getWithRelationship(const EntityReferences& entityReferences,
-                                   const TraitsDataPtr& relationshipTraitsData,
+                                   const trait::TraitsDataPtr& relationshipTraitsData,
                                    const trait::TraitSet& resultTraitSet,
                                    access::RelationsAccess relationsAccess,
                                    const ContextConstPtr& context,
@@ -1109,7 +1109,7 @@ class OPENASSETIO_CORE_EXPORT ManagerInterface {
    * entities should have.
    */
   virtual void getWithRelationshipPaged(const EntityReferences& entityReferences,
-                                        const TraitsDataPtr& relationshipTraitsData,
+                                        const trait::TraitsDataPtr& relationshipTraitsData,
                                         const trait::TraitSet& resultTraitSet, size_t pageSize,
                                         access::RelationsAccess relationsAccess,
                                         const ContextConstPtr& context,
@@ -1279,11 +1279,11 @@ class OPENASSETIO_CORE_EXPORT ManagerInterface {
    * supplied references, and callbacks have been called on the same
    * thread that called `preflight`
    *
-   * @warning If the supplied @fqref{TraitsData} "trait data" is missing
-   * required traits for any of the provided references (maybe they are
-   * mismatched with the target entity), or the populated properties are
-   * insufficient or invalid for upcoming @ref resolve for
-   * @fqref{access.ResolveAccess.kWrite} "write" requests or the
+   * @warning If the supplied @fqref{trait.TraitsData} "trait data" is
+   * missing required traits for any of the provided references (maybe
+   * they are mismatched with the target entity), or the populated
+   * properties are insufficient or invalid for upcoming @ref resolve
+   * for @fqref{access.ResolveAccess.kWrite} "write" requests or the
    * eventual @ref register_, then error that element with an
    * appropriate @fqref{errors.BatchElementError.ErrorCode}.
    *
@@ -1358,10 +1358,10 @@ class OPENASSETIO_CORE_EXPORT ManagerInterface {
    *
    * This instructs the implementation to ensure a valid entity exists
    * for each given reference and to persist the data provided in the
-   * @fqref{TraitsData}. This will be called either in isolation or
-   * after calling preflight, depending on whether there is work needed
-   * to be done to generate the data. Preflight is omitted if the data
-   * is already available at the time of publishing.
+   * @fqref{trait.TraitsData}. This will be called either in isolation
+   * or after calling preflight, depending on whether there is work
+   * needed to be done to generate the data. Preflight is omitted if the
+   * data is already available at the time of publishing.
    *
    * This call must block until registration is complete for all
    * supplied references, and callbacks have been called on the same
@@ -1439,7 +1439,7 @@ class OPENASSETIO_CORE_EXPORT ManagerInterface {
    * callback must be called on the same thread that initiated the call
    * to `register`.
    *
-   * @see @fqref{TraitsData} "TraitsData"
+   * @see @fqref{trait.TraitsData} "TraitsData"
    * @see @ref preflight
    */
   // NOLINTNEXTLINE(readability-identifier-naming)
diff --git a/src/openassetio-core/include/openassetio/trait/TraitsData.hpp b/src/openassetio-core/include/openassetio/trait/TraitsData.hpp
new file mode 100644
index 000000000..c0d24fb30
--- /dev/null
+++ b/src/openassetio-core/include/openassetio/trait/TraitsData.hpp
@@ -0,0 +1,179 @@
+// SPDX-License-Identifier: Apache-2.0
+// Copyright 2013-2022 The Foundry Visionmongers Ltd
+/**
+ *  Provide the transport-level data container for trait sets and their
+ *  property values.
+ */
+#pragma once
+
+#include <memory>
+#include <unordered_set>
+
+#include <openassetio/export.h>
+#include <openassetio/trait/collection.hpp>
+#include <openassetio/trait/property.hpp>
+
+namespace openassetio {
+inline namespace OPENASSETIO_CORE_ABI_VERSION {
+namespace trait {
+OPENASSETIO_DECLARE_PTR(TraitsData)
+
+/**
+ * A transport-level container for data exchange between a @ref host and
+ * a @ref manager.
+ *
+ * The @ref Specification system combines one or more @ref trait
+ * "traits" into a @ref trait_set to classify concepts within the API.
+ * Traits may define a number of simple-typed properties, allowing them
+ * to be used to exchange data between interested parties.
+ *
+ * A key requirement of the traits system is to be fully run-time
+ * extensible. Additional specifications and traits can be defined as
+ * required by any particular API integration.
+ *
+ * This is accomplished by breaking the system into two components:
+ *  - A simple, generic data container that holds a trait set and its
+ *    properties.
+ *  - Custom views on this container that provide strongly-typed access.
+ *
+ * TraitsData is the transport-layer container that holds a @ref
+ * trait_set, and any values set for the properties of these traits.
+ * It has no semantic understanding of the data, providing simple
+ * "by name" set/get of traits and their properties.
+ *
+ * This allows easy serialization and exchange of this data between
+ * languages and sub-systems using the low-level introspection
+ * functionality provided by this class.
+ *
+ * As generic access to the container's data (based on
+ * "well-known-strings") is inherently unstable. Instances of this class
+ * should generally be wrapped in one of the specialized @ref
+ * Specification "specification" or @ref trait derived "views" at
+ * runtime by a @ref host or @ref manager to ensure consistent access to
+ * the correct keys.
+ *
+ * Trait @ref trait::property::Key "property keys" are always strings.
+ * Property values are strings, integers, floating point, or booleans.
+ * Any of a trait's properties can be legitimately left unset - it is up
+ * to the consumer (host or manager, depending on the API method) to
+ * decide how this should be handled.
+ *
+ * @todo Add InfoDictionary trait property value type.
+ *
+ * @see trait::property
+ * @see @ref entities_traits_and_specifications
+ */
+class OPENASSETIO_CORE_EXPORT TraitsData final {
+ public:
+  OPENASSETIO_ALIAS_PTR(TraitsData)
+
+  /**
+   * Construct an empty instance, with no traits.
+   */
+  [[nodiscard]] static TraitsDataPtr make();
+
+  /**
+   * Construct such that this instance has the given set of traits.
+   *
+   * @param traitSet The constituent traits IDs.
+   */
+  [[nodiscard]] static TraitsDataPtr make(const trait::TraitSet& traitSet);
+
+  /**
+   * Construct such that this instance is a deep copy of the other.
+   *
+   * @param other The instance to copy.
+   */
+  [[nodiscard]] static TraitsDataPtr make(const TraitsDataConstPtr& other);
+
+  /**
+   * Defaulted destructor.
+   */
+  ~TraitsData();
+
+  /**
+   * Return the trait IDs held by the instance.
+   */
+  [[nodiscard]] trait::TraitSet traitSet() const;
+
+  /**
+   * Return whether this instance has the given trait.
+   *
+   * @param traitId ID of trait to check for.
+   * @return `true` if trait is present, `false` otherwise.
+   */
+  [[nodiscard]] bool hasTrait(const trait::TraitId& traitId) const;
+
+  /**
+   * Add the specified trait to this instance.
+   *
+   * If this instance already has this trait, it is a no-op.
+   *
+   * @param traitId ID of the trait to add.
+   */
+  void addTrait(const trait::TraitId& traitId);
+
+  /**
+   * Add the specified traits to this instance.
+   *
+   * If this instance already has any of the supplied traits, they
+   * are skipped.
+   *
+   * @param traitSet A trait set with the traits to add.
+   */
+  void addTraits(const trait::TraitSet& traitSet);
+
+  /**
+   * Get the value of a given trait property, if the property has
+   * been set.
+   *
+   * @param[out] out Storage for result, only written to if the property
+   * is set.
+   * @param traitId ID of trait to query.
+   * @param propertyKey Key of trait's property to query.
+   * @return `true` if value was found, `false` if it is unset.
+   * @exception `std::out_of_range` if this instance does not have
+   * this trait.
+   */
+  bool getTraitProperty(trait::property::Value* out, const trait::TraitId& traitId,
+                        const trait::property::Key& propertyKey) const;
+
+  /**
+   * Set the value of given trait property.
+   *
+   * If the instance does not yet have this trait, it will be
+   * added by this call.
+   *
+   * @param traitId ID of trait to update.
+   * @param propertyKey Key of property to set.
+   * @param propertyValue Value to set.
+   */
+  void setTraitProperty(const trait::TraitId& traitId, const trait::property::Key& propertyKey,
+                        trait::property::Value propertyValue);
+
+  /**
+   * Returns the properties set for a given trait.
+   *
+   * If the trait has not been given to this instance, or the trait has
+   * no properties set, then it will return an empty set.
+   */
+  [[nodiscard]] trait::property::KeySet traitPropertyKeys(const trait::TraitId& traitId) const;
+
+  /**
+   * Compares instances based on their trait and property values.
+   *
+   * @param other The instance to compare to.
+   */
+  bool operator==(const TraitsData& other) const;
+
+ private:
+  TraitsData();
+  explicit TraitsData(const trait::TraitSet& traitSet);
+  TraitsData(const TraitsData& other);
+
+  class Impl;
+  std::unique_ptr<Impl> impl_;
+};
+}  // namespace trait
+}  // namespace OPENASSETIO_CORE_ABI_VERSION
+}  // namespace openassetio
diff --git a/src/openassetio-core/include/openassetio/trait/collection.hpp b/src/openassetio-core/include/openassetio/trait/collection.hpp
index bf9344253..d482fd3e5 100644
--- a/src/openassetio-core/include/openassetio/trait/collection.hpp
+++ b/src/openassetio-core/include/openassetio/trait/collection.hpp
@@ -12,7 +12,7 @@
 #include <openassetio/trait/property.hpp>
 #include <openassetio/typedefs.hpp>
 
-OPENASSETIO_FWD_DECLARE(TraitsData)
+OPENASSETIO_FWD_DECLARE(trait, TraitsData)
 
 namespace openassetio {
 inline namespace OPENASSETIO_CORE_ABI_VERSION {
@@ -32,7 +32,7 @@ using TraitSet = std::unordered_set<TraitId>;
 using TraitSets = std::vector<TraitSet>;
 
 /**
- * An ordered list of @fqref{TraitsData} "TraitsData" instances.
+ * An ordered list of @fqref{trait.TraitsData} "TraitsData" instances.
  */
 using TraitsDatas = std::vector<TraitsDataPtr>;
 }  // namespace trait
diff --git a/src/openassetio-core/include/openassetio/typedefs.hpp b/src/openassetio-core/include/openassetio/typedefs.hpp
index 8204396cb..3c476fbfa 100644
--- a/src/openassetio-core/include/openassetio/typedefs.hpp
+++ b/src/openassetio-core/include/openassetio/typedefs.hpp
@@ -17,8 +17,8 @@ inline namespace OPENASSETIO_CORE_ABI_VERSION {
  * @name Primitive Types
  *
  * These types are used throughout OpenAssetIO, especially within
- * dictionary-like types such as @fqref{TraitsData} "TraitsData" or
- * @fqref{InfoDictionary} "InfoDictionary".
+ * dictionary-like types such as @fqref{trait.TraitsData} "TraitsData"
+ * or @fqref{InfoDictionary} "InfoDictionary".
  *
  * OpenAssetIO must be able to bridge disparate platforms, including
  * serialization of data. It is therefore useful to ensure that our core
diff --git a/src/openassetio-core/src/Context.cpp b/src/openassetio-core/src/Context.cpp
index c85621691..03881e5dd 100644
--- a/src/openassetio-core/src/Context.cpp
+++ b/src/openassetio-core/src/Context.cpp
@@ -4,11 +4,12 @@
 
 namespace openassetio {
 inline namespace OPENASSETIO_CORE_ABI_VERSION {
-ContextPtr Context::make(TraitsDataPtr locale, managerApi::ManagerStateBasePtr managerState) {
+ContextPtr Context::make(trait::TraitsDataPtr locale,
+                         managerApi::ManagerStateBasePtr managerState) {
   return std::shared_ptr<Context>(new Context(std::move(locale), std::move(managerState)));
 }
 
-Context::Context(TraitsDataPtr locale_, managerApi::ManagerStateBasePtr managerState_)
+Context::Context(trait::TraitsDataPtr locale_, managerApi::ManagerStateBasePtr managerState_)
     : locale{std::move(locale_)}, managerState{std::move(managerState_)} {}
 }  // namespace OPENASSETIO_CORE_ABI_VERSION
 }  // namespace openassetio
diff --git a/src/openassetio-core/src/hostApi/Manager.cpp b/src/openassetio-core/src/hostApi/Manager.cpp
index 90855901e..f3189bff9 100644
--- a/src/openassetio-core/src/hostApi/Manager.cpp
+++ b/src/openassetio-core/src/hostApi/Manager.cpp
@@ -6,7 +6,6 @@
 #include <fmt/format.h>
 
 #include <openassetio/Context.hpp>
-#include <openassetio/TraitsData.hpp>
 #include <openassetio/constants.hpp>
 #include <openassetio/hostApi/EntityReferencePager.hpp>
 #include <openassetio/hostApi/Manager.hpp>
@@ -14,6 +13,7 @@
 #include <openassetio/managerApi/EntityReferencePagerInterface.hpp>
 #include <openassetio/managerApi/HostSession.hpp>
 #include <openassetio/managerApi/ManagerInterface.hpp>
+#include <openassetio/trait/TraitsData.hpp>
 #include <openassetio/typedefs.hpp>
 
 #include "../errors/exceptionMessages.hpp"
@@ -90,14 +90,14 @@ trait::TraitsDatas Manager::managementPolicy(const trait::TraitSets &traitSets,
 ContextPtr Manager::createContext() {
   ContextPtr context = Context::make();
   context->managerState = managerInterface_->createState(hostSession_);
-  context->locale = TraitsData::make();
+  context->locale = trait::TraitsData::make();
   return context;
 }
 
 ContextPtr Manager::createChildContext(const ContextPtr &parentContext) {
   // Copy-construct the locale so changes made to the child context
   // don't affect the parent (and vice versa).
-  ContextPtr context = Context::make(TraitsData::make(parentContext->locale));
+  ContextPtr context = Context::make(trait::TraitsData::make(parentContext->locale));
   if (parentContext->managerState) {
     context->managerState =
         managerInterface_->createChildState(parentContext->managerState, hostSession_);
@@ -162,14 +162,14 @@ void Manager::resolve(const EntityReferences &entityReferences, const trait::Tra
 }
 
 // Singular Except
-TraitsDataPtr hostApi::Manager::resolve(
+trait::TraitsDataPtr hostApi::Manager::resolve(
     const EntityReference &entityReference, const trait::TraitSet &traitSet,
     const access::ResolveAccess resolveAccess, const ContextConstPtr &context,
     [[maybe_unused]] const BatchElementErrorPolicyTag::Exception &errorPolicyTag) {
-  TraitsDataPtr resolveResult;
+  trait::TraitsDataPtr resolveResult;
   resolve(
       {entityReference}, traitSet, resolveAccess, context,
-      [&resolveResult]([[maybe_unused]] std::size_t index, TraitsDataPtr data) {
+      [&resolveResult]([[maybe_unused]] std::size_t index, trait::TraitsDataPtr data) {
         resolveResult = std::move(data);
       },
       [&entityReference, resolveAccess](std::size_t index, errors::BatchElementError error) {
@@ -182,14 +182,14 @@ TraitsDataPtr hostApi::Manager::resolve(
 }
 
 // Singular variant
-std::variant<errors::BatchElementError, TraitsDataPtr> hostApi::Manager::resolve(
+std::variant<errors::BatchElementError, trait::TraitsDataPtr> hostApi::Manager::resolve(
     const EntityReference &entityReference, const trait::TraitSet &traitSet,
     const access::ResolveAccess resolveAccess, const ContextConstPtr &context,
     [[maybe_unused]] const BatchElementErrorPolicyTag::Variant &errorPolicyTag) {
-  std::variant<errors::BatchElementError, TraitsDataPtr> resolveResult;
+  std::variant<errors::BatchElementError, trait::TraitsDataPtr> resolveResult;
   resolve(
       {entityReference}, traitSet, resolveAccess, context,
-      [&resolveResult]([[maybe_unused]] std::size_t index, TraitsDataPtr data) {
+      [&resolveResult]([[maybe_unused]] std::size_t index, trait::TraitsDataPtr data) {
         resolveResult = std::move(data);
       },
       [&resolveResult]([[maybe_unused]] std::size_t index, errors::BatchElementError error) {
@@ -200,16 +200,16 @@ std::variant<errors::BatchElementError, TraitsDataPtr> hostApi::Manager::resolve
 }
 
 // Multi except
-std::vector<TraitsDataPtr> hostApi::Manager::resolve(
+std::vector<trait::TraitsDataPtr> hostApi::Manager::resolve(
     const EntityReferences &entityReferences, const trait::TraitSet &traitSet,
     const access::ResolveAccess resolveAccess, const ContextConstPtr &context,
     [[maybe_unused]] const BatchElementErrorPolicyTag::Exception &errorPolicyTag) {
-  std::vector<TraitsDataPtr> resolveResult;
+  std::vector<trait::TraitsDataPtr> resolveResult;
   resolveResult.resize(entityReferences.size());
 
   resolve(
       entityReferences, traitSet, resolveAccess, context,
-      [&resolveResult](std::size_t index, TraitsDataPtr data) {
+      [&resolveResult](std::size_t index, trait::TraitsDataPtr data) {
         resolveResult[index] = std::move(data);
       },
       [&entityReferences, resolveAccess](std::size_t index, errors::BatchElementError error) {
@@ -223,15 +223,16 @@ std::vector<TraitsDataPtr> hostApi::Manager::resolve(
 }
 
 // Multi variant
-std::vector<std::variant<errors::BatchElementError, TraitsDataPtr>> hostApi::Manager::resolve(
+std::vector<std::variant<errors::BatchElementError, trait::TraitsDataPtr>>
+hostApi::Manager::resolve(
     const EntityReferences &entityReferences, const trait::TraitSet &traitSet,
     const access::ResolveAccess resolveAccess, const ContextConstPtr &context,
     [[maybe_unused]] const BatchElementErrorPolicyTag::Variant &errorPolicyTag) {
-  std::vector<std::variant<errors::BatchElementError, TraitsDataPtr>> resolveResult;
+  std::vector<std::variant<errors::BatchElementError, trait::TraitsDataPtr>> resolveResult;
   resolveResult.resize(entityReferences.size());
   resolve(
       entityReferences, traitSet, resolveAccess, context,
-      [&resolveResult](std::size_t index, TraitsDataPtr data) {
+      [&resolveResult](std::size_t index, trait::TraitsDataPtr data) {
         resolveResult[index] = std::move(data);
       },
       [&resolveResult](std::size_t index, errors::BatchElementError error) {
@@ -251,7 +252,7 @@ void Manager::defaultEntityReference(const trait::TraitSets &traitSets,
 }
 
 void Manager::getWithRelationship(const EntityReferences &entityReferences,
-                                  const TraitsDataPtr &relationshipTraitsData,
+                                  const trait::TraitsDataPtr &relationshipTraitsData,
                                   const access::RelationsAccess relationsAccess,
                                   const ContextConstPtr &context,
                                   const Manager::RelationshipSuccessCallback &successCallback,
@@ -275,7 +276,7 @@ void Manager::getWithRelationships(const EntityReference &entityReference,
 }
 
 void Manager::getWithRelationshipPaged(
-    const EntityReferences &entityReferences, const TraitsDataPtr &relationshipTraitsData,
+    const EntityReferences &entityReferences, const trait::TraitsDataPtr &relationshipTraitsData,
     size_t pageSize, const access::RelationsAccess relationsAccess, const ContextConstPtr &context,
     const Manager::PagedRelationshipSuccessCallback &successCallback,
     const Manager::BatchElementErrorCallback &errorCallback,
@@ -347,7 +348,7 @@ void Manager::preflight(const EntityReferences &entityReferences,
 }
 
 EntityReference Manager::preflight(
-    const EntityReference &entityReference, const TraitsDataPtr &traitsHint,
+    const EntityReference &entityReference, const trait::TraitsDataPtr &traitsHint,
     const access::PublishingAccess publishingAccess, const ContextConstPtr &context,
     [[maybe_unused]] const Manager::BatchElementErrorPolicyTag::Exception &errorPolicyTag) {
   EntityReference result{""};
@@ -366,7 +367,7 @@ EntityReference Manager::preflight(
 }
 
 std::variant<errors::BatchElementError, EntityReference> Manager::preflight(
-    const EntityReference &entityReference, const TraitsDataPtr &traitsHint,
+    const EntityReference &entityReference, const trait::TraitsDataPtr &traitsHint,
     const access::PublishingAccess publishingAccess, const ContextConstPtr &context,
     [[maybe_unused]] const Manager::BatchElementErrorPolicyTag::Variant &errorPolicyTag) {
   std::variant<errors::BatchElementError, EntityReference> result;
@@ -442,7 +443,7 @@ void Manager::register_(const EntityReferences &entityReferences,
 
 // Singular Except
 EntityReference hostApi::Manager::register_(
-    const EntityReference &entityReference, const TraitsDataPtr &entityTraitsData,
+    const EntityReference &entityReference, const trait::TraitsDataPtr &entityTraitsData,
     const access::PublishingAccess publishingAccess, const ContextConstPtr &context,
     [[maybe_unused]] const BatchElementErrorPolicyTag::Exception &errorPolicyTag) {
   EntityReference result("");
@@ -462,7 +463,7 @@ EntityReference hostApi::Manager::register_(
 
 // Singular variant
 std::variant<errors::BatchElementError, EntityReference> hostApi::Manager::register_(
-    const EntityReference &entityReference, const TraitsDataPtr &entityTraitsData,
+    const EntityReference &entityReference, const trait::TraitsDataPtr &entityTraitsData,
     const access::PublishingAccess publishingAccess, const ContextConstPtr &context,
     [[maybe_unused]] const BatchElementErrorPolicyTag::Variant &errorPolicyTag) {
   std::variant<errors::BatchElementError, EntityReference> result;
diff --git a/src/openassetio-core/src/managerApi/ManagerInterface.cpp b/src/openassetio-core/src/managerApi/ManagerInterface.cpp
index 8067739a4..a6475fcef 100644
--- a/src/openassetio-core/src/managerApi/ManagerInterface.cpp
+++ b/src/openassetio-core/src/managerApi/ManagerInterface.cpp
@@ -2,11 +2,12 @@
 // Copyright 2013-2023 The Foundry Visionmongers Ltd
 #include <stdexcept>
 
-#include <openassetio/TraitsData.hpp>
 #include <openassetio/errors/exceptions.hpp>
 #include <openassetio/hostApi/EntityReferencePager.hpp>
 #include <openassetio/managerApi/EntityReferencePagerInterface.hpp>
 #include <openassetio/managerApi/ManagerInterface.hpp>
+#include <openassetio/trait/TraitsData.hpp>
+
 namespace openassetio {
 inline namespace OPENASSETIO_CORE_ABI_VERSION {
 namespace managerApi {
@@ -73,7 +74,7 @@ void ManagerInterface::defaultEntityReference(
 
 void ManagerInterface::getWithRelationship(
     const EntityReferences& entityReferences,
-    [[maybe_unused]] const TraitsDataPtr& relationshipTraitsData,
+    [[maybe_unused]] const trait::TraitsDataPtr& relationshipTraitsData,
     [[maybe_unused]] const trait::TraitSet& resultTraitSet,
     [[maybe_unused]] const access::RelationsAccess relationsAccess,
     [[maybe_unused]] const ContextConstPtr& context,
@@ -116,7 +117,7 @@ class EmptyEntityReferencePagerInterface : public managerApi::EntityReferencePag
 
 void ManagerInterface::getWithRelationshipPaged(
     const EntityReferences& entityReferences,
-    [[maybe_unused]] const TraitsDataPtr& relationshipTraitsData,
+    [[maybe_unused]] const trait::TraitsDataPtr& relationshipTraitsData,
     [[maybe_unused]] const trait::TraitSet& resultTraitSet, [[maybe_unused]] size_t pageSize,
     [[maybe_unused]] const access::RelationsAccess relationsAccess,
     [[maybe_unused]] const ContextConstPtr& context,
diff --git a/src/openassetio-core/src/TraitsData.cpp b/src/openassetio-core/src/trait/TraitsData.cpp
similarity index 98%
rename from src/openassetio-core/src/TraitsData.cpp
rename to src/openassetio-core/src/trait/TraitsData.cpp
index a9b7b8a80..94b64827c 100644
--- a/src/openassetio-core/src/TraitsData.cpp
+++ b/src/openassetio-core/src/trait/TraitsData.cpp
@@ -3,10 +3,12 @@
 
 #include <unordered_map>
 
-#include <openassetio/TraitsData.hpp>
+#include <openassetio/trait/TraitsData.hpp>
 
 namespace openassetio {
 inline namespace OPENASSETIO_CORE_ABI_VERSION {
+namespace trait {
+
 class TraitsData::Impl {
  public:
   Impl() = default;
@@ -124,5 +126,6 @@ trait::property::KeySet TraitsData::traitPropertyKeys(const trait::TraitId& trai
 }
 
 bool TraitsData::operator==(const TraitsData& other) const { return *impl_ == *other.impl_; }
+}  // namespace trait
 }  // namespace OPENASSETIO_CORE_ABI_VERSION
 }  // namespace openassetio
diff --git a/src/openassetio-python/bridge/src/python/converter.cpp b/src/openassetio-python/bridge/src/python/converter.cpp
index f11b351f1..cd1f553ab 100644
--- a/src/openassetio-python/bridge/src/python/converter.cpp
+++ b/src/openassetio-python/bridge/src/python/converter.cpp
@@ -5,7 +5,6 @@
 #include <pybind11/embed.h>
 
 #include <openassetio/Context.hpp>
-#include <openassetio/TraitsData.hpp>
 #include <openassetio/errors/exceptions.hpp>
 #include <openassetio/hostApi/HostInterface.hpp>
 #include <openassetio/hostApi/Manager.hpp>
@@ -18,6 +17,7 @@
 #include <openassetio/managerApi/HostSession.hpp>
 #include <openassetio/managerApi/ManagerInterface.hpp>
 #include <openassetio/managerApi/ManagerStateBase.hpp>
+#include <openassetio/trait/TraitsData.hpp>
 
 // Private headers
 #include <openassetio/private/python/pointers.hpp>
@@ -86,7 +86,7 @@ typename T::Ptr castFromPyObject(PyObject* pyObject) {
   template OPENASSETIO_PYTHON_BRIDGE_EXPORT Class::Ptr castFromPyObject<Class>(PyObject*);
 
 OPENASSETIO_SPECIALIZE_PYTHON_CONVERSIONS(openassetio::Context)
-OPENASSETIO_SPECIALIZE_PYTHON_CONVERSIONS(openassetio::TraitsData)
+OPENASSETIO_SPECIALIZE_PYTHON_CONVERSIONS(trait::TraitsData)
 OPENASSETIO_SPECIALIZE_PYTHON_CONVERSIONS(hostApi::HostInterface)
 OPENASSETIO_SPECIALIZE_PYTHON_CONVERSIONS(hostApi::Manager)
 OPENASSETIO_SPECIALIZE_PYTHON_CONVERSIONS(hostApi::ManagerFactory)
diff --git a/src/openassetio-python/cmodule/CMakeLists.txt b/src/openassetio-python/cmodule/CMakeLists.txt
index 592689b07..8b39a247d 100644
--- a/src/openassetio-python/cmodule/CMakeLists.txt
+++ b/src/openassetio-python/cmodule/CMakeLists.txt
@@ -45,7 +45,6 @@ target_sources(
     src/constantsBinding.cpp
     src/ContextBinding.cpp
     src/EntityReferenceBinding.cpp
-    src/TraitsDataBinding.cpp
     src/errors/exceptionsBinding.cpp
     src/errors/BatchElementErrorBinding.cpp
     src/hostApi/EntityReferencePagerBinding.cpp
@@ -61,6 +60,7 @@ target_sources(
     src/managerApi/EntityReferencePagerInterfaceBinding.cpp
     src/managerApi/ManagerInterfaceBinding.cpp
     src/managerApi/ManagerStateBaseBinding.cpp
+    src/trait/TraitsDataBinding.cpp
 )
 
 target_link_libraries(openassetio-python-module
diff --git a/src/openassetio-python/cmodule/src/ContextBinding.cpp b/src/openassetio-python/cmodule/src/ContextBinding.cpp
index 1b12f369d..2fc6b4011 100644
--- a/src/openassetio-python/cmodule/src/ContextBinding.cpp
+++ b/src/openassetio-python/cmodule/src/ContextBinding.cpp
@@ -4,8 +4,8 @@
 #include <pybind11/stl.h>
 
 #include <openassetio/Context.hpp>
-#include <openassetio/TraitsData.hpp>
 #include <openassetio/managerApi/ManagerStateBase.hpp>
+#include <openassetio/trait/TraitsData.hpp>
 
 #include "PyRetainingSharedPtr.hpp"
 #include "_openassetio.hpp"
@@ -13,8 +13,8 @@
 void registerContext(const py::module& mod) {
   using openassetio::Context;
   using openassetio::ContextPtr;
-  using openassetio::TraitsDataPtr;
   using openassetio::managerApi::ManagerStateBasePtr;
+  using openassetio::trait::TraitsDataPtr;
   using PyRetainingManagerStateBasePtr =
       openassetio::PyRetainingSharedPtr<openassetio::managerApi::ManagerStateBase>;
 
diff --git a/src/openassetio-python/cmodule/src/_openassetio.cpp b/src/openassetio-python/cmodule/src/_openassetio.cpp
index b72c24d05..0d32e58a4 100644
--- a/src/openassetio-python/cmodule/src/_openassetio.cpp
+++ b/src/openassetio-python/cmodule/src/_openassetio.cpp
@@ -18,13 +18,14 @@ PYBIND11_MODULE(_openassetio, mod) {
   const py::module log = mod.def_submodule("log");
   const py::module constants = mod.def_submodule("constants");
   const py::module errors = mod.def_submodule("errors");
+  const py::module trait = mod.def_submodule("trait");
 
   registerAccess(access);
   registerConstants(constants);
   registerLoggerInterface(log);
   registerConsoleLogger(log);
   registerSeverityFilter(log);
-  registerTraitsData(mod);
+  registerTraitsData(trait);
   registerManagerStateBase(managerApi);
   registerContext(mod);
   registerBatchElementError(errors);
diff --git a/src/openassetio-python/cmodule/src/hostApi/EntityReferencePagerBinding.cpp b/src/openassetio-python/cmodule/src/hostApi/EntityReferencePagerBinding.cpp
index 43219f4f8..a743c3663 100644
--- a/src/openassetio-python/cmodule/src/hostApi/EntityReferencePagerBinding.cpp
+++ b/src/openassetio-python/cmodule/src/hostApi/EntityReferencePagerBinding.cpp
@@ -6,7 +6,6 @@
 #include <pybind11/stl.h>
 
 #include <openassetio/Context.hpp>
-#include <openassetio/TraitsData.hpp>
 #include <openassetio/errors/BatchElementError.hpp>
 #include <openassetio/hostApi/EntityReferencePager.hpp>
 #include <openassetio/managerApi/EntityReferencePagerInterface.hpp>
diff --git a/src/openassetio-python/cmodule/src/hostApi/ManagerBinding.cpp b/src/openassetio-python/cmodule/src/hostApi/ManagerBinding.cpp
index 1b70f4628..f7bea03ec 100644
--- a/src/openassetio-python/cmodule/src/hostApi/ManagerBinding.cpp
+++ b/src/openassetio-python/cmodule/src/hostApi/ManagerBinding.cpp
@@ -6,20 +6,20 @@
 #include <pybind11/stl.h>
 
 #include <openassetio/Context.hpp>
-#include <openassetio/TraitsData.hpp>
 #include <openassetio/errors/BatchElementError.hpp>
 #include <openassetio/errors/exceptions.hpp>
 #include <openassetio/hostApi/Manager.hpp>
 #include <openassetio/managerApi/HostSession.hpp>
 #include <openassetio/managerApi/ManagerInterface.hpp>
+#include <openassetio/trait/TraitsData.hpp>
 #include <openassetio/trait/collection.hpp>
 
 #include "../_openassetio.hpp"
 
 namespace {
 using openassetio::EntityReferences;
-using openassetio::TraitsDataPtr;
 using openassetio::hostApi::Manager;
+using openassetio::trait::TraitsDataPtr;
 using openassetio::trait::TraitsDatas;
 
 void validateTraitsDatas(const TraitsDatas& traitsDatas) {
@@ -38,12 +38,12 @@ void registerManager(const py::module& mod) {
   using openassetio::ContextConstPtr;
   using openassetio::EntityReference;
   using openassetio::EntityReferences;
-  using openassetio::TraitsDataPtr;
   using openassetio::errors::BatchElementError;
   using openassetio::hostApi::Manager;
   using openassetio::hostApi::ManagerPtr;
   using openassetio::managerApi::HostSessionPtr;
   using openassetio::managerApi::ManagerInterfacePtr;
+  using openassetio::trait::TraitsDataPtr;
 
   py::class_<Manager, ManagerPtr> pyManager{mod, "Manager"};
 
diff --git a/src/openassetio-python/cmodule/src/managerApi/EntityReferencePagerInterfaceBinding.cpp b/src/openassetio-python/cmodule/src/managerApi/EntityReferencePagerInterfaceBinding.cpp
index aead356a2..752a2e7b6 100644
--- a/src/openassetio-python/cmodule/src/managerApi/EntityReferencePagerInterfaceBinding.cpp
+++ b/src/openassetio-python/cmodule/src/managerApi/EntityReferencePagerInterfaceBinding.cpp
@@ -5,7 +5,6 @@
 
 #include <openassetio/Context.hpp>
 #include <openassetio/InfoDictionary.hpp>
-#include <openassetio/TraitsData.hpp>
 #include <openassetio/managerApi/EntityReferencePagerInterface.hpp>
 #include <openassetio/managerApi/HostSession.hpp>
 #include <openassetio/trait/collection.hpp>
diff --git a/src/openassetio-python/cmodule/src/managerApi/ManagerInterfaceBinding.cpp b/src/openassetio-python/cmodule/src/managerApi/ManagerInterfaceBinding.cpp
index f06e8b87f..f6c3a7970 100644
--- a/src/openassetio-python/cmodule/src/managerApi/ManagerInterfaceBinding.cpp
+++ b/src/openassetio-python/cmodule/src/managerApi/ManagerInterfaceBinding.cpp
@@ -5,12 +5,12 @@
 
 #include <openassetio/Context.hpp>
 #include <openassetio/InfoDictionary.hpp>
-#include <openassetio/TraitsData.hpp>
 #include <openassetio/hostApi/EntityReferencePager.hpp>
 #include <openassetio/managerApi/EntityReferencePagerInterface.hpp>
 #include <openassetio/managerApi/HostSession.hpp>
 #include <openassetio/managerApi/ManagerInterface.hpp>
 #include <openassetio/managerApi/ManagerStateBase.hpp>
+#include <openassetio/trait/TraitsData.hpp>
 #include <openassetio/trait/collection.hpp>
 #include <openassetio/typedefs.hpp>
 
@@ -120,7 +120,7 @@ struct PyManagerInterface : ManagerInterface {
   }
 
   void getWithRelationship(
-      const EntityReferences& entityReferences, const TraitsDataPtr& relationshipTraitsData,
+      const EntityReferences& entityReferences, const trait::TraitsDataPtr& relationshipTraitsData,
       const trait::TraitSet& resultTraitSet, const access::RelationsAccess relationsAccess,
       const ContextConstPtr& context, const HostSessionPtr& hostSession,
       const ManagerInterface::RelationshipSuccessCallback& successCallback,
@@ -142,7 +142,7 @@ struct PyManagerInterface : ManagerInterface {
   }
 
   void getWithRelationshipPaged(
-      const EntityReferences& entityReferences, const TraitsDataPtr& relationshipTraitsData,
+      const EntityReferences& entityReferences, const trait::TraitsDataPtr& relationshipTraitsData,
       const trait::TraitSet& resultTraitSet, size_t pageSize,
       const access::RelationsAccess relationsAccess, const ContextConstPtr& context,
       const HostSessionPtr& hostSession,
@@ -200,12 +200,12 @@ void registerManagerInterface(const py::module& mod) {
   using openassetio::ContextConstPtr;
   using openassetio::EntityReference;
   using openassetio::EntityReferences;
-  using openassetio::TraitsDataPtr;
   using openassetio::managerApi::HostSessionPtr;
   using openassetio::managerApi::ManagerInterface;
   using openassetio::managerApi::ManagerInterfacePtr;
   using openassetio::managerApi::ManagerStateBasePtr;
   using openassetio::managerApi::PyManagerInterface;
+  using openassetio::trait::TraitsDataPtr;
 
   py::class_<ManagerInterface, PyManagerInterface, ManagerInterfacePtr>(mod, "ManagerInterface")
       .def(py::init())
diff --git a/src/openassetio-python/cmodule/src/TraitsDataBinding.cpp b/src/openassetio-python/cmodule/src/trait/TraitsDataBinding.cpp
similarity index 89%
rename from src/openassetio-python/cmodule/src/TraitsDataBinding.cpp
rename to src/openassetio-python/cmodule/src/trait/TraitsDataBinding.cpp
index 50ac27482..ccc9f16a7 100644
--- a/src/openassetio-python/cmodule/src/TraitsDataBinding.cpp
+++ b/src/openassetio-python/cmodule/src/trait/TraitsDataBinding.cpp
@@ -5,15 +5,15 @@
 #include <pybind11/operators.h>
 #include <pybind11/stl.h>
 
-#include <openassetio/TraitsData.hpp>
+#include <openassetio/trait/TraitsData.hpp>
 #include <openassetio/trait/collection.hpp>
 
-#include "_openassetio.hpp"
+#include "../_openassetio.hpp"
 
 void registerTraitsData(const py::module& mod) {
-  using openassetio::TraitsData;
-  using openassetio::TraitsDataConstPtr;
-  using openassetio::TraitsDataPtr;
+  using openassetio::trait::TraitsData;
+  using openassetio::trait::TraitsDataConstPtr;
+  using openassetio::trait::TraitsDataPtr;
   namespace trait = openassetio::trait;
   namespace property = openassetio::trait::property;
   using MaybeValue = std::optional<property::Value>;
diff --git a/src/openassetio-python/package/openassetio/__init__.py b/src/openassetio-python/package/openassetio/__init__.py
index 574268a49..2aff1502a 100644
--- a/src/openassetio-python/package/openassetio/__init__.py
+++ b/src/openassetio-python/package/openassetio/__init__.py
@@ -70,12 +70,20 @@
 The documentation for OpenAssetIO can be found here:
    https://openassetio.github.io/OpenAssetIO.
 """
-# TODO(DF): @pylint
-from ._openassetio import (  # pylint: disable=import-error
+
+# pylint: disable=wrong-import-position,import-error
+
+from ._openassetio import (
     constants,
-    TraitsData,
     Context,
     EntityReference,
 )
 
-# pylint: disable=wrong-import-position
+
+#
+# Deprecated: https://github.com/OpenAssetIO/OpenAssetIO/issues/1127
+#
+from ._openassetio import trait
+
+TraitsData = trait.TraitsData
+del trait
diff --git a/src/openassetio-python/package/openassetio/trait.py b/src/openassetio-python/package/openassetio/trait.py
new file mode 100644
index 000000000..42c30bfa5
--- /dev/null
+++ b/src/openassetio-python/package/openassetio/trait.py
@@ -0,0 +1,24 @@
+#
+#   Copyright 2023 The Foundry Visionmongers Ltd
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+#
+"""
+@namespace openassetio.trait
+Provides the classes related to traits and their data.
+"""
+
+from . import _openassetio  # pylint: disable=no-name-in-module
+
+
+TraitsData = _openassetio.trait.TraitsData


Test patch:
diff --git a/src/openassetio-core/tests/CMakeLists.txt b/src/openassetio-core/tests/CMakeLists.txt
index c9f6529fb..8f1cbf7e0 100644
--- a/src/openassetio-core/tests/CMakeLists.txt
+++ b/src/openassetio-core/tests/CMakeLists.txt
@@ -21,6 +21,7 @@ target_sources(openassetio-core-cpp-test-exe
     BatchElementErrorTest.cpp
     ContextTest.cpp
     TraitsDataTest.cpp
+    deprecationsTest.cpp
     hostApi/ManagerTest.cpp
     managerApi/HostTest.cpp
     managerApi/HostSessionTest.cpp
diff --git a/src/openassetio-core/tests/ContextTest.cpp b/src/openassetio-core/tests/ContextTest.cpp
index 06ca068e3..2c1e277c8 100644
--- a/src/openassetio-core/tests/ContextTest.cpp
+++ b/src/openassetio-core/tests/ContextTest.cpp
@@ -6,12 +6,12 @@
 
 #include <openassetio/Context.hpp>
 
-OPENASSETIO_FWD_DECLARE(TraitsData)
+OPENASSETIO_FWD_DECLARE(trait, TraitsData)
 OPENASSETIO_FWD_DECLARE(managerApi, ManagerStateBase)
 
 using openassetio::Context;
 
 SCENARIO("Context constructor is private") {
-  STATIC_REQUIRE_FALSE(std::is_constructible_v<Context, openassetio::TraitsDataPtr,
+  STATIC_REQUIRE_FALSE(std::is_constructible_v<Context, openassetio::trait::TraitsDataPtr,
                                                openassetio::managerApi::ManagerStateBasePtr>);
 }
diff --git a/src/openassetio-core/tests/TraitsDataTest.cpp b/src/openassetio-core/tests/TraitsDataTest.cpp
index 6f9ff6e86..c8aa8afeb 100644
--- a/src/openassetio-core/tests/TraitsDataTest.cpp
+++ b/src/openassetio-core/tests/TraitsDataTest.cpp
@@ -4,13 +4,13 @@
 
 #include <catch2/catch.hpp>
 
-#include <openassetio/TraitsData.hpp>
+#include <openassetio/trait/TraitsData.hpp>
 #include <openassetio/trait/collection.hpp>
 #include <openassetio/trait/property.hpp>
 
 using openassetio::Int;
-using openassetio::TraitsData;
-using openassetio::TraitsDataPtr;
+using openassetio::trait::TraitsData;
+using openassetio::trait::TraitsDataPtr;
 using openassetio::trait::property::Key;
 using openassetio::trait::property::Value;
 
@@ -23,7 +23,7 @@ SCENARIO("TraitsData trait set constructor is private") {
 }
 
 SCENARIO("TraitsData copy constructor is private") {
-  STATIC_REQUIRE_FALSE(std::is_constructible_v<TraitsData, const openassetio::TraitsData&>);
+  STATIC_REQUIRE_FALSE(std::is_constructible_v<TraitsData, const TraitsData&>);
 }
 
 SCENARIO("TraitsData make from other creates a deep copy") {
diff --git a/src/openassetio-core/tests/deprecationsTest.cpp b/src/openassetio-core/tests/deprecationsTest.cpp
new file mode 100644
index 000000000..e9aa0f9f3
--- /dev/null
+++ b/src/openassetio-core/tests/deprecationsTest.cpp
@@ -0,0 +1,23 @@
+// SPDX-License-Identifier: Apache-2.0
+// Copyright 2023 The Foundry Visionmongers Ltd
+#include <type_traits>
+
+#include <catch2/catch.hpp>
+
+#include <openassetio/TraitsData.hpp>
+#include <openassetio/trait/TraitsData.hpp>
+
+// Disable deprecation warning that triggers -Werror, failing the build.
+#if defined(__clang__)
+#pragma clang diagnostic ignored "-Wdeprecated-declarations"
+#elif defined(__GNUC__)
+#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
+#endif
+
+// Tests for deprecated functionality.
+// All cases must have links to the relevant GitHub issues.
+
+// https://github.com/OpenAssetIO/OpenAssetIO/issues/1127
+SCENARIO("TraitsData still accessible in top level namespace") {
+  STATIC_REQUIRE(std::is_same_v<openassetio::TraitsData, openassetio::trait::TraitsData>);
+}
diff --git a/src/openassetio-core/tests/hostApi/ManagerTest.cpp b/src/openassetio-core/tests/hostApi/ManagerTest.cpp
index dbad3419c..d725b6dff 100644
--- a/src/openassetio-core/tests/hostApi/ManagerTest.cpp
+++ b/src/openassetio-core/tests/hostApi/ManagerTest.cpp
@@ -9,7 +9,6 @@
 #include <catch2/trompeloeil.hpp>
 
 #include <openassetio/Context.hpp>
-#include <openassetio/TraitsData.hpp>
 #include <openassetio/errors/exceptions.hpp>
 #include <openassetio/hostApi/HostInterface.hpp>
 #include <openassetio/hostApi/Manager.hpp>
@@ -17,6 +16,8 @@
 #include <openassetio/managerApi/Host.hpp>
 #include <openassetio/managerApi/HostSession.hpp>
 #include <openassetio/managerApi/ManagerInterface.hpp>
+#include <openassetio/trait/TraitsData.hpp>
+
 namespace openassetio {
 inline namespace OPENASSETIO_CORE_ABI_VERSION {
 namespace {
@@ -118,7 +119,7 @@ SCENARIO("Resolving entities") {
       const openassetio::EntityReference ref = openassetio::EntityReference{"testReference"};
       const openassetio::EntityReferences refs = {ref};
 
-      const openassetio::TraitsDataPtr expected = openassetio::TraitsData::make();
+      const openassetio::trait::TraitsDataPtr expected = openassetio::trait::TraitsData::make();
       expected->addTrait("aTestTrait");
 
       // With success callback side effect
@@ -127,23 +128,23 @@ SCENARIO("Resolving entities") {
           .LR_SIDE_EFFECT(_6(0, expected));
 
       WHEN("singular resolve is called with default errorPolicyTag") {
-        const openassetio::TraitsDataPtr actual =
+        const openassetio::trait::TraitsDataPtr actual =
             manager->resolve(ref, traits, resolveAccess, context);
         THEN("returned TraitsData is as expected") { CHECK(expected.get() == actual.get()); }
       }
       WHEN("singular resolve is called with kException errorPolicyTag") {
-        const openassetio::TraitsDataPtr actual =
+        const openassetio::trait::TraitsDataPtr actual =
             manager->resolve(ref, traits, resolveAccess, context,
                              hostApi::Manager::BatchElementErrorPolicyTag::kException);
         THEN("returned TraitsData is as expected") { CHECK(expected.get() == actual.get()); }
       }
       WHEN("singular resolve is called with kVariant errorPolicyTag") {
-        std::variant<openassetio::errors::BatchElementError, openassetio::TraitsDataPtr> actual =
-            manager->resolve(ref, traits, resolveAccess, context,
-                             hostApi::Manager::BatchElementErrorPolicyTag::kVariant);
+        std::variant<openassetio::errors::BatchElementError, openassetio::trait::TraitsDataPtr>
+            actual = manager->resolve(ref, traits, resolveAccess, context,
+                                      hostApi::Manager::BatchElementErrorPolicyTag::kVariant);
         THEN("returned variant contains the expected TraitsData") {
-          CHECK(std::holds_alternative<openassetio::TraitsDataPtr>(actual));
-          auto actualVal = std::get<openassetio::TraitsDataPtr>(actual);
+          CHECK(std::holds_alternative<openassetio::trait::TraitsDataPtr>(actual));
+          auto actualVal = std::get<openassetio::trait::TraitsDataPtr>(actual);
           CHECK(expected.get() == actualVal.get());
         }
       }
@@ -153,13 +154,13 @@ SCENARIO("Resolving entities") {
                                                   openassetio::EntityReference{"testReference2"},
                                                   openassetio::EntityReference{"testReference3"}};
 
-      const openassetio::TraitsDataPtr expected1 = openassetio::TraitsData::make();
+      const openassetio::trait::TraitsDataPtr expected1 = openassetio::trait::TraitsData::make();
       expected1->addTrait("aTestTrait1");
-      const openassetio::TraitsDataPtr expected2 = openassetio::TraitsData::make();
+      const openassetio::trait::TraitsDataPtr expected2 = openassetio::trait::TraitsData::make();
       expected2->addTrait("aTestTrait2");
-      const openassetio::TraitsDataPtr expected3 = openassetio::TraitsData::make();
+      const openassetio::trait::TraitsDataPtr expected3 = openassetio::trait::TraitsData::make();
       expected3->addTrait("aTestTrait3");
-      std::vector<openassetio::TraitsDataPtr> expectedVec{expected1, expected2, expected3};
+      std::vector<openassetio::trait::TraitsDataPtr> expectedVec{expected1, expected2, expected3};
 
       // With success callback side effect
       REQUIRE_CALL(mockManagerInterface,
@@ -169,26 +170,26 @@ SCENARIO("Resolving entities") {
           .LR_SIDE_EFFECT(_6(2, expectedVec[2]));
 
       WHEN("batch resolve is called with default errorPolicyTag") {
-        const std::vector<openassetio::TraitsDataPtr> actualVec =
+        const std::vector<openassetio::trait::TraitsDataPtr> actualVec =
             manager->resolve(refs, traits, resolveAccess, context);
         THEN("returned list of TraitsDatas is as expected") { CHECK(expectedVec == actualVec); }
       }
       WHEN("batch resolve is called with kException errorPolicyTag") {
-        const std::vector<openassetio::TraitsDataPtr> actualVec =
+        const std::vector<openassetio::trait::TraitsDataPtr> actualVec =
             manager->resolve(refs, traits, resolveAccess, context,
                              hostApi::Manager::BatchElementErrorPolicyTag::kException);
         THEN("returned list of TraitsDatas is as expected") { CHECK(expectedVec == actualVec); }
       }
       WHEN("batch resolve is called with kVariant errorPolicyTag") {
-        std::vector<
-            std::variant<openassetio::errors::BatchElementError, openassetio::TraitsDataPtr>>
+        std::vector<std::variant<openassetio::errors::BatchElementError,
+                                 openassetio::trait::TraitsDataPtr>>
             actualVec = manager->resolve(refs, traits, resolveAccess, context,
                                          hostApi::Manager::BatchElementErrorPolicyTag::kVariant);
         THEN("returned lists of variants contains the expected TraitsDatas") {
           CHECK(expectedVec.size() == actualVec.size());
           for (size_t i = 0; i < actualVec.size(); ++i) {
-            CHECK(std::holds_alternative<openassetio::TraitsDataPtr>(actualVec[i]));
-            auto actualVal = std::get<openassetio::TraitsDataPtr>(actualVec[i]);
+            CHECK(std::holds_alternative<openassetio::trait::TraitsDataPtr>(actualVec[i]));
+            auto actualVal = std::get<openassetio::trait::TraitsDataPtr>(actualVec[i]);
             CHECK(expectedVec[i].get() == actualVal.get());
           }
         }
@@ -199,13 +200,13 @@ SCENARIO("Resolving entities") {
                                                   openassetio::EntityReference{"testReference2"},
                                                   openassetio::EntityReference{"testReference3"}};
 
-      const openassetio::TraitsDataPtr expected1 = openassetio::TraitsData::make();
+      const openassetio::trait::TraitsDataPtr expected1 = openassetio::trait::TraitsData::make();
       expected1->addTrait("aTestTrait1");
-      const openassetio::TraitsDataPtr expected2 = openassetio::TraitsData::make();
+      const openassetio::trait::TraitsDataPtr expected2 = openassetio::trait::TraitsData::make();
       expected2->addTrait("aTestTrait2");
-      const openassetio::TraitsDataPtr expected3 = openassetio::TraitsData::make();
+      const openassetio::trait::TraitsDataPtr expected3 = openassetio::trait::TraitsData::make();
       expected3->addTrait("aTestTrait3");
-      std::vector<openassetio::TraitsDataPtr> expectedVec{expected1, expected2, expected3};
+      std::vector<openassetio::trait::TraitsDataPtr> expectedVec{expected1, expected2, expected3};
 
       // With success callback side effect, given out of order.
       REQUIRE_CALL(mockManagerInterface,
@@ -215,14 +216,14 @@ SCENARIO("Resolving entities") {
           .LR_SIDE_EFFECT(_6(1, expectedVec[1]));
 
       WHEN("batch resolve is called with default errorPolicyTag") {
-        const std::vector<openassetio::TraitsDataPtr> actualVec =
+        const std::vector<openassetio::trait::TraitsDataPtr> actualVec =
             manager->resolve(refs, traits, resolveAccess, context);
         THEN("returned list of TraitsDatas is ordered in index order") {
           CHECK(expectedVec == actualVec);
         }
       }
       WHEN("batch resolve is called with kException errorPolicyTag") {
-        const std::vector<openassetio::TraitsDataPtr> actualVec =
+        const std::vector<openassetio::trait::TraitsDataPtr> actualVec =
             manager->resolve(refs, traits, resolveAccess, context,
                              hostApi::Manager::BatchElementErrorPolicyTag::kException);
         THEN("returned list of TraitsDatas is ordered in index order") {
@@ -230,15 +231,15 @@ SCENARIO("Resolving entities") {
         }
       }
       WHEN("batch resolve is called with kVariant errorPolicyTag") {
-        std::vector<
-            std::variant<openassetio::errors::BatchElementError, openassetio::TraitsDataPtr>>
+        std::vector<std::variant<openassetio::errors::BatchElementError,
+                                 openassetio::trait::TraitsDataPtr>>
             actualVec = manager->resolve(refs, traits, resolveAccess, context,
                                          hostApi::Manager::BatchElementErrorPolicyTag::kVariant);
         THEN("returned lists of variants is ordered in index order") {
           CHECK(expectedVec.size() == actualVec.size());
           for (size_t i = 0; i < actualVec.size(); ++i) {
-            CHECK(std::holds_alternative<openassetio::TraitsDataPtr>(actualVec[i]));
-            auto actualVal = std::get<openassetio::TraitsDataPtr>(actualVec[i]);
+            CHECK(std::holds_alternative<openassetio::trait::TraitsDataPtr>(actualVec[i]));
+            auto actualVal = std::get<openassetio::trait::TraitsDataPtr>(actualVec[i]);
             CHECK(expectedVec[i].get() == actualVal.get());
           }
         }
@@ -275,9 +276,9 @@ SCENARIO("Resolving entities") {
         }
       }
       WHEN("singular resolve is called with kVariant errorPolicyTag") {
-        std::variant<openassetio::errors::BatchElementError, openassetio::TraitsDataPtr> actual =
-            manager->resolve(ref, traits, resolveAccess, context,
-                             hostApi::Manager::BatchElementErrorPolicyTag::kVariant);
+        std::variant<openassetio::errors::BatchElementError, openassetio::trait::TraitsDataPtr>
+            actual = manager->resolve(ref, traits, resolveAccess, context,
+                                      hostApi::Manager::BatchElementErrorPolicyTag::kVariant);
         THEN("returned variant contains the expected BatchElementError") {
           CHECK(std::holds_alternative<openassetio::errors::BatchElementError>(actual));
           const auto& actualVal = std::get<openassetio::errors::BatchElementError>(actual);
@@ -292,7 +293,8 @@ SCENARIO("Resolving entities") {
                                                   openassetio::EntityReference{"testReference2"},
                                                   openassetio::EntityReference{"testReference3"}};
 
-      const openassetio::TraitsDataPtr expectedValue2 = openassetio::TraitsData::make();
+      const openassetio::trait::TraitsDataPtr expectedValue2 =
+          openassetio::trait::TraitsData::make();
       expectedValue2->addTrait("aTestTrait");
       const openassetio::errors::BatchElementError expectedError0{
           openassetio::errors::BatchElementError::ErrorCode::kMalformedEntityReference,
@@ -325,8 +327,8 @@ SCENARIO("Resolving entities") {
         }
       }
       WHEN("batch resolve is called with kVariant errorPolicyTag") {
-        std::vector<
-            std::variant<openassetio::errors::BatchElementError, openassetio::TraitsDataPtr>>
+        std::vector<std::variant<openassetio::errors::BatchElementError,
+                                 openassetio::trait::TraitsDataPtr>>
             actualVec = manager->resolve(refs, traits, resolveAccess, context,
                                          hostApi::Manager::BatchElementErrorPolicyTag::kVariant);
         THEN("returned lists of variants contains the expected objects") {
@@ -336,7 +338,7 @@ SCENARIO("Resolving entities") {
           auto error1 = std::get<openassetio::errors::BatchElementError>(actualVec[1]);
           CHECK(error1 == expectedError1);
 
-          CHECK(std::get<openassetio::TraitsDataPtr>(actualVec[2]) == expectedValue2);
+          CHECK(std::get<openassetio::trait::TraitsDataPtr>(actualVec[2]) == expectedValue2);
         }
       }
     }
@@ -356,8 +358,8 @@ SCENARIO("Preflighting entities") {
         openassetio::EntityReference{"testReference2"},
         openassetio::EntityReference{"testReference3"}};
 
-    const openassetio::TraitsDataPtr traitsData =
-        openassetio::TraitsData::make({"fakeTrait", "secondFakeTrait"});
+    const openassetio::trait::TraitsDataPtr traitsData =
+        openassetio::trait::TraitsData::make({"fakeTrait", "secondFakeTrait"});
     const openassetio::trait::TraitsDatas threeTraitsDatas{traitsData, traitsData, traitsData};
 
     const openassetio::ManagerFixture fixture;
@@ -594,10 +596,10 @@ SCENARIO("Registering entities") {
     // Create TraitSets used in the register method calls
     const openassetio::trait::TraitSet traits = {"fakeTrait", "secondFakeTrait"};
     const openassetio::trait::TraitsDatas threeTraitsDatas = {
-        openassetio::TraitsData::make(traits), openassetio::TraitsData::make(traits),
-        openassetio::TraitsData::make(traits)};
+        openassetio::trait::TraitsData::make(traits), openassetio::trait::TraitsData::make(traits),
+        openassetio::trait::TraitsData::make(traits)};
 
-    auto singleTraitsData = openassetio::TraitsData::make(traits);
+    auto singleTraitsData = openassetio::trait::TraitsData::make(traits);
     const openassetio::trait::TraitsDatas singleTraitsDatas = {singleTraitsData};
 
     GIVEN("manager plugin successfully registers a single entity reference") {
diff --git a/src/openassetio-core/tests/typedefsTest.cpp b/src/openassetio-core/tests/typedefsTest.cpp
index 355e1dbc3..f66a474ba 100644
--- a/src/openassetio-core/tests/typedefsTest.cpp
+++ b/src/openassetio-core/tests/typedefsTest.cpp
@@ -6,7 +6,6 @@
 #include <catch2/catch.hpp>
 
 #include <openassetio/Context.hpp>
-#include <openassetio/TraitsData.hpp>
 #include <openassetio/hostApi/HostInterface.hpp>
 #include <openassetio/hostApi/Manager.hpp>
 #include <openassetio/hostApi/ManagerFactory.hpp>
@@ -18,6 +17,7 @@
 #include <openassetio/managerApi/HostSession.hpp>
 #include <openassetio/managerApi/ManagerInterface.hpp>
 #include <openassetio/managerApi/ManagerStateBase.hpp>
+#include <openassetio/trait/TraitsData.hpp>
 
 namespace {
 
@@ -30,7 +30,7 @@ namespace managerApi = openassetio::managerApi;
 // clang-format off
 using ClassesWithPtrAlias = std::tuple<
     CLASS_AND_PTRS(openassetio::Context),
-    CLASS_AND_PTRS(openassetio::TraitsData),
+    CLASS_AND_PTRS(openassetio::trait::TraitsData),
     CLASS_AND_PTRS(hostApi::HostInterface),
     CLASS_AND_PTRS(hostApi::Manager),
     CLASS_AND_PTRS(hostApi::ManagerFactory),
diff --git a/src/openassetio-python/package/openassetio/test/manager/_implementation.py b/src/openassetio-python/package/openassetio/test/manager/_implementation.py
index 64c63cf51..9128846b2 100644
--- a/src/openassetio-python/package/openassetio/test/manager/_implementation.py
+++ b/src/openassetio-python/package/openassetio/test/manager/_implementation.py
@@ -20,7 +20,8 @@
 """
 import unittest
 
-from openassetio import hostApi, log, pluginSystem, TraitsData
+from openassetio import hostApi, log, pluginSystem
+from openassetio.trait import TraitsData
 from .. import kTestHarnessTraitId, kCasePropertyKey
 
 
diff --git a/src/openassetio-python/package/openassetio/test/manager/apiComplianceSuite.py b/src/openassetio-python/package/openassetio/test/manager/apiComplianceSuite.py
index df074cc0f..5c703d29e 100644
--- a/src/openassetio-python/package/openassetio/test/manager/apiComplianceSuite.py
+++ b/src/openassetio-python/package/openassetio/test/manager/apiComplianceSuite.py
@@ -37,9 +37,10 @@
 # pylint: disable=too-many-lines,unbalanced-tuple-unpacking
 
 from .harness import FixtureAugmentedTestCase
-from ... import EntityReference, TraitsData
+from ... import EntityReference
 from ...errors import BatchElementError
 from ...access import PolicyAccess, ResolveAccess, RelationsAccess, PublishingAccess
+from ...trait import TraitsData
 
 
 __all__ = []
diff --git a/src/openassetio-python/package/openassetio/test/manager/harness.py b/src/openassetio-python/package/openassetio/test/manager/harness.py
index 03e943653..9dfb9d7fc 100644
--- a/src/openassetio-python/package/openassetio/test/manager/harness.py
+++ b/src/openassetio-python/package/openassetio/test/manager/harness.py
@@ -188,8 +188,8 @@ def __init__(self, fixtures, manager, locale, *args, **kwargs):
         @param manager hostApi.Manager.Manager The OpenAssetIO
         @ref manager to be used by test cases.
 
-        @param locale @fqref{TraitsData} "TraitsData" The @ref locale to
-        use by test cases.
+        @param locale @fqref{trait.TraitsData} "TraitsData" The @ref
+        locale to use by test cases.
 
         @param args `List[Any]` Additional args passed along to the
         base class.
diff --git a/src/openassetio-python/tests/bridge/python/test_converter.cpp b/src/openassetio-python/tests/bridge/python/test_converter.cpp
index 4a72da6fc..6284e2c6c 100644
--- a/src/openassetio-python/tests/bridge/python/test_converter.cpp
+++ b/src/openassetio-python/tests/bridge/python/test_converter.cpp
@@ -11,7 +11,6 @@
 #include <openassetio/python/converter.hpp>
 
 #include <openassetio/Context.hpp>
-#include <openassetio/TraitsData.hpp>
 #include <openassetio/hostApi/HostInterface.hpp>
 #include <openassetio/hostApi/Manager.hpp>
 #include <openassetio/hostApi/ManagerFactory.hpp>
@@ -23,6 +22,7 @@
 #include <openassetio/managerApi/HostSession.hpp>
 #include <openassetio/managerApi/ManagerInterface.hpp>
 #include <openassetio/managerApi/ManagerStateBase.hpp>
+#include <openassetio/trait/TraitsData.hpp>
 
 /*
  * The below tests exercise the to/from python converter functions.
@@ -48,7 +48,7 @@ SCENARIO("Mutations in one language are reflected in the other") {
   py::module_::import("openassetio");
 
   GIVEN("A C++ object casted to a Python object") {
-    const TraitsDataPtr traitsData = TraitsData::make();
+    const trait::TraitsDataPtr traitsData = trait::TraitsData::make();
     PyObject* pyTraitsData = openassetio::python::converter::castToPyObject(traitsData);
     REQUIRE(pyTraitsData != nullptr);
 
@@ -76,11 +76,11 @@ SCENARIO("Mutations in one language are reflected in the other") {
 
   GIVEN("A Python object casted to a C++ object") {
     // Use pybind to conveniently create a CPython object with a ref count of 1.
-    const py::object pyClass = py::module_::import("openassetio").attr("TraitsData");
+    const py::object pyClass = py::module_::import("openassetio.trait").attr("TraitsData");
     py::object pyInstance = pyClass();
     PyObject* pyTraitsData = pyInstance.release().ptr();
-    const TraitsDataPtr traitsData =
-        openassetio::python::converter::castFromPyObject<TraitsData>(pyTraitsData);
+    const trait::TraitsDataPtr traitsData =
+        openassetio::python::converter::castFromPyObject<trait::TraitsData>(pyTraitsData);
 
     WHEN("data is set via the C++ object") {
       traitsData->addTrait(kTestTraitId);
@@ -106,7 +106,7 @@ SCENARIO("Mutations in one language are reflected in the other") {
 
 SCENARIO("Casting to PyObject extends object lifetime") {
   GIVEN("A Python object casted from a C++ object") {
-    TraitsDataPtr traitsData = TraitsData::make();
+    trait::TraitsDataPtr traitsData = trait::TraitsData::make();
     traitsData->addTrait(kTestTraitId);
     PyObject* pyTraitsData = openassetio::python::converter::castToPyObject(traitsData);
 
@@ -134,14 +134,14 @@ SCENARIO("Casting to a C++ object binds object lifetime") {
     py::module_::import("openassetio");
 
     // Use pybind to conveniently create a CPython object with a ref count of 1.
-    const py::object pyClass = py::module_::import("openassetio").attr("TraitsData");
+    const py::object pyClass = py::module_::import("openassetio.trait").attr("TraitsData");
     py::object pyInstance = pyClass();
     PyObject* pyTraitsData = pyInstance.release().ptr();
     CHECK(Py_REFCNT(pyTraitsData) == 1);
 
     WHEN("Python object is converted to a C++ object") {
-      TraitsDataPtr traitsData =
-          openassetio::python::converter::castFromPyObject<TraitsData>(pyTraitsData);
+      trait::TraitsDataPtr traitsData =
+          openassetio::python::converter::castFromPyObject<trait::TraitsData>(pyTraitsData);
 
       THEN("Python reference is obtained") { CHECK(Py_REFCNT(pyTraitsData) == 2); }
 
@@ -202,13 +202,13 @@ SCENARIO("Error attempting to convert API objects without openassetio module loa
     REQUIRE_FALSE(py::module_::import("sys").attr("modules").contains("openassetio"));
 
     AND_GIVEN("an OpenAssetIO C++ API object") {
-      const TraitsDataPtr traitsData = TraitsData::make();
+      const trait::TraitsDataPtr traitsData = trait::TraitsData::make();
 
       WHEN("the C++ object is casted to a Python object") {
         THEN("cast throws expected exception") {
           REQUIRE_THROWS_WITH(openassetio::python::converter::castToPyObject(traitsData),
                               std::string("Unregistered type : openassetio::" STRINGIFY(
-                                  OPENASSETIO_CORE_ABI_VERSION) "::TraitsData"));
+                                  OPENASSETIO_CORE_ABI_VERSION) "::trait::TraitsData"));
         }
       }
 
@@ -227,7 +227,7 @@ SCENARIO("Error attempting to convert API objects without openassetio module loa
           THEN("cast throws expected exception") {
             REQUIRE_THROWS_WITH(openassetio::python::converter::castToPyObject(traitsData),
                                 std::string("Unregistered type : openassetio::" STRINGIFY(
-                                    OPENASSETIO_CORE_ABI_VERSION) "::TraitsData"));
+                                    OPENASSETIO_CORE_ABI_VERSION) "::trait::TraitsData"));
 
             AND_THEN("CPython error state is maintained") {
               // castToPyObject, despite messing with the PyErr state
@@ -252,7 +252,7 @@ namespace managerApi = openassetio::managerApi;
 // clang-format off
 using CastableClasses = std::tuple<
     openassetio::Context,
-    openassetio::TraitsData,
+    openassetio::trait::TraitsData,
     hostApi::HostInterface,
     hostApi::Manager,
     hostApi::ManagerFactory,
diff --git a/src/openassetio-python/tests/conftest.py b/src/openassetio-python/tests/conftest.py
index df96d4106..50c9ef619 100644
--- a/src/openassetio-python/tests/conftest.py
+++ b/src/openassetio-python/tests/conftest.py
@@ -23,7 +23,7 @@
 
 import pytest
 
-from openassetio import Context, EntityReference, TraitsData
+from openassetio import Context, EntityReference
 from openassetio.access import (
     PolicyAccess,
     ResolveAccess,
@@ -39,6 +39,7 @@
     EntityReferencePagerInterface,
 )
 from openassetio.hostApi import HostInterface
+from openassetio.trait import TraitsData
 
 
 # pylint: disable=invalid-name
diff --git a/src/openassetio-python/tests/package/hostApi/test_manager.py b/src/openassetio-python/tests/package/hostApi/test_manager.py
index 91c3a36a3..e8d831eff 100644
--- a/src/openassetio-python/tests/package/hostApi/test_manager.py
+++ b/src/openassetio-python/tests/package/hostApi/test_manager.py
@@ -28,7 +28,6 @@
 from openassetio import (
     Context,
     EntityReference,
-    TraitsData,
     managerApi,
     constants,
     access,
@@ -41,6 +40,7 @@
 )
 from openassetio.hostApi import Manager, EntityReferencePager
 from openassetio.managerApi import EntityReferencePagerInterface
+from openassetio.trait import TraitsData
 
 
 ## @todo Remove comments regarding Entity methods when splitting them from core API
diff --git a/src/openassetio-python/tests/package/managerApi/test_managerinterface.py b/src/openassetio-python/tests/package/managerApi/test_managerinterface.py
index 854fecfd5..e6c20d71f 100644
--- a/src/openassetio-python/tests/package/managerApi/test_managerinterface.py
+++ b/src/openassetio-python/tests/package/managerApi/test_managerinterface.py
@@ -25,13 +25,14 @@
 
 import pytest
 
-from openassetio import EntityReference, TraitsData, Context, errors
+from openassetio import EntityReference, Context, errors
+from openassetio.access import RelationsAccess, DefaultEntityAccess
 from openassetio.managerApi import (
     ManagerInterface,
     ManagerStateBase,
     EntityReferencePagerInterface,
 )
-from openassetio.access import RelationsAccess, DefaultEntityAccess
+from openassetio.trait import TraitsData
 
 
 class Test_ManagerInterface_identifier:
diff --git a/src/openassetio-python/tests/package/test/manager/conftest.py b/src/openassetio-python/tests/package/test/manager/conftest.py
index f9bf9fcdf..47cf42c8b 100644
--- a/src/openassetio-python/tests/package/test/manager/conftest.py
+++ b/src/openassetio-python/tests/package/test/manager/conftest.py
@@ -25,7 +25,8 @@
 
 import pytest
 
-from openassetio import hostApi, TraitsData, EntityReference
+from openassetio import hostApi, EntityReference
+from openassetio.trait import TraitsData
 
 
 @pytest.fixture
diff --git a/src/openassetio-python/tests/package/test/manager/resources/plugins/StubManager.py b/src/openassetio-python/tests/package/test/manager/resources/plugins/StubManager.py
index d0008c291..b3e7553cc 100644
--- a/src/openassetio-python/tests/package/test/manager/resources/plugins/StubManager.py
+++ b/src/openassetio-python/tests/package/test/manager/resources/plugins/StubManager.py
@@ -19,9 +19,9 @@
 test suite.
 """
 
-from openassetio import TraitsData
 from openassetio.managerApi import ManagerInterface
 from openassetio.pluginSystem import PythonPluginSystemManagerPlugin
+from openassetio.trait import TraitsData
 
 
 class StubManager(ManagerInterface):
diff --git a/src/openassetio-python/tests/package/test/manager/test__implementation.py b/src/openassetio-python/tests/package/test/manager/test__implementation.py
index 8b55a8819..329718e1d 100644
--- a/src/openassetio-python/tests/package/test/manager/test__implementation.py
+++ b/src/openassetio-python/tests/package/test/manager/test__implementation.py
@@ -28,13 +28,13 @@
 
 import pytest
 
-from openassetio import TraitsData
 from openassetio.test import kTestHarnessTraitId, kCasePropertyKey
 from openassetio.test.manager.harness import FixtureAugmentedTestCase
 from openassetio.test.manager._implementation import (
     _ValidatorHarnessHostInterface,
     _ValidatorTestLoader,
 )
+from openassetio.trait import TraitsData
 
 
 class Test_Loader_loadTestsFromTestCase:
diff --git a/src/openassetio-python/tests/package/test_constants.py b/src/openassetio-python/tests/package/test_constants.py
index 05cfb77b6..ad64989e6 100644
--- a/src/openassetio-python/tests/package/test_constants.py
+++ b/src/openassetio-python/tests/package/test_constants.py
@@ -26,9 +26,3 @@ def test():
     assert constants.kInfoKey_SmallIcon == "smallIcon"
     assert constants.kInfoKey_Icon == "icon"
     assert constants.kInfoKey_EntityReferencesMatchPrefix == "entityReferencesMatchPrefix"
-    assert constants.kField_SmallIcon == constants.kInfoKey_SmallIcon
-    assert constants.kField_Icon == constants.kInfoKey_Icon
-    assert (
-        constants.kField_EntityReferencesMatchPrefix
-        == constants.kInfoKey_EntityReferencesMatchPrefix
-    )
diff --git a/src/openassetio-python/tests/package/test_context.py b/src/openassetio-python/tests/package/test_context.py
index 90da20b15..e3ba202bc 100644
--- a/src/openassetio-python/tests/package/test_context.py
+++ b/src/openassetio-python/tests/package/test_context.py
@@ -21,7 +21,8 @@
 # pylint: disable=missing-class-docstring,missing-function-docstring
 import pytest
 
-from openassetio import Context, managerApi, TraitsData
+from openassetio import Context, managerApi
+from openassetio.trait import TraitsData
 
 
 class Test_Context_init:
@@ -47,7 +48,7 @@ class TestState(managerApi.ManagerStateBase):
 class Test_Context_locale:
     def test_when_set_to_unknown_value_then_raises_ValueError(self, a_context):
         expected_msg = (
-            r"incompatible function arguments.*\n.*arg0: openassetio._openassetio.TraitsData"
+            r"incompatible function arguments.*\n.*arg0: openassetio._openassetio.trait.TraitsData"
         )
 
         with pytest.raises(TypeError, match=expected_msg):
diff --git a/src/openassetio-python/tests/package/test_deprecations.py b/src/openassetio-python/tests/package/test_deprecations.py
new file mode 100644
index 000000000..1061d4f62
--- /dev/null
+++ b/src/openassetio-python/tests/package/test_deprecations.py
@@ -0,0 +1,46 @@
+#
+#   Copyright 2023 The Foundry Visionmongers Ltd
+#
+#   Licensed under the Apache License, Version 2.0 (the "License");
+#   you may not use this file except in compliance with the License.
+#   You may obtain a copy of the License at
+#
+#       http://www.apache.org/licenses/LICENSE-2.0
+#
+#   Unless required by applicable law or agreed to in writing, software
+#   distributed under the License is distributed on an "AS IS" BASIS,
+#   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+#   See the License for the specific language governing permissions and
+#   limitations under the License.
+#
+"""
+Tests for deprecated functionality.
+
+All cases must have a link to the relevant GitHub issue in their
+docstrings.
+"""
+
+# pylint: disable=missing-function-docstring,import-outside-toplevel
+
+
+def test_renamed_kField_constants():
+    """
+    https://github.com/OpenAssetIO/OpenAssetIO/issues/998
+    """
+    from openassetio import constants
+
+    assert constants.kField_SmallIcon == constants.kInfoKey_SmallIcon
+    assert constants.kField_Icon == constants.kInfoKey_Icon
+    assert (
+        constants.kField_EntityReferencesMatchPrefix
+        == constants.kInfoKey_EntityReferencesMatchPrefix
+    )
+
+
+def test_top_level_TraitsData_import():
+    """
+    https://github.com/OpenAssetIO/OpenAssetIO/issues/1127
+    """
+    from openassetio import trait, TraitsData
+
+    assert TraitsData is trait.TraitsData
diff --git a/src/openassetio-python/tests/package/test_imports.py b/src/openassetio-python/tests/package/test_imports.py
index c80494f46..b1e4dd8fe 100644
--- a/src/openassetio-python/tests/package/test_imports.py
+++ b/src/openassetio-python/tests/package/test_imports.py
@@ -59,9 +59,6 @@ def test_importing_EntityReference_succeeds(self):
     def test_importing_log_succeeds(self):
         from openassetio import log
 
-    def test_importing_TraitsData_succeeds(self):
-        from openassetio import TraitsData
-
 
 class Test_core_imports:
     def test_importing_audit_succeeds(self):
@@ -135,6 +132,11 @@ def test_importing_UnhandledException_succeeds(self):
         from openassetio.errors import UnhandledException
 
 
+class Test_trait_imports:
+    def test_importing_TraitsData_succeeds(self):
+        from openassetio.trait import TraitsData
+
+
 class Test_test_imports:
     def test_importing_manager_succeeds(self):
         from openassetio.test import manager
diff --git a/src/openassetio-python/tests/package/test_traitsdata.py b/src/openassetio-python/tests/package/test_traitsdata.py
index c92312c05..5556042ae 100644
--- a/src/openassetio-python/tests/package/test_traitsdata.py
+++ b/src/openassetio-python/tests/package/test_traitsdata.py
@@ -8,7 +8,7 @@
 
 # TODO(DF): @pylint - re-enable once Python dev vs. install mess sorted.
 # pylint: disable=no-name-in-module
-from openassetio import TraitsData
+from openassetio.trait import TraitsData
 
 
 class Test_TraitsData_Inheritance: