Repository: smarie/python-yamlable

Issue:
Consider Sequence Support?
```python
# coding: utf-8
from yamlable import YamlObject2
import yaml

class Base:
    def __init__(self, *args, **kwargs):
        print("here", args, kwargs)
        self.args = args
        self.kwargs = kwargs

    def __repr__(self):
        return f"{self.__dict__}"


class Ice(YamlObject2, Base):
    yaml_tag = "![*ice]"


class Fire(YamlObject2, Base):
    yaml_tag = "![*fire]"


file = """
dict:
  - &ice ~
  - &fire ~
data:
  - ![*ice] {k: 1, v: 2}
  - ![*fire] [1, 2]
"""

data = yaml.safe_load(file)

print(data)
```

When parsing Ice (mapping, to dict), everything works well. However YamlAble doesn't have a handler to handle sequence yet, it will cause
`
E:\Python36\python.exe C:/Users/Administrator/AppData/Roaming/JetBrains/PyCharm2020.1/scratches/test_scratch.py
here () {'k': 1, 'v': 2}

Traceback (most recent call last):
  File "C:/Users/Administrator/AppData/Roaming/JetBrains/PyCharm2020.1/scratches/test_scratch.py", line 31, in <module>
    data = yaml.safe_load(file)
  File "E:\Python36\lib\site-packages\yaml\__init__.py", line 162, in safe_load
    return load(stream, SafeLoader)
  File "E:\Python36\lib\site-packages\yaml\__init__.py", line 114, in load
    return loader.get_single_data()
  File "E:\Python36\lib\site-packages\yaml\constructor.py", line 51, in get_single_data
    return self.construct_document(node)
  File "E:\Python36\lib\site-packages\yaml\constructor.py", line 60, in construct_document
    for dummy in generator:
  File "E:\Python36\lib\site-packages\yaml\constructor.py", line 408, in construct_yaml_seq
    data.extend(self.construct_sequence(node))
  File "E:\Python36\lib\site-packages\yaml\constructor.py", line 130, in construct_sequence
    for child in node.value]
  File "E:\Python36\lib\site-packages\yaml\constructor.py", line 130, in <listcomp>
    for child in node.value]
  File "E:\Python36\lib\site-packages\yaml\constructor.py", line 100, in construct_object
    data = constructor(self, node)
  File "E:\Python36\lib\site-packages\yamlable\yaml_objects.py", line 115, in from_yaml
    constructor_args = read_yaml_node_as_dict(loader, node)
  File "E:\Python36\lib\site-packages\yamlable\base.py", line 210, in read_yaml_node_as_dict
    loader.flatten_mapping(node)
  File "E:\Python36\lib\site-packages\yaml\constructor.py", line 184, in flatten_mapping
    key_node, value_node = node.value[index]
TypeError: 'ScalarNode' object is not iterable
`

Maybe a type router to call constructor in different ways is better?

Discussion:

Patch:
diff --git a/ci_tools/.gitignore b/ci_tools/.gitignore
deleted file mode 100644
index 667b860..0000000
--- a/ci_tools/.gitignore
+++ /dev/null
@@ -1,5 +0,0 @@
-# private key
-github_travis_rsa*
-
-# coverage results
-.coverage
\ No newline at end of file
diff --git a/ci_tools/flake8-requirements.txt b/ci_tools/flake8-requirements.txt
index 8608576..c681193 100644
--- a/ci_tools/flake8-requirements.txt
+++ b/ci_tools/flake8-requirements.txt
@@ -11,4 +11,5 @@ pydocstyle>=5.1.1,<6
 pycodestyle>=2.6.0,<3
 mccabe>=0.6.1,<1
 naming>=0.5.1,<1
-pyflakes>=2.2,<3
\ No newline at end of file
+pyflakes>=2.2,<3
+genbadge[flake8]
diff --git a/docs/changelog.md b/docs/changelog.md
index 2fed733..d490885 100644
--- a/docs/changelog.md
+++ b/docs/changelog.md
@@ -1,5 +1,9 @@
 # Changelog
 
+### 1.1.0 - Now load objects from sequences and scalars too
+
+ - Objects (subclasses of `YamlAble` or `YamlObject2`) can now be loaded from both mappings, sequences and scalars. Codecs (subclasses of `YamlCodec`) can also support this feature. Fixes [#12](https://github.com/smarie/python-yamlable/issues/12)
+
 ### 1.0.4 - better type hinting (mypy)
 
  - Most type hints have been fixed, in particular for `@yaml_info`. Fixes [#11](https://github.com/smarie/python-yamlable/issues/11).
diff --git a/docs/index.md b/docs/index.md
index 9d9fc0c..47631c2 100644
--- a/docs/index.md
+++ b/docs/index.md
@@ -26,6 +26,10 @@ In addition `yamlable` provides a way to create Yaml codecs for several object t
 
 ## Usage
 
+### 1. The (recommended) `YamlAble` way
+
+#### a. Creating the class
+
 Let's make a class yaml-able: we have to
 
  - inherit from `YamlAble`
@@ -38,7 +42,7 @@ from yamlable import yaml_info, YamlAble
 @yaml_info(yaml_tag_ns='com.yamlable.example')
 class Foo(YamlAble):
 
-    def __init__(self, a, b):
+    def __init__(self, a, b="hey"):
         """ Constructor """
         self.a = a
         self.b = b
@@ -67,6 +71,8 @@ That's it! Let's check that our class is correct and allows us to create instanc
 Foo - {'a': 1, 'b': 'hello'}
 ```
 
+#### b. Dumping and loading to/from YAML
+
 Now let's dump and load it using `pyyaml`:
 
 ```python
@@ -105,7 +111,44 @@ a: 1
 b: hello
 ```
 
-See [Usage](./usage) for other possibilities of `yamlable`.
+#### c. Support for sequences and scalars
+
+Objects can also be loaded from YAML sequences:
+
+```python
+>>> print(yaml.safe_load("""
+!yamlable/com.yamlable.example.Foo
+- 0
+- hey
+"""))
+
+Foo - {'a': 0, 'b': 'hey'}
+```
+
+The default implementation of `__from_yaml_sequence__` (that you may wish to override in your subclass), is to call
+the constructor with the sequence contents as positional arguments.
+
+The same also works for scalars:
+
+```python
+>>> print(yaml.safe_load("""
+!yamlable/com.yamlable.example.Foo 0
+"""))
+
+Foo - {'a': "0", 'b': 'hey'}
+```
+
+The default implementation of `__from_yaml_scalar__` (that you may wish to override in your subclass), is to call
+the constructor with the scalar as first positional argument.
+
+!!! warning "Scalars are not resolved"
+    As can be seen in the above example, scalars are not auto-resolved when constructing an object from a scalar. So an
+    integer `0` is actually received as a string `"0"` by `from_yaml_scalar`.
+
+
+#### d. What if you can not modify the class ?
+
+See [Usage](./usage#yamlcodec) for another possibility offered by `yamlable`: creating a codec to handle YAML for several classes at once, typically classes that you cannot modify.
 
 
 ## Main features / benefits
diff --git a/docs/usage.md b/docs/usage.md
index 60cc27d..1b835e7 100644
--- a/docs/usage.md
+++ b/docs/usage.md
@@ -4,6 +4,8 @@ You have seen in the [main page](./index) a small example to understand the conc
 
 ## `YamlCodec`
 
+### 1. Writing a codec class
+
 Sometimes you do not have the possibility to change the classes of the objects that you wish to encode/decode. In this case the solution is to write an independent codec, inheriting from `YamlCodec`. Once again this feature leverages the `multi_constructor` and `multi_representer` concepts available in the `PyYaml` internals, but with `YamlCodec` it becomes a bit easier to do.
 
 Let's assume that the following two classes are given and can not be modified:
@@ -84,6 +86,8 @@ class MyCodec(YamlCodec):
         return types_to_yaml_tags[type(obj)], vars(obj)
 ```
 
+### 2. Registering a codec
+
 When you codec has been defined, it needs to be registerd before being usable. You can specify with which `PyYaml` Loaders/Dumpers it should be registered, or use the default (all): 
 
 ```python
@@ -91,6 +95,8 @@ When you codec has been defined, it needs to be registerd before being usable. Y
 MyCodec.register_with_pyyaml()
 ```
 
+### 3. Using a codec
+
 Finally let's test that the codec works:
 
 ```python
@@ -111,3 +117,10 @@ assert dump(b) == by
 assert f == load(fy)
 assert b == load(by)
 ```
+
+### 4. Sequences and scalars
+
+Objects can be loaded from sequences and scalars, in addition to dictionaries. To support this possibility, you simply need to fill the class methods:
+
+ - `from_yaml_sequence` for sequences
+ - `from_yaml_scalar` for scalars
diff --git a/docs/mkdocs.yml b/mkdocs.yml
similarity index 84%
rename from docs/mkdocs.yml
rename to mkdocs.yml
index 1e19cf5..fd9e721 100644
--- a/docs/mkdocs.yml
+++ b/mkdocs.yml
@@ -1,8 +1,10 @@
 site_name: yamlable
 # site_description: 'A short description of my project'
 repo_url: https://github.com/smarie/python-yamlable
-docs_dir: .
-site_dir: ../site
+# docs_dir: .
+# site_dir: ../site
+# default branch is main instead of master now on github
+edit_uri : ./edit/main/docs
 nav:
     - Home: index.md
     - Usage details: usage.md
diff --git a/noxfile.py b/noxfile.py
index 1825717..db49ab9 100644
--- a/noxfile.py
+++ b/noxfile.py
@@ -1,6 +1,5 @@
 from itertools import product
 from json import dumps
-
 import logging
 
 import nox  # noqa
@@ -140,10 +139,10 @@ def flake8(session: PowerSession):
     """Launch flake8 qualimetry."""
 
     session.install("-r", str(Folders.ci_tools / "flake8-requirements.txt"))
-    session.install("genbadge[flake8]")
-    session.run2("pip install -e .[flake8]")
+    session.run2("pip install .")
 
     rm_folder(Folders.flake8_reports)
+    Folders.flake8_reports.mkdir(parents=True, exist_ok=True)
     rm_file(Folders.flake8_intermediate_file)
 
     # Options are set in `setup.cfg` file
diff --git a/yamlable/base.py b/yamlable/base.py
index 0553879..e99078e 100644
--- a/yamlable/base.py
+++ b/yamlable/base.py
@@ -6,6 +6,8 @@
 from abc import ABCMeta
 from collections import OrderedDict
 
+from yaml import ScalarNode, SequenceNode, MappingNode
+
 try:
     # Python 2 only:
     from StringIO import StringIO as _StringIO  # type: ignore  # noqa
@@ -27,7 +29,7 @@ from warnings import warn
 import six
 
 try:  # python 3.5+
-    from typing import Union, TypeVar, Dict, Any
+    from typing import Union, TypeVar, Dict, Any, Sequence
 
     Y = TypeVar('Y', bound='AbstractYamlObject')
 
@@ -49,6 +51,30 @@ class AbstractYamlObject(six.with_metaclass(ABCMeta, object)):
     Default implementation uses vars(self) and cls(**dct), but subclasses can override.
     """
 
+    # def __to_yaml_scalar__(self):
+    #     # type: (...) -> Any
+    #     """
+    #     Implementors should transform the object into a scalar containing all information necessary to decode the
+    #     object as a YAML scalar in the future.
+    #
+    #     Default implementation raises an error.
+    #     :return:
+    #     """
+    #     raise NotImplementedError("Please override `__to_yaml_scalar__` if you wish to dump instances of `%s`"
+    #                               " as yaml scalars." % type(self).__name__)
+    #
+    # def __to_yaml_sequence__(self):
+    #     # type: (...) -> Sequence[Any]
+    #     """
+    #     Implementors should transform the object into a Sequence containing all information necessary to decode the
+    #     object as a YAML sequence in the future.
+    #
+    #     Default implementation raises an error.
+    #     :return:
+    #     """
+    #     raise NotImplementedError("Please override `__to_yaml_sequence__` if you wish to dump instances of `%s`"
+    #                               " as yaml sequences." % type(self).__name__)
+
     def __to_yaml_dict__(self):
         # type: (...) -> Dict[str, Any]
         """
@@ -67,6 +93,52 @@ class AbstractYamlObject(six.with_metaclass(ABCMeta, object)):
         # Default: return vars(self) (Note: no need to make a copy, pyyaml does not modify it)
         return vars(self)
 
+    @classmethod
+    def __from_yaml_scalar__(cls,      # type: Type[Y]
+                             scalar,   # type: Any
+                             yaml_tag  # type: str
+                             ):
+        # type: (...) -> Y
+        """
+        Implementors should transform the given scalar (read from yaml by the pyYaml stack) into an object instance.
+        The yaml tag associated to this object, read in the yaml document, is provided in parameter.
+
+        Note that for YamlAble and YamlObject2 subclasses, if this method is called the yaml tag will already have
+        been checked so implementors do not have to validate it.
+
+        Default implementation returns cls(scalar)
+
+        :param scalar: the yaml scalar
+        :param yaml_tag: the yaml schema id that was used for encoding the object (it has already been checked
+            against is_json_schema_id_supported)
+        :return:
+        """
+        # Default: call constructor with positional arguments
+        return cls(scalar)  # type: ignore
+
+    @classmethod
+    def __from_yaml_sequence__(cls,      # type: Type[Y]
+                               seq,      # type: Sequence[Any]
+                               yaml_tag  # type: str
+                               ):
+        # type: (...) -> Y
+        """
+        Implementors should transform the given Sequence (read from yaml by the pyYaml stack) into an object instance.
+        The yaml tag associated to this object, read in the yaml document, is provided in parameter.
+
+        Note that for YamlAble and YamlObject2 subclasses, if this method is called the yaml tag will already have
+        been checked so implementors do not have to validate it.
+
+        Default implementation returns cls(*seq)
+
+        :param seq: the yaml sequence
+        :param yaml_tag: the yaml schema id that was used for encoding the object (it has already been checked
+            against is_json_schema_id_supported)
+        :return:
+        """
+        # Default: call constructor with positional arguments
+        return cls(*seq)  # type: ignore
+
     @classmethod
     def __from_yaml_dict__(cls,      # type: Type[Y]
                            dct,      # type: Dict[str, Any]
@@ -200,6 +272,7 @@ NONE_IGNORE_CHECKS = None
 
 
 def read_yaml_node_as_dict(loader, node):
+    # type: (...) -> OrderedDict
     """
     Utility method to read a yaml node into a dictionary
 
@@ -207,7 +280,64 @@ def read_yaml_node_as_dict(loader, node):
     :param node:
     :return:
     """
-    loader.flatten_mapping(node)
-    pairs = loader.construct_pairs(node, deep=True)  # 'deep' allows the construction to be complete (inner seq...)
+    # loader.flatten_mapping(node)
+    # pairs = loader.construct_pairs(node, deep=True)  # 'deep' allows the construction to be complete (inner seq...)
+    pairs = loader.construct_mapping(node, deep=True)  # 'deep' allows the construction to be complete (inner seq...)
     constructor_args = OrderedDict(pairs)
     return constructor_args
+
+
+def read_yaml_node_as_sequence(loader, node):
+    # type: (...) -> Sequence
+    """
+    Utility method to read a yaml node into a sequence
+
+    :param loader:
+    :param node:
+    :return:
+    """
+    seq = loader.construct_sequence(node, deep=True)  # 'deep' allows the construction to be complete (inner seq...)
+    return seq
+
+
+def read_yaml_node_as_scalar(loader, node):
+    # type: (...) -> Any
+    """
+    Utility method to read a yaml node into a sequence
+
+    :param loader:
+    :param node:
+    :return:
+    """
+    value = loader.construct_scalar(node)
+    return value
+
+
+def read_yaml_node_as_yamlobject(
+    cls,      # type: Type[AbstractYamlObject]
+    loader,
+    node,     # type: MappingNode
+    yaml_tag  # type: str
+):
+    # type: (...) -> AbstractYamlObject
+    """
+    Default implementation: loads the node as a dictionary and calls __from_yaml_dict__ with this dictionary
+
+    :param loader:
+    :param node:
+    :return:
+    """
+    if isinstance(node, ScalarNode):
+        constructor_args = read_yaml_node_as_scalar(loader, node)
+        return cls.__from_yaml_scalar__(constructor_args, yaml_tag=yaml_tag)  # type: ignore
+
+    elif isinstance(node, SequenceNode):
+        constructor_args = read_yaml_node_as_sequence(loader, node)
+        return cls.__from_yaml_sequence__(constructor_args, yaml_tag=yaml_tag)  # type: ignore
+
+    elif isinstance(node, MappingNode):
+        constructor_args = read_yaml_node_as_dict(loader, node)
+        return cls.__from_yaml_dict__(constructor_args, yaml_tag=yaml_tag)  # type: ignore
+
+    else:
+        raise TypeError("Unknown type of yaml node: %r. Please report this to `yamlable` project." % type(node))
diff --git a/yamlable/main.py b/yamlable/main.py
index b8f8dc6..4092d6c 100644
--- a/yamlable/main.py
+++ b/yamlable/main.py
@@ -9,7 +9,8 @@ from abc import abstractmethod, ABCMeta
 import six
 
 try:  # python 3.5+
-    from typing import TypeVar, Callable, Iterable, Any, Tuple, Dict, Set, List
+    from typing import TypeVar, Callable, Iterable, Any, Tuple, Dict, Set, List, Sequence
+
     YA = TypeVar('YA', bound='YamlAble')
     T = TypeVar('T')
 except ImportError:
@@ -20,9 +21,10 @@ try:  # python 3.5.4+
 except ImportError:
     pass  # normal for old versions of typing
 
-from yaml import Loader, SafeLoader, Dumper, SafeDumper, MappingNode
+from yaml import Loader, SafeLoader, Dumper, SafeDumper, MappingNode, ScalarNode, SequenceNode
 
-from yamlable.base import AbstractYamlObject, read_yaml_node_as_dict
+from yamlable.base import AbstractYamlObject, read_yaml_node_as_yamlobject, read_yaml_node_as_dict, \
+    read_yaml_node_as_sequence, read_yaml_node_as_scalar
 from yamlable.yaml_objects import YamlObject2
 
 
@@ -231,12 +233,15 @@ def decode_yamlable(loader,
     for clazz in candidates:
         try:
             if clazz.is_yaml_tag_supported(yaml_tag):
-                constructor_args = read_yaml_node_as_dict(loader, node)
-                return clazz.__from_yaml_dict__(constructor_args, yaml_tag=yaml_tag)
+                return read_yaml_node_as_yamlobject(
+                    cls=clazz, loader=loader, node=node, yaml_tag=yaml_tag
+                )  # type: ignore
+            else:
+                errors[clazz.__name__] = "yaml tag %r is not supported." % yaml_tag
         except Exception as e:
             errors[clazz.__name__] = e
 
-    raise TypeError("No YamlAble subclass found able to decode object !yamlable/" + yaml_tag + ". Tried classes: "
+    raise TypeError("No YamlAble subclass found able to decode object '!yamlable/" + yaml_tag + "'. Tried classes: "
                     + str(candidates) + ". Caught errors: " + str(errors) + ". "
                     "Please check the value of <cls>.__yaml_tag_suffix__ on these classes. Note that this value may be "
                     "set using @yaml_info() so help(yaml_info) might help too.")
@@ -342,7 +347,7 @@ def _get_all_subclasses(typ,             # type: Type[T]
         try:
             if to is not typ and to not in result and issubclass(to, typ):  # is_subtype(to, typ, bound_typevars={}):
                 result.append(to)
-        except Exception:
+        except Exception:  # noqa
             # catching an error with is_subtype(Dict, Dict[str, int], bound_typevars={})
             pass
 
@@ -403,8 +408,22 @@ class YamlCodec(six.with_metaclass(ABCMeta, object)):
         :return:
         """
         if cls.is_yaml_tag_supported(yaml_tag_suffix):
-            constructor_args = read_yaml_node_as_dict(loader, node)
-            return cls.from_yaml_dict(yaml_tag_suffix, constructor_args, **kwargs)
+            # Note: same as in read_yaml_node_as_yamlobject but different yaml tag handling so code copy
+
+            if isinstance(node, ScalarNode):
+                constructor_args = read_yaml_node_as_scalar(loader, node)
+                return cls.from_yaml_scalar(yaml_tag_suffix, constructor_args, **kwargs)  # type: ignore
+
+            elif isinstance(node, SequenceNode):
+                constructor_args = read_yaml_node_as_sequence(loader, node)
+                return cls.from_yaml_sequence(yaml_tag_suffix, constructor_args, **kwargs)  # type: ignore
+
+            elif isinstance(node, MappingNode):
+                constructor_args = read_yaml_node_as_dict(loader, node)
+                return cls.from_yaml_dict(yaml_tag_suffix, constructor_args, **kwargs)  # type: ignore
+
+            else:
+                raise TypeError("Unknown type of yaml node: %r. Please report this to `yamlable` project." % type(node))
 
     @classmethod
     @abstractmethod
@@ -419,6 +438,40 @@ class YamlCodec(six.with_metaclass(ABCMeta, object)):
         :return:
         """
 
+    @classmethod
+    def from_yaml_scalar(cls,
+                         yaml_tag_suffix,  # type: str
+                         scalar,           # type: Any
+                         **kwargs):
+        # type: (...) -> Any
+        """
+        Implementing classes should create an object corresponding to the given yaml tag, using the given YAML scalar.
+
+        :param scalar:
+        :param yaml_tag_suffix:
+        :param kwargs: keyword arguments coming from pyyaml, not sure what you will find here.
+        :return:
+        """
+        raise NotImplementedError("This codec does not support loading objects from scalar. Please override "
+                                  "`from_yaml_scalar` to support this feature.")
+
+    @classmethod
+    def from_yaml_sequence(cls,
+                           yaml_tag_suffix,  # type: str
+                           seq,              # type: Sequence[Any]
+                           **kwargs):
+        # type: (...) -> Any
+        """
+        Implementing classes should create an object corresponding to the given yaml tag, using the given YAML sequence.
+
+        :param seq:
+        :param yaml_tag_suffix:
+        :param kwargs: keyword arguments coming from pyyaml, not sure what you will find here.
+        :return:
+        """
+        raise NotImplementedError("This codec does not support loading objects from sequence. Please override "
+                                  "`from_yaml_sequence` to support this feature.")
+
     @classmethod
     @abstractmethod
     def from_yaml_dict(cls,
@@ -427,8 +480,7 @@ class YamlCodec(six.with_metaclass(ABCMeta, object)):
                        **kwargs):
         # type: (...) -> Any
         """
-        Implementing classes should create an object corresponding to the given yaml tag, using the given constructor
-        arguments.
+        Implementing classes should create an object corresponding to the given yaml tag, using the given YAML mapping.
 
         :param dct:
         :param yaml_tag_suffix:
diff --git a/yamlable/yaml_objects.py b/yamlable/yaml_objects.py
index 68f29b7..8853a26 100644
--- a/yamlable/yaml_objects.py
+++ b/yamlable/yaml_objects.py
@@ -20,7 +20,7 @@ except ImportError:
 
 from yaml import YAMLObjectMetaclass, YAMLObject, SafeLoader, MappingNode
 
-from yamlable.base import AbstractYamlObject, read_yaml_node_as_dict
+from yamlable.base import AbstractYamlObject, read_yaml_node_as_yamlobject
 
 
 class YAMLObjectMetaclassStrict(YAMLObjectMetaclass):
@@ -106,11 +106,10 @@ class YamlObject2(six.with_metaclass(ABCYAMLMeta, AbstractYamlObject, YAMLObject
                   ):
         # type: (...) -> YamlObject2
         """
-        Default implementation: loads the node as a dictionary and calls __from_yaml_dict__ with this dictionary
+        Default implementation: relies on AbstractYamlObject API to load the node as a dictionary/sequence/scalar
 
         :param loader:
         :param node:
         :return:
         """
-        constructor_args = read_yaml_node_as_dict(loader, node)
-        return cls.__from_yaml_dict__(constructor_args, yaml_tag=cls.yaml_tag)  # type: ignore
+        return read_yaml_node_as_yamlobject(cls=cls, loader=loader, node=node, yaml_tag=cls.yaml_tag)


Test patch:
diff --git a/yamlable/tests/test_yamlable.py b/yamlable/tests/test_yamlable.py
index 865a20f..d1ad1a1 100644
--- a/yamlable/tests/test_yamlable.py
+++ b/yamlable/tests/test_yamlable.py
@@ -58,10 +58,13 @@ def test_yamlable():
     class Foo(YamlAble):
         # __yaml_tag_suffix__ = 'foo'   not needed: we used @yaml_info
 
-        def __init__(self, a, b):
+        def __init__(self, a, b="hey"):
             self.a = a
             self.b = b
 
+        def __repr__(self):
+            return "Foo(a=%r,b=%r)" % (self.a, self.b)
+
         def __eq__(self, other):
             return vars(self) == vars(other)
 
@@ -111,6 +114,25 @@ b: hello
     # load pyyaml
     assert f == load(y)
 
+    # mapping, sequences and scalar
+    y_map = """
+!yamlable/yaml.tests.Foo
+a: 1
+"""
+    y_seq = """
+!yamlable/yaml.tests.Foo
+- 1
+"""
+    y_scalar = """
+!yamlable/yaml.tests.Foo
+1
+"""
+    assert Foo.loads_yaml(y_map) == Foo(a=1)
+    assert Foo.loads_yaml(y_seq) == Foo(a=1)
+
+    # Important: if we provide a scalar, there will not be any auto-resolver
+    assert Foo.loads_yaml(y_scalar) == Foo(a="1")
+
 
 def test_yamlable_legacy_method_names():
     """ Tests that YamlAbleMixIn works correctly """
diff --git a/yamlable/tests/test_yamlcodec.py b/yamlable/tests/test_yamlcodec.py
index 270b0ef..94a7401 100644
--- a/yamlable/tests/test_yamlcodec.py
+++ b/yamlable/tests/test_yamlcodec.py
@@ -1,3 +1,5 @@
+import pytest
+
 try:  # python 3.5+
     from typing import Tuple, Any, Iterable, Dict
 except ImportError:
@@ -86,3 +88,40 @@ c: what?
     # load pyyaml
     assert f == load(fy)
     assert b == load(by)
+
+    # load from sequence / scalar
+    by_seq = """!mycodec/yaml.tests.Bar
+- what?
+"""
+    by_scalar = "!mycodec/yaml.tests.Bar what?"
+
+    with pytest.raises(NotImplementedError):
+        load(by_seq)
+
+    with pytest.raises(NotImplementedError):
+        load(by_scalar)
+
+    class MyCodec2(MyCodec):
+        @classmethod
+        def from_yaml_sequence(cls,
+                               yaml_tag_suffix,  # type: str
+                               seq,              # type: Sequence[Any]
+                               **kwargs):
+            # type: (...) -> Any
+            typ = yaml_tags_to_types[yaml_tag_suffix]
+            return typ(*seq)
+
+        @classmethod
+        def from_yaml_scalar(cls,
+                             yaml_tag_suffix,  # type: str
+                             scalar,           # type: Any
+                             **kwargs):
+            # type: (...) -> Any
+            typ = yaml_tags_to_types[yaml_tag_suffix]
+            return typ(scalar)
+
+    # register the codec
+    MyCodec2.register_with_pyyaml()
+
+    assert b == load(by_seq)
+    assert b == load(by_scalar)
diff --git a/yamlable/tests/test_yamlobjects.py b/yamlable/tests/test_yamlobjects.py
index 8e2d784..1bc7744 100644
--- a/yamlable/tests/test_yamlobjects.py
+++ b/yamlable/tests/test_yamlobjects.py
@@ -106,7 +106,7 @@ def test_abstract_parent():
     # instantiate
     f = FooValid(1, 'hello')
 
-    # dump to yaml
+    # dump to yaml (use safe=False since this is a non-standard yaml type)
     o = f.dumps_yaml(safe=False, default_flow_style=False)
     assert o == """!foo
 a: 1
@@ -116,3 +116,44 @@ b: hello
     # load from yaml
     g = FooValid.loads_yaml(o)
     assert f == g
+
+
+def test_sequence():
+    class Hero(object):
+        def __init__(self, name, hp=100, sp=0):
+            self.name = name
+            self.hp = hp
+            self.sp = sp
+
+        def __eq__(self, other):
+            return (self.name == other.name) and (self.hp == other.hp) and (self.sp == other.sp)
+
+        def __repr__(self):
+            return "%s(name=%r, hp=%r, sp=%r)" % (self.__class__.__name__, self.name, self.hp, self.sp)
+
+    class HeroY(YamlObject2, Hero):
+        yaml_tag = "!hero"
+
+    # Mapping
+    h_map = HeroY.loads_yaml("""
+!hero
+ name: Welthyr Syxgon
+ sp: 0
+ hp: 1200
+""")  # note: we purposedly inverted the sp/hp order here
+
+    # Sequence
+    h_seq = HeroY.loads_yaml("""
+!hero
+ - Welthyr Syxgon
+ - 1200
+""")  # note: we purposedly do not include the last value > default
+
+    assert h_map == h_seq == HeroY("Welthyr Syxgon", 1200)
+
+    # Scalar
+    h_scalar = HeroY.loads_yaml("""
+!hero Welthyr Syxgon
+""")
+
+    assert h_scalar == HeroY("Welthyr Syxgon")