diff --git a/.travis.yml b/.travis.yml
index 429bf09..3fc825d 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -6,6 +6,7 @@ python:
 install:
   - "pip install -r requirements.txt"
   - "pip install python-coveralls pytest-cov"
+  - "pip install coverage --upgrade"
   - "python setup.py install"
 script: PYTHONPATH=. py.test tests/ --doctest-modules  -v --cov pyclarity_lims --cov-report term-missing
 after_success:
diff --git a/docs/PracticalExamples.rst b/docs/PracticalExamples.rst
index 3f0e5d8..4226dea 100644
--- a/docs/PracticalExamples.rst
+++ b/docs/PracticalExamples.rst
@@ -123,4 +123,35 @@ Here is an example with one sample placed into a tube.
         s.actions.put()
 
         # Complete the step
-        s.advance()
\ No newline at end of file
+        s.advance()
+
+
+Mix samples in a pool using the api
+-----------------------------------
+
+Some step will allow you to mix multiple input :py:class:`artifacts <pyclarity_lims.entities.Artifact>` into a pool also
+represented by an :py:class:`artifact <pyclarity_lims.entities.Artifact>`. This can be performed using the
+:py:class:`StepPools <pyclarity_lims.entities.StepPools>` entities.
+
+Because the pool :py:class:`artifact <pyclarity_lims.entities.Artifact>` needs to be created in the LIMS, we only
+need to provide the pool name and we need to provide `None` in place of the pool
+
+.. code::
+
+        # Assuming a Step in the pooling stage
+        s = Step(l, id='122-12345')
+        # This provides a list of all the artifacts available to pool
+        s.pools.available_inputs
+        # The pooled_inputs is a dict where the key is the name of the pool
+        # the value is a Tuple with first element is the pool artifact and the second if the pooled input
+        # here we're not specifying the pool and will let the LIMS create it.
+        s.pools.pooled_inputs['Pool1'] = (None, tuple(s.pools.available_inputs))
+        # then upload
+        s.pools.put()
+        # There no more input artifacts available
+        assert s.pools.available_inputs == []
+        # There is a pool artifact created
+        assert type(s.pools.pooled_inputs['Pool1'][0]).__name__ == 'Artifact'
+
+        # Now we can advance the step
+        s.advance()
diff --git a/pyclarity_lims/descriptors.py b/pyclarity_lims/descriptors.py
index 19243cd..5f0ae65 100644
--- a/pyclarity_lims/descriptors.py
+++ b/pyclarity_lims/descriptors.py
@@ -399,7 +399,8 @@ class XmlPooledInputDict(XmlDictionary, Nestable):
         self._delitem(key)
         node = ElementTree.SubElement(self.rootnode(self.instance), 'pool')
         node.attrib['name'] = key
-        node.attrib['uri'] = pool.uri
+        if pool and pool.uri:
+            node.attrib['output-uri'] = pool.uri
         for inart in list_input:
             sub = ElementTree.Element('input')
             sub.attrib['uri'] = inart.uri
@@ -413,11 +414,15 @@ class XmlPooledInputDict(XmlDictionary, Nestable):
 
     def _parse_element(self, element, **kwargs):
         from pyclarity_lims.entities import Artifact
+        if 'output-uri' in element.attrib:
+            pool = Artifact(self.instance.lims, uri=element.attrib.get('output-uri'))
+        else:
+            pool = None
         dict.__setitem__(
             self,
             element.attrib.get('name'),
             (
-                Artifact(self.instance.lims, uri=element.attrib.get('output-uri')),
+                pool,
                 tuple(Artifact(self.instance.lims, uri=sub.attrib.get('uri')) for sub in element.findall('input'))
             )
         )
diff --git a/pyclarity_lims/entities.py b/pyclarity_lims/entities.py
index 9d22d8d..30017cc 100644
--- a/pyclarity_lims/entities.py
+++ b/pyclarity_lims/entities.py
@@ -796,6 +796,15 @@ class StepPools(Entity):
         * an output Artifact containing the pool.
         * a tuple containing the input artifacts for that pool.
     """
+    available_inputs = EntityListDescriptor(tag='input', klass=Artifact, nesting=['available-inputs'])
+    """List of artifact available for pooling. 
+    
+    Note that adding artifacts to a pool will not remove them from this list until put() is run.
+    """
+
+    def put(self):
+        self.root = super(StepPools, self).put()
+        return self.root
 
 
 class Step(Entity):

Pooling samples
Hi all, (Edited from my original post, earlier today, because I was using the wrong data structure for StepPools.pooled_inputs)
Not an issue for you, rather for me! I'm trying to pool 2 plates into 2 tubes. First I create the two pools, set up the step and add the Illumina indexes to the tubes, I think I'm all set here, so you can probably ignore this:

```
    pool1 = Container.create(lims, type=tube_ct)
    pool2 = Container.create(lims, type=tube_ct)
    pools = [pool1, pool2]

    step = Step.create(lims, protocol_step=protocol_step, inputs=artifacts,
                       container_type_name=tube_ct.name)

    process = Process(lims, id=step.actions.step.id)

    # Assign the correct Illumina UDI indexes
<snip>
...
        sample.artifact.reagent_labels = [label]
        sample.artifact.put()
```
The issue comes when I try and pool the plates. I create a data structure and try and assign this to step.pools.pooled_inputs, thus:

```
    output_pools = {}
    for idx, plate in enumerate(plates):
        input_arts = []
        plate.get(force=True)
        out_container = pools[idx]
        pool_name = out_container.name
        placement_dict = plate.get_placements()
        for placekey in placement_dict.keys():
            art = placement_dict[placekey]
            print 'Artifact:', art
            input_arts.append(art)
        art_tuple = tuple(input_arts)  # tuble of input arts to add to a tube
        output_pools[pool_name] = tuple((out_container, art_tuple))
        out_container.get(force=True)
        for art in input_arts:
            art.put()
        plate.put()

    step.pools.pooled_inputs = output_pools

    step.advance()

```

The error I get is:

```
    step.advance()
  File "/usr/local/lib/python2.7/dist-packages/pyclarity_lims/entities.py", line 843, in advance
    data=self.lims.tostring(ElementTree.ElementTree(self.root))
  File "/usr/local/lib/python2.7/dist-packages/pyclarity_lims/lims.py", line 172, in post
    return self.parse_response(r, accept_status_codes=[200, 201, 202])
  File "/usr/local/lib/python2.7/dist-packages/pyclarity_lims/lims.py", line 214, in parse_response
    self.validate_response(response, accept_status_codes)
  File "/usr/local/lib/python2.7/dist-packages/pyclarity_lims/lims.py", line 207, in validate_response
    raise requests.exceptions.HTTPError(message)
requests.exceptions.HTTPError: 400: Unpooled inputs exist

```
I *think* I've got the data structure correct for StepPools.pooled_inputs. I'm thinking it must be the caching of the artifacts. Can anyone see where I've gone wrong?

Thanks!


diff --git a/tests/test_descriptors.py b/tests/test_descriptors.py
index c4972d9..cf2dfee 100644
--- a/tests/test_descriptors.py
+++ b/tests/test_descriptors.py
@@ -513,13 +513,24 @@ class TestXmlPooledInputDict(TestCase):
     def test___getitem__(self):
         assert self.dict1['pool1'] == (self.out1, (self.in1, self.in2))
 
-    def test___setitem__(self):
+    def test___setitem1__(self):
         assert len(self.dict1) == 2
         assert len(self.dict1.rootnode(self.dict1.instance)) == 2
+        # This works in the test but does not work in reality because
+        # the pool artifact needs to be creaated by the LIMS.
         self.dict1['pool3'] = (self.out1, (self.in1, self.in2))
         assert len(self.dict1) == 3
         assert len(self.dict1.rootnode(self.dict1.instance)) == 3
 
+    def test___setitem2__(self):
+        assert len(self.dict1) == 2
+        assert len(self.dict1.rootnode(self.dict1.instance)) == 2
+
+        # This is the correct way of creating a pool from scratch
+        self.dict1['pool3'] = (None, (self.in1, self.in2))
+        assert len(self.dict1) == 3
+        assert len(self.dict1.rootnode(self.dict1.instance)) == 3
+
 
 class TestEntityList(TestDescriptor):
     def setUp(self):

pip install -e .
parse_log_pytest
requirements.txt
pytest
apt-get update apt-get install -y gcc
3.6
requirements.txt
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
attrs==22.2.0
certifi==2021.5.30
charset-normalizer==2.0.12
idna==3.10
importlib-metadata==4.8.3
iniconfig==1.1.1
packaging==21.3
pluggy==1.0.0
py==1.11.0
-e git+https://github.com/EdinburghGenomics/pyclarity-lims.git@1a9f7d14e5597f7607ab85171432c7791d523d08#egg=pyclarity_lims
pyparsing==3.1.4
pytest==7.0.1
requests==2.27.1
tomli==1.2.3
typing_extensions==4.1.1
urllib3==1.26.20
zipp==3.6.0

name: pyclarity-lims
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - certifi=2021.5.30=py36h06a4308_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.3=he6710b0_2
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=1.1.1w=h7f8727e_0
  - pip=21.2.2=py36h06a4308_0
  - python=3.6.13=h12debd9_1
  - readline=8.2=h5eee18b_0
  - setuptools=58.0.4=py36h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - wheel=0.37.1=pyhd3eb1b0_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - attrs==22.2.0
      - charset-normalizer==2.0.12
      - idna==3.10
      - importlib-metadata==4.8.3
      - iniconfig==1.1.1
      - packaging==21.3
      - pluggy==1.0.0
      - py==1.11.0
      - pyparsing==3.1.4
      - pytest==7.0.1
      - requests==2.27.1
      - tomli==1.2.3
      - typing-extensions==4.1.1
      - urllib3==1.26.20
      - zipp==3.6.0
prefix: /opt/conda/envs/pyclarity-lims

tests/test_descriptors.py::TestXmlPooledInputDict::test___setitem2__
tests/test_descriptors.py::TestStringDescriptor::test__get__ tests/test_descriptors.py::TestStringDescriptor::test__set__ tests/test_descriptors.py::TestStringDescriptor::test_create tests/test_descriptors.py::TestIntegerDescriptor::test__get__ tests/test_descriptors.py::TestIntegerDescriptor::test__set__ tests/test_descriptors.py::TestIntegerDescriptor::test_create tests/test_descriptors.py::TestBooleanDescriptor::test__get__ tests/test_descriptors.py::TestBooleanDescriptor::test__set__ tests/test_descriptors.py::TestBooleanDescriptor::test_create tests/test_descriptors.py::TestEntityDescriptor::test__get__ tests/test_descriptors.py::TestEntityDescriptor::test__set__ tests/test_descriptors.py::TestEntityDescriptor::test_create tests/test_descriptors.py::TestEntityListDescriptor::test__get__ tests/test_descriptors.py::TestStringAttributeDescriptor::test__get__ tests/test_descriptors.py::TestStringAttributeDescriptor::test__set__ tests/test_descriptors.py::TestStringAttributeDescriptor::test_create tests/test_descriptors.py::TestStringListDescriptor::test__get__ tests/test_descriptors.py::TestStringListDescriptor::test__set__ tests/test_descriptors.py::TestStringDictionaryDescriptor::test__get__ tests/test_descriptors.py::TestStringDictionaryDescriptor::test__set__ tests/test_descriptors.py::TestUdfDictionary::test___delitem__ tests/test_descriptors.py::TestUdfDictionary::test___getitem__ tests/test_descriptors.py::TestUdfDictionary::test___iter__ tests/test_descriptors.py::TestUdfDictionary::test___setitem__ tests/test_descriptors.py::TestUdfDictionary::test___setitem__new tests/test_descriptors.py::TestUdfDictionary::test___setitem__unicode tests/test_descriptors.py::TestUdfDictionary::test_clear tests/test_descriptors.py::TestUdfDictionary::test_create tests/test_descriptors.py::TestUdfDictionary::test_create_with_nesting tests/test_descriptors.py::TestUdfDictionary::test_items tests/test_descriptors.py::TestPlacementDictionary::test___delitem__ tests/test_descriptors.py::TestPlacementDictionary::test___getitem__ tests/test_descriptors.py::TestPlacementDictionary::test___setitem__ tests/test_descriptors.py::TestPlacementDictionary::test___setitem__2 tests/test_descriptors.py::TestPlacementDictionary::test_clear tests/test_descriptors.py::TestSubTagDictionary::test___getitem__ tests/test_descriptors.py::TestSubTagDictionary::test___setitem__ tests/test_descriptors.py::TestSubTagDictionary::test___setitem__from_empty tests/test_descriptors.py::TestXmlElementAttributeDict::test___getitem__ tests/test_descriptors.py::TestXmlElementAttributeDict::test___setitem__ tests/test_descriptors.py::TestXmlElementAttributeDict::test__len__ tests/test_descriptors.py::TestXmlPooledInputDict::test___getitem__ tests/test_descriptors.py::TestXmlPooledInputDict::test___setitem1__ tests/test_descriptors.py::TestEntityList::test___add__ tests/test_descriptors.py::TestEntityList::test__get__ tests/test_descriptors.py::TestEntityList::test__iadd__ tests/test_descriptors.py::TestEntityList::test_append tests/test_descriptors.py::TestEntityList::test_clear tests/test_descriptors.py::TestEntityList::test_insert tests/test_descriptors.py::TestEntityList::test_set tests/test_descriptors.py::TestEntityList::test_set_list tests/test_descriptors.py::TestInputOutputMapList::test___get__ tests/test_descriptors.py::TestExternalidList::test_append tests/test_descriptors.py::TestExternalidList::test_get tests/test_descriptors.py::TestXmlAttributeList::test_append tests/test_descriptors.py::TestXmlAttributeList::test_get tests/test_descriptors.py::TestXmlAttributeList::test_insert tests/test_descriptors.py::TestXmlReagentLabelList::test_append tests/test_descriptors.py::TestXmlReagentLabelList::test_get tests/test_descriptors.py::TestXmlAction::test_parse tests/test_descriptors.py::TestXmlAction::test_set tests/test_descriptors.py::TestQueuedArtifactList::test_parse tests/test_descriptors.py::TestQueuedArtifactList::test_set
.travis.yml pyclarity_lims/descriptors.py pyclarity_lims/entities.py docs/PracticalExamples.rst
.travis.yml pyclarity_lims/descriptors.py pyclarity_lims/entities.py docs/PracticalExamples.rst