diff --git a/README.rst b/README.rst
index 6770ec6..79b8e7e 100644
--- a/README.rst
+++ b/README.rst
@@ -170,6 +170,9 @@ repositories/maintainers-guide#additionalMaintainers>`__.
 
 **Current Maintainers of this TC Open Repository**
 
+-  `Greg Back <mailto:gback@mitre.org>`__; GitHub ID:
+   https://github.com/gtback/; WWW: `MITRE
+   Corporation <http://www.mitre.org/>`__
 -  `Chris Lenk <mailto:clenk@mitre.org>`__; GitHub ID:
    https://github.com/clenk/; WWW: `MITRE
    Corporation <http://www.mitre.org/>`__
diff --git a/docs/guide/creating.ipynb b/docs/guide/creating.ipynb
index 61bbe15..058aae3 100644
--- a/docs/guide/creating.ipynb
+++ b/docs/guide/creating.ipynb
@@ -881,7 +881,7 @@
    "name": "python",
    "nbconvert_exporter": "python",
    "pygments_lexer": "ipython3",
-   "version": "3.6.3"
+   "version": "3.6.5"
   }
  },
  "nbformat": 4,
diff --git a/docs/guide/patterns.ipynb b/docs/guide/patterns.ipynb
new file mode 100644
index 0000000..ee06675
--- /dev/null
+++ b/docs/guide/patterns.ipynb
@@ -0,0 +1,509 @@
+{
+ "cells": [
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "# STIX2 Patterns\n",
+    "\n",
+    "The Python ``stix2`` library supports STIX 2 patterning insofar that patterns may be used for the pattern property of Indicators, identical to the STIX 2 specification. ``stix2`` does not evaluate patterns against STIX 2 content; for that functionality see [cti-pattern-matcher](https://github.com/oasis-open/cti-pattern-matcher).\n",
+    "\n",
+    "Patterns in the ``stix2`` library are built compositely from the bottom up, creating subcomponent expressions first before those at higher levels.\n",
+    "\n",
+    "## API Tips\n",
+    "\n",
+    "### ObservationExpression\n",
+    "\n",
+    "Within the STIX 2 Patterning specification, Observation Expressions denote a complete expression to be evaluated against a discrete observation.  In other words, an Observation Expression must be created to apply to a single Observation instance. This is further made clear by the visual brackets(```[]```) that encapsulate an Observation Expression. Thus, whatever sub expressions that are within the Observation Expression are meant to be matched against the same Observable instance.\n",
+    "\n",
+    "This requirement manifests itself within the ``stix2`` library via ```ObservationExpression```. When creating STIX 2 observation expressions, whenever the current expression is complete, wrap it with ```ObservationExpression()```. This allows the complete pattern expression - no matter its complexity - to be rendered as a proper specification-adhering string. __*Note: When pattern expressions are added to Indicator objects, the expression objects are implicitly converted to string representations*__. While the extra step may seem tedious in the construction of simple pattern expressions, this explicit marking of observation expressions becomes vital when converting the pattern expressions to strings. \n",
+    "\n",
+    "In all the examples, you can observe how in the process of building pattern expressions, when an Observation Expression is completed, it is wrapped with ```ObservationExpression()```.\n",
+    "\n",
+    "### ParentheticalExpression\n",
+    "\n",
+    "Do not be confused by the ```ParentheticalExpression``` object. It is not a distinct expression type but is also used to properly craft pattern expressions by denoting order priority and grouping of expression components. Use it in a similar manner as ```ObservationExpression```, wrapping completed subcomponent expressions with ```ParentheticalExpression()``` if explicit ordering is required. For usage examples with ```ParentheticalExpression```'s, see [here](#Compound-Observation-Expressions).\n",
+    "\n",
+    "### BooleanExpressions vs CompoundObservationExpressions\n",
+    "\n",
+    "Be careful to note the difference between these two very similar pattern components. \n",
+    "\n",
+    "__BooleanExpressions__\n",
+    "\n",
+    " - [AndBooleanExpression](../api/stix2.patterns.rst#stix2.patterns.AndBooleanExpression)\n",
+    " - [OrbooleanExpression](../api/stix2.patterns.rst#stix2.patterns.OrBooleanExpression)\n",
+    " \n",
+    "  __Usage__: When the boolean sub-expressions refer to the *same* root object \n",
+    "\n",
+    "  __Example__:\n",
+    "    ```[domain-name:value = \"www.5z8.info\" AND domain-name:resolvess_to_refs[*].value = \"'198.51.100.1/32'\"]```\n",
+    "    \n",
+    "  __Rendering__: when pattern is rendered, brackets or parenthesis will encapsulate boolean expression\n",
+    " \n",
+    "__CompoundObservationExpressions__\n",
+    "\n",
+    " - [AndObservationExpression](../api/stix2.patterns.rst#stix2.patterns.AndObservationExpression)\n",
+    " - [OrObservationExpression](../api/stix2.patterns.rst#stix2.patterns.OrObservationExpression)\n",
+    " \n",
+    "  __Usage__: When the boolean sub-expressions refer to *different* root objects\n",
+    "\n",
+    "  __Example__:\n",
+    "    ```[file:name=\"foo.dll\"] AND [process:name = \"procfoo\"]```\n",
+    "    \n",
+    "  __Rendering__: when pattern is rendered, brackets will encapsulate each boolean sub-expression\n",
+    "\n",
+    "\n",
+    "\n",
+    "## Examples\n",
+    "\n",
+    "### Comparison Expressions"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 3,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "from stix2 import DomainName, File, IPv4Address\n",
+    "from stix2 import (ObjectPath, EqualityComparisonExpression, ObservationExpression,\n",
+    "                   GreaterThanComparisonExpression, IsSubsetComparisonExpression,\n",
+    "                   FloatConstant, StringConstant)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "#### Equality Comparison expressions"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 7,
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "\t[domain-name:value = 'site.of.interest.zaz']\n",
+      "\n",
+      "\t[file:parent_directory_ref.path = 'C:\\\\Windows\\\\System32']\n",
+      "\n"
+     ]
+    }
+   ],
+   "source": [
+    "lhs = ObjectPath(\"domain-name\", [\"value\"])\n",
+    "ece_1 = ObservationExpression(EqualityComparisonExpression(lhs, \"site.of.interest.zaz\"))\n",
+    "print(\"\\t{}\\n\".format(ece_1))\n",
+    "\n",
+    "lhs = ObjectPath(\"file\", [\"parent_directory_ref\",\"path\"])\n",
+    "ece_2 = ObservationExpression(EqualityComparisonExpression(lhs, \"C:\\\\Windows\\\\System32\"))\n",
+    "print(\"\\t{}\\n\".format(ece_2))"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "#### Greater-than Comparison expressions"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 5,
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "\t[file:extensions.windows-pebinary-ext.sections[*].entropy > 7.0]\n",
+      "\n"
+     ]
+    }
+   ],
+   "source": [
+    "lhs = ObjectPath(\"file\", [\"extensions\", \"windows-pebinary-ext\", \"sections[*]\", \"entropy\"])\n",
+    "gte = ObservationExpression(GreaterThanComparisonExpression(lhs, FloatConstant(\"7.0\")))\n",
+    "print(\"\\t{}\\n\".format(gte))"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "#### IsSubset Comparison expressions"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 6,
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "\t[network-traffic:dst_ref.value ISSUBSET '2001:0db8:dead:beef:0000:0000:0000:0000/64']\n",
+      "\n"
+     ]
+    }
+   ],
+   "source": [
+    "lhs = ObjectPath(\"network-traffic\", [\"dst_ref\", \"value\"])\n",
+    "iss = ObservationExpression(IsSubsetComparisonExpression(lhs, StringConstant(\"2001:0db8:dead:beef:0000:0000:0000:0000/64\")))\n",
+    "print(\"\\t{}\\n\".format(iss))"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "### Compound Observation Expressions"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 1,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "from stix2 import (IntegerConstant, HashConstant, ObjectPath,\n",
+    "                   EqualityComparisonExpression, AndBooleanExpression,\n",
+    "                   OrBooleanExpression, ParentheticalExpression,\n",
+    "                   AndObservationExpression, OrObservationExpression,\n",
+    "                   FollowedByObservationExpression, ObservationExpression)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "#### AND boolean"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 3,
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "(AND)\n",
+      "[email-message:sender_ref.value = 'stark@example.com' AND email-message:subject = 'Conference Info']\n",
+      "\n"
+     ]
+    }
+   ],
+   "source": [
+    "ece3 = EqualityComparisonExpression(ObjectPath(\"email-message\", [\"sender_ref\", \"value\"]), \"stark@example.com\")\n",
+    "ece4 = EqualityComparisonExpression(ObjectPath(\"email-message\", [\"subject\"]), \"Conference Info\")\n",
+    "abe = ObservationExpression(AndBooleanExpression([ece3, ece4]))\n",
+    "print(\"(AND)\\n{}\\n\".format(abe))"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "#### OR boolean"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 4,
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "(OR)\n",
+      "[url:value = 'http://example.com/foo' OR url:value = 'http://example.com/bar']\n",
+      "\n"
+     ]
+    }
+   ],
+   "source": [
+    "ece5 = EqualityComparisonExpression(ObjectPath(\"url\", [\"value\"]), \"http://example.com/foo\")\n",
+    "ece6 = EqualityComparisonExpression(ObjectPath(\"url\", [\"value\"]), \"http://example.com/bar\")\n",
+    "obe = ObservationExpression(OrBooleanExpression([ece5, ece6]))\n",
+    "print(\"(OR)\\n{}\\n\".format(obe))"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "#### ( OR ) AND   boolean"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 5,
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "(OR,AND)\n",
+      "[(file:name = 'pdf.exe' OR file:size = 371712) AND file:created = 2014-01-13 07:03:17+00:00]\n",
+      "\n"
+     ]
+    }
+   ],
+   "source": [
+    "ece7 = EqualityComparisonExpression(ObjectPath(\"file\", [\"name\"]), \"pdf.exe\")\n",
+    "ece8 = EqualityComparisonExpression(ObjectPath(\"file\", [\"size\"]), IntegerConstant(\"371712\"))\n",
+    "ece9 = EqualityComparisonExpression(ObjectPath(\"file\", [\"created\"]), \"2014-01-13T07:03:17Z\")\n",
+    "obe1 = OrBooleanExpression([ece7, ece8])\n",
+    "pobe = ParentheticalExpression(obe1)\n",
+    "abe1 = ObservationExpression(AndBooleanExpression([pobe, ece9]))\n",
+    "print(\"(OR,AND)\\n{}\\n\".format(abe1))"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "#### ( AND ) OR ( OR )  observation"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 6,
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "(AND,OR,OR)\n",
+      "([file:name = 'foo.dll'] AND [win-registry-key:key = 'HKEY_LOCAL_MACHINE\\\\foo\\\\bar']) OR [process:name = 'fooproc' OR process:name = 'procfoo']\n",
+      "\n"
+     ]
+    }
+   ],
+   "source": [
+    "ece20 = ObservationExpression(EqualityComparisonExpression(ObjectPath(\"file\", [\"name\"]), \"foo.dll\"))\n",
+    "ece21 = ObservationExpression(EqualityComparisonExpression(ObjectPath(\"win-registry-key\", [\"key\"]), \"HKEY_LOCAL_MACHINE\\\\foo\\\\bar\"))\n",
+    "ece22 = EqualityComparisonExpression(ObjectPath(\"process\", [\"name\"]), \"fooproc\")\n",
+    "ece23 = EqualityComparisonExpression(ObjectPath(\"process\", [\"name\"]), \"procfoo\")\n",
+    "# NOTE: we need to use AND/OR observation expression instead of just boolean \n",
+    "# expressions as the operands are not on the same object-type\n",
+    "aoe = ParentheticalExpression(AndObservationExpression([ece20, ece21]))\n",
+    "obe2 = ObservationExpression(OrBooleanExpression([ece22, ece23]))\n",
+    "ooe = OrObservationExpression([aoe, obe2])\n",
+    "print(\"(AND,OR,OR)\\n{}\\n\".format(ooe))"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "#### FOLLOWED-BY"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 7,
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "(FollowedBy)\n",
+      "[file:hashes.MD5 = '79054025255fb1a26e4bc422aef54eb4'] FOLLOWEDBY [win-registry-key:key = 'HKEY_LOCAL_MACHINE\\\\foo\\\\bar']\n",
+      "\n"
+     ]
+    }
+   ],
+   "source": [
+    "ece10 = ObservationExpression(EqualityComparisonExpression(ObjectPath(\"file\", [\"hashes\", \"MD5\"]), HashConstant(\"79054025255fb1a26e4bc422aef54eb4\", \"MD5\")))\n",
+    "ece11 = ObservationExpression(EqualityComparisonExpression(ObjectPath(\"win-registry-key\", [\"key\"]), \"HKEY_LOCAL_MACHINE\\\\foo\\\\bar\"))\n",
+    "fbe = FollowedByObservationExpression([ece10, ece11])\n",
+    "print(\"(FollowedBy)\\n{}\\n\".format(fbe))"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "### Qualified Observation Expressions"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 8,
+   "metadata": {},
+   "outputs": [],
+   "source": [
+    "from stix2 import (TimestampConstant, HashConstant, ObjectPath, EqualityComparisonExpression,\n",
+    "                   AndBooleanExpression, WithinQualifier, RepeatQualifier, StartStopQualifier,\n",
+    "                   QualifiedObservationExpression, FollowedByObservationExpression,\n",
+    "                   ParentheticalExpression, ObservationExpression)"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "#### WITHIN"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 9,
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "(WITHIN)\n",
+      "([file:hashes.MD5 = '79054025255fb1a26e4bc422aef54eb4'] FOLLOWEDBY [win-registry-key:key = 'HKEY_LOCAL_MACHINE\\\\foo\\\\bar']) WITHIN 300 SECONDS\n",
+      "\n"
+     ]
+    }
+   ],
+   "source": [
+    "ece10 = ObservationExpression(EqualityComparisonExpression(ObjectPath(\"file\", [\"hashes\", \"MD5\"]), HashConstant(\"79054025255fb1a26e4bc422aef54eb4\", \"MD5\")))\n",
+    "ece11 = ObservationExpression(EqualityComparisonExpression(ObjectPath(\"win-registry-key\", [\"key\"]), \"HKEY_LOCAL_MACHINE\\\\foo\\\\bar\"))\n",
+    "fbe = FollowedByObservationExpression([ece10, ece11])\n",
+    "par = ParentheticalExpression(fbe)\n",
+    "qoe = QualifiedObservationExpression(par, WithinQualifier(300))\n",
+    "print(\"(WITHIN)\\n{}\\n\".format(qoe))"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "#### REPEATS, WITHIN"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 10,
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "(REPEAT, WITHIN)\n",
+      "[network-traffic:dst_ref.type = 'domain-name' AND network-traffic:dst_ref.value = 'example.com'] REPEATS 5 TIMES WITHIN 180 SECONDS\n",
+      "\n"
+     ]
+    }
+   ],
+   "source": [
+    "ece12 = EqualityComparisonExpression(ObjectPath(\"network-traffic\", [\"dst_ref\", \"type\"]), \"domain-name\")\n",
+    "ece13 = EqualityComparisonExpression(ObjectPath(\"network-traffic\", [\"dst_ref\", \"value\"]), \"example.com\")\n",
+    "abe2 = ObservationExpression(AndBooleanExpression([ece12, ece13]))\n",
+    "qoe1 = QualifiedObservationExpression(QualifiedObservationExpression(abe2, RepeatQualifier(5)), WithinQualifier(180))\n",
+    "print(\"(REPEAT, WITHIN)\\n{}\\n\".format(qoe1))"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "#### START, STOP"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 11,
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "(START-STOP)\n",
+      "[file:name = 'foo.dll'] START t'2016-06-01T00:00:00Z' STOP t'2016-07-01T00:00:00Z'\n",
+      "\n"
+     ]
+    }
+   ],
+   "source": [
+    "ece14 = ObservationExpression(EqualityComparisonExpression(ObjectPath(\"file\", [\"name\"]), \"foo.dll\"))\n",
+    "ssq = StartStopQualifier(TimestampConstant('2016-06-01T00:00:00Z'), TimestampConstant('2016-07-01T00:00:00Z'))\n",
+    "qoe2 = QualifiedObservationExpression(ece14, ssq)\n",
+    "print(\"(START-STOP)\\n{}\\n\".format(qoe2))"
+   ]
+  },
+  {
+   "cell_type": "markdown",
+   "metadata": {},
+   "source": [
+    "## Attaching patterns to STIX2 Domain objects\n",
+    "\n",
+    "\n",
+    "### Example"
+   ]
+  },
+  {
+   "cell_type": "code",
+   "execution_count": 10,
+   "metadata": {},
+   "outputs": [
+    {
+     "name": "stdout",
+     "output_type": "stream",
+     "text": [
+      "{\n",
+      "    \"type\": \"indicator\",\n",
+      "    \"id\": \"indicator--219bc5fc-fdbf-4b54-a2fc-921be7ab3acb\",\n",
+      "    \"created\": \"2018-08-29T23:58:00.548Z\",\n",
+      "    \"modified\": \"2018-08-29T23:58:00.548Z\",\n",
+      "    \"name\": \"Cryptotorch\",\n",
+      "    \"pattern\": \"[file:name = '$$t00rzch$$.elf']\",\n",
+      "    \"valid_from\": \"2018-08-29T23:58:00.548391Z\",\n",
+      "    \"labels\": [\n",
+      "        \"malware\",\n",
+      "        \"ransomware\"\n",
+      "    ]\n",
+      "}\n"
+     ]
+    }
+   ],
+   "source": [
+    "from stix2 import Indicator, EqualityComparisonExpression, ObservationExpression\n",
+    "\n",
+    "ece14 = ObservationExpression(EqualityComparisonExpression(ObjectPath(\"file\", [\"name\"]), \"$$t00rzch$$.elf\"))\n",
+    "ind = Indicator(name=\"Cryptotorch\", labels=[\"malware\", \"ransomware\"], pattern=ece14)\n",
+    "print(ind)"
+   ]
+  }
+ ],
+ "metadata": {
+  "kernelspec": {
+   "display_name": "Python 3",
+   "language": "python",
+   "name": "python3"
+  },
+  "language_info": {
+   "codemirror_mode": {
+    "name": "ipython",
+    "version": 3
+   },
+   "file_extension": ".py",
+   "mimetype": "text/x-python",
+   "name": "python",
+   "nbconvert_exporter": "python",
+   "pygments_lexer": "ipython3",
+   "version": "3.6.3"
+  }
+ },
+ "nbformat": 4,
+ "nbformat_minor": 2
+}
diff --git a/stix2/patterns.py b/stix2/patterns.py
index 3f9cbd9..146ec04 100644
--- a/stix2/patterns.py
+++ b/stix2/patterns.py
@@ -18,6 +18,11 @@ class _Constant(object):
 
 
 class StringConstant(_Constant):
+    """Pattern string constant
+
+    Args:
+        value (str): string value
+    """
     def __init__(self, value):
         self.value = value
 
@@ -26,17 +31,27 @@ class StringConstant(_Constant):
 
 
 class TimestampConstant(_Constant):
+    """Pattern timestamp constant
+
+    Args:
+        value (datetime.datetime OR str): if string, must be a timestamp string
+    """
     def __init__(self, value):
         try:
             self.value = parse_into_datetime(value)
         except Exception:
-            raise ValueError("must be a datetime object or timestamp string.")
+            raise ValueError("Must be a datetime object or timestamp string.")
 
     def __str__(self):
         return "t%s" % repr(self.value)
 
 
 class IntegerConstant(_Constant):
+    """Pattern interger constant
+
+    Args:
+        value (int): integer value
+    """
     def __init__(self, value):
         try:
             self.value = int(value)
@@ -59,6 +74,13 @@ class FloatConstant(_Constant):
 
 
 class BooleanConstant(_Constant):
+    """Pattern boolean constant
+
+    Args:
+       value (str OR int):
+           (str) 'true', 't' for True; 'false', 'f' for False
+           (int) 1 for True; 0 for False
+    """
     def __init__(self, value):
         if isinstance(value, bool):
             self.value = value
@@ -106,6 +128,15 @@ _HASH_REGEX = {
 
 
 class HashConstant(StringConstant):
+    """Pattern hash constant
+
+    Args:
+        value (str): hash value
+        type (str): hash algorithm name. Supported hash algorithms:
+            "MD5", "MD6", RIPEMD160", "SHA1", "SHA224", "SHA256",
+            "SHA384", "SHA512", "SHA3224", "SHA3256", "SHA3384",
+            "SHA3512", "SSDEEP", "WHIRLPOOL"
+    """
     def __init__(self, value, type):
         key = type.upper().replace('-', '')
         if key in _HASH_REGEX:
@@ -116,7 +147,11 @@ class HashConstant(StringConstant):
 
 
 class BinaryConstant(_Constant):
+    """Pattern binary constant
 
+    Args:
+        value (str): base64 encoded string value
+    """
     def __init__(self, value):
         try:
             base64.b64decode(value)
@@ -129,6 +164,11 @@ class BinaryConstant(_Constant):
 
 
 class HexConstant(_Constant):
+    """Pattern hexadecimal constant
+
+    Args:
+        value (str): hexadecimal value
+    """
     def __init__(self, value):
         if not re.match('^([a-fA-F0-9]{2})+$', value):
             raise ValueError("must contain an even number of hexadecimal characters")
@@ -139,6 +179,11 @@ class HexConstant(_Constant):
 
 
 class ListConstant(_Constant):
+    """Pattern list constant
+
+    Args:
+        value (list): list of values
+    """
     def __init__(self, values):
         self.value = values
 
@@ -147,6 +192,12 @@ class ListConstant(_Constant):
 
 
 def make_constant(value):
+    """Convert value to Pattern constant, best effort attempt
+    at determining root value type and corresponding conversion
+
+    Args:
+        value: value to convert to Pattern constant
+    """
     if isinstance(value, _Constant):
         return value
 
@@ -182,6 +233,16 @@ class _ObjectPathComponent(object):
 
 
 class BasicObjectPathComponent(_ObjectPathComponent):
+    """Basic object path component (for an observation or expression)
+
+    By "Basic", implies that the object path component is not a
+    list, object reference or futher referenced property, i.e. terminal
+    component
+
+    Args:
+        property_name (str): object property name
+        is_key (bool): is dictionary key, default: False
+    """
     def __init__(self, property_name, is_key=False):
         self.property_name = property_name
         # TODO: set is_key to True if this component is a dictionary key
@@ -192,6 +253,12 @@ class BasicObjectPathComponent(_ObjectPathComponent):
 
 
 class ListObjectPathComponent(_ObjectPathComponent):
+    """List object path component (for an observation or expression)
+
+    Args:
+        property_name (str): list object property name
+        index (int): index of the list property's value that is specified
+    """
     def __init__(self, property_name, index):
         self.property_name = property_name
         self.index = index
@@ -201,6 +268,11 @@ class ListObjectPathComponent(_ObjectPathComponent):
 
 
 class ReferenceObjectPathComponent(_ObjectPathComponent):
+    """Reference object path component (for an observation or expression)
+
+    Args:
+        reference_property_name (str): reference object property name
+    """
     def __init__(self, reference_property_name):
         self.property_name = reference_property_name
 
@@ -209,6 +281,12 @@ class ReferenceObjectPathComponent(_ObjectPathComponent):
 
 
 class ObjectPath(object):
+    """Pattern operand object (property) path
+
+    Args:
+        object_type_name (str): name of object type for corresponding object path component
+        property_path (_ObjectPathComponent OR str): object path
+    """
     def __init__(self, object_type_name, property_path):
         self.object_type_name = object_type_name
         self.property_path = [x if isinstance(x, _ObjectPathComponent) else
@@ -219,11 +297,17 @@ class ObjectPath(object):
         return "%s:%s" % (self.object_type_name, ".".join(["%s" % x for x in self.property_path]))
 
     def merge(self, other):
+        """Extend the object property with that of the supplied object property path"""
         self.property_path.extend(other.property_path)
         return self
 
     @staticmethod
     def make_object_path(lhs):
+        """Create ObjectPath from string encoded object path
+
+        Args:
+            lhs (str): object path of left-hand-side component of expression
+        """
         path_as_parts = lhs.split(":")
         return ObjectPath(path_as_parts[0], path_as_parts[1].split("."))
 
@@ -233,6 +317,14 @@ class _PatternExpression(object):
 
 
 class _ComparisonExpression(_PatternExpression):
+    """Pattern Comparison Expression
+
+    Args:
+        operator (str): operator of comparison expression
+        lhs (ObjectPath OR str): object path of left-hand-side component of expression
+        rhs (ObjectPath OR str): object path of right-hand-side component of expression
+        negated (bool): comparison expression negated. Default: False
+    """
     def __init__(self, operator, lhs, rhs, negated=False):
         if operator == "=" and isinstance(rhs, (ListConstant, list)):
             self.operator = "IN"
@@ -257,56 +349,134 @@ class _ComparisonExpression(_PatternExpression):
 
 
 class EqualityComparisonExpression(_ComparisonExpression):
+    """Pattern Equality Comparison Expression
+
+    Args:
+        lhs (ObjectPath OR str): object path of left-hand-side component of expression
+        rhs (ObjectPath OR str): object path of right-hand-side component of expression
+        negated (bool): comparison expression negated. Default: False
+    """
     def __init__(self, lhs, rhs, negated=False):
         super(EqualityComparisonExpression, self).__init__("=", lhs, rhs, negated)
 
 
 class GreaterThanComparisonExpression(_ComparisonExpression):
+    """Pattern Greater-than Comparison Expression
+
+    Args:
+        lhs (ObjectPath OR str): object path of left-hand-side component of expression
+        rhs (ObjectPath OR str): object path of right-hand-side component of expression
+        negated (bool): comparison expression negated. Default: False
+    """
     def __init__(self, lhs, rhs, negated=False):
         super(GreaterThanComparisonExpression, self).__init__(">", lhs, rhs, negated)
 
 
 class LessThanComparisonExpression(_ComparisonExpression):
+    """Pattern Less-than Comparison Expression
+
+    Args:
+        lhs (ObjectPath OR str): object path of left-hand-side component of expression
+        rhs (ObjectPath OR str): object path of right-hand-side component of expression
+        negated (bool): comparison expression negated. Default: False
+    """
     def __init__(self, lhs, rhs, negated=False):
         super(LessThanComparisonExpression, self).__init__("<", lhs, rhs, negated)
 
 
 class GreaterThanEqualComparisonExpression(_ComparisonExpression):
+    """Pattern Greater-Than-or-Equal-to Comparison Expression
+
+    Args:
+        lhs (ObjectPath OR str): object path of left-hand-side component of expression
+        rhs (ObjectPath OR str): object path of right-hand-side component of expression
+        negated (bool): comparison expression negated. Default: False
+    """
     def __init__(self, lhs, rhs, negated=False):
         super(GreaterThanEqualComparisonExpression, self).__init__(">=", lhs, rhs, negated)
 
 
 class LessThanEqualComparisonExpression(_ComparisonExpression):
+    """Pattern Less-Than-or-Equal-to Comparison Expression
+
+    Args:
+        lhs (ObjectPath OR str): object path of left-hand-side component of expression
+        rhs (ObjectPath OR str): object path of right-hand-side component of expression
+        negated (bool): comparison expression negated. Default: False
+    """
+
     def __init__(self, lhs, rhs, negated=False):
         super(LessThanEqualComparisonExpression, self).__init__("<=", lhs, rhs, negated)
 
 
 class InComparisonExpression(_ComparisonExpression):
+    """'in' Comparison Expression
+
+    Args:
+        lhs (ObjectPath OR str): object path of left-hand-side component of expression
+        rhs (ObjectPath OR str): object path of right-hand-side component of expression
+        negated (bool): comparison expression negated. Default: False
+    """
     def __init__(self, lhs, rhs, negated=False):
         super(InComparisonExpression, self).__init__("IN", lhs, rhs, negated)
 
 
 class LikeComparisonExpression(_ComparisonExpression):
+    """'like' Comparison Expression
+
+    Args:
+        lhs (ObjectPath OR str): object path of left-hand-side component of expression
+        rhs (ObjectPath OR str): object path of right-hand-side component of expression
+        negated (bool): comparison expression negated. Default: False
+    """
+
     def __init__(self, lhs, rhs, negated=False):
         super(LikeComparisonExpression, self).__init__("LIKE", lhs, rhs, negated)
 
 
 class MatchesComparisonExpression(_ComparisonExpression):
+    """'Matches' Comparison Expression
+
+    Args:
+        lhs (ObjectPath OR str): object path of left-hand-side component of expression
+        rhs (ObjectPath OR str): object path of right-hand-side component of expression
+        negated (bool): comparison expression negated. Default: False
+    """
     def __init__(self, lhs, rhs, negated=False):
         super(MatchesComparisonExpression, self).__init__("MATCHES", lhs, rhs, negated)
 
 
 class IsSubsetComparisonExpression(_ComparisonExpression):
-        def __init__(self, lhs, rhs, negated=False):
-            super(IsSubsetComparisonExpression, self).__init__("ISSUBSET", lhs, rhs, negated)
+    """ 'is subset' Comparison Expression
+
+    Args:
+        lhs (ObjectPath OR str): object path of left-hand-side component of expression
+        rhs (ObjectPath OR str): object path of right-hand-side component of expression
+        negated (bool): comparison expression negated. Default: False
+    """
+    def __init__(self, lhs, rhs, negated=False):
+        super(IsSubsetComparisonExpression, self).__init__("ISSUBSET", lhs, rhs, negated)
 
 
 class IsSupersetComparisonExpression(_ComparisonExpression):
-        def __init__(self, lhs, rhs, negated=False):
-            super(IsSupersetComparisonExpression, self).__init__("ISSUPERSET", lhs, rhs, negated)
+    """ 'is super set' Comparison Expression
+
+    Args:
+        lhs (ObjectPath OR str): object path of left-hand-side component of expression
+        rhs (ObjectPath OR str): object path of right-hand-side component of expression
+        negated (bool): comparison expression negated. Default: False
+    """
+    def __init__(self, lhs, rhs, negated=False):
+        super(IsSupersetComparisonExpression, self).__init__("ISSUPERSET", lhs, rhs, negated)
 
 
 class _BooleanExpression(_PatternExpression):
+    """Boolean Pattern Expression
+
+    Args:
+        operator (str): boolean operator
+        operands (list): boolean operands
+    """
     def __init__(self, operator, operands):
         self.operator = operator
         self.operands = []
@@ -322,21 +492,37 @@ class _BooleanExpression(_PatternExpression):
     def __str__(self):
         sub_exprs = []
         for o in self.operands:
-            sub_exprs.append("%s" % o)
+            sub_exprs.append(str(o))
         return (" " + self.operator + " ").join(sub_exprs)
 
 
 class AndBooleanExpression(_BooleanExpression):
+    """'AND' Boolean Pattern Expression. Only use if both operands are of
+    the same root object.
+
+    Args:
+        operands (list): AND operands
+    """
     def __init__(self, operands):
         super(AndBooleanExpression, self).__init__("AND", operands)
 
 
 class OrBooleanExpression(_BooleanExpression):
+    """'OR' Boolean Pattern Expression. Only use if both operands are of the same root object
+
+    Args:
+        operands (list): OR operands
+    """
     def __init__(self, operands):
         super(OrBooleanExpression, self).__init__("OR", operands)
 
 
 class ObservationExpression(_PatternExpression):
+    """Observation Expression
+
+    Args:
+        operand (str): observation expression operand
+    """
     def __init__(self, operand):
         self.operand = operand
 
@@ -345,6 +531,12 @@ class ObservationExpression(_PatternExpression):
 
 
 class _CompoundObservationExpression(_PatternExpression):
+    """Compound Observation Expression
+
+    Args:
+        operator (str): compound observation operator
+        operands (str): compound observation operands
+    """
     def __init__(self, operator, operands):
         self.operator = operator
         self.operands = operands
@@ -357,21 +549,41 @@ class _CompoundObservationExpression(_PatternExpression):
 
 
 class AndObservationExpression(_CompoundObservationExpression):
+    """'AND' Compound Observation Pattern Expression
+
+    Args:
+        operands (str): compound observation operands
+    """
     def __init__(self, operands):
         super(AndObservationExpression, self).__init__("AND", operands)
 
 
 class OrObservationExpression(_CompoundObservationExpression):
+    """Pattern 'OR' Compound Observation Expression
+
+    Args:
+        operands (str): compound observation operands
+    """
     def __init__(self, operands):
         super(OrObservationExpression, self).__init__("OR", operands)
 
 
 class FollowedByObservationExpression(_CompoundObservationExpression):
+    """Pattern 'Followed by' Compound Observation Expression
+
+    Args:
+        operands (str): compound observation operands
+    """
     def __init__(self, operands):
         super(FollowedByObservationExpression, self).__init__("FOLLOWEDBY", operands)
 
 
 class ParentheticalExpression(_PatternExpression):
+    """Pattern Parenthetical Observation Expression
+
+    Args:
+       exp (str): observation expression
+    """
     def __init__(self, exp):
         self.expression = exp
         if hasattr(exp, "root_type"):
@@ -386,6 +598,11 @@ class _ExpressionQualifier(_PatternExpression):
 
 
 class RepeatQualifier(_ExpressionQualifier):
+    """Pattern Repeat Qualifier
+
+    Args:
+        times_to_repeat (int): times the qualifiers is repeated
+    """
     def __init__(self, times_to_repeat):
         if isinstance(times_to_repeat, IntegerConstant):
             self.times_to_repeat = times_to_repeat
@@ -399,6 +616,11 @@ class RepeatQualifier(_ExpressionQualifier):
 
 
 class WithinQualifier(_ExpressionQualifier):
+    """Pattern 'Within' Qualifier
+
+    Args:
+        number_of_seconds (int): seconds value for 'within' qualifier
+    """
     def __init__(self, number_of_seconds):
         if isinstance(number_of_seconds, IntegerConstant):
             self.number_of_seconds = number_of_seconds
@@ -412,6 +634,12 @@ class WithinQualifier(_ExpressionQualifier):
 
 
 class StartStopQualifier(_ExpressionQualifier):
+    """Pattern Start/Stop Qualifier
+
+    Args:
+        start_time (TimestampConstant OR datetime.date): start timestamp for qualifier
+        stop_time (TimestampConstant OR datetime.date): stop timestamp for qualifier
+    """
     def __init__(self, start_time, stop_time):
         if isinstance(start_time, TimestampConstant):
             self.start_time = start_time
@@ -431,6 +659,12 @@ class StartStopQualifier(_ExpressionQualifier):
 
 
 class QualifiedObservationExpression(_PatternExpression):
+    """Pattern Qualified Observation Expression
+
+    Args:
+        observation_expression (PatternExpression OR _CompoundObservationExpression OR ): pattern expression
+        qualifier (_ExpressionQualifier): pattern expression qualifier
+    """
     def __init__(self, observation_expression, qualifier):
         self.observation_expression = observation_expression
         self.qualifier = qualifier

Add documentation for the `patterns` module
https://stix2.readthedocs.io/en/latest/api/stix2.patterns.html is pretty barebones, and we don't have anything in the User's Guide about it.
diff --git a/stix2/test/test_pattern_expressions.py b/stix2/test/test_pattern_expressions.py
index 14e3774..a4d0a5c 100644
--- a/stix2/test/test_pattern_expressions.py
+++ b/stix2/test/test_pattern_expressions.py
@@ -9,6 +9,7 @@ def test_create_comparison_expression():
 
     exp = stix2.EqualityComparisonExpression("file:hashes.'SHA-256'",
                                              stix2.HashConstant("aec070645fe53ee3b3763059376134f058cc337247c978add178b6ccdfb0019f", "SHA-256"))   # noqa
+
     assert str(exp) == "file:hashes.'SHA-256' = 'aec070645fe53ee3b3763059376134f058cc337247c978add178b6ccdfb0019f'"
 
 
@@ -18,6 +19,7 @@ def test_boolean_expression():
     exp2 = stix2.MatchesComparisonExpression("email-message:body_multipart[*].body_raw_ref.name",
                                              stix2.StringConstant("^Final Report.+\\.exe$"))
     exp = stix2.AndBooleanExpression([exp1, exp2])
+
     assert str(exp) == "email-message:from_ref.value MATCHES '.+\\\\@example\\\\.com$' AND email-message:body_multipart[*].body_raw_ref.name MATCHES '^Final Report.+\\\\.exe$'"  # noqa
 
 
@@ -66,9 +68,8 @@ def test_file_observable_expression():
                                                   "aec070645fe53ee3b3763059376134f058cc337247c978add178b6ccdfb0019f",
                                                   'SHA-256'))
     exp2 = stix2.EqualityComparisonExpression("file:mime_type", stix2.StringConstant("application/x-pdf"))
-    bool_exp = stix2.AndBooleanExpression([exp1, exp2])
-    exp = stix2.ObservationExpression(bool_exp)
-    assert str(exp) == "[file:hashes.'SHA-256' = 'aec070645fe53ee3b3763059376134f058cc337247c978add178b6ccdfb0019f' AND file:mime_type = 'application/x-pdf']"  # noqa
+    bool_exp = stix2.ObservationExpression(stix2.AndBooleanExpression([exp1, exp2]))
+    assert str(bool_exp) == "[file:hashes.'SHA-256' = 'aec070645fe53ee3b3763059376134f058cc337247c978add178b6ccdfb0019f' AND file:mime_type = 'application/x-pdf']"  # noqa
 
 
 @pytest.mark.parametrize("observation_class, op", [
@@ -109,14 +110,12 @@ def test_artifact_payload():
                                               "application/vnd.tcpdump.pcap")
     exp2 = stix2.MatchesComparisonExpression("artifact:payload_bin",
                                              stix2.StringConstant("\\xd4\\xc3\\xb2\\xa1\\x02\\x00\\x04\\x00"))
-    and_exp = stix2.AndBooleanExpression([exp1, exp2])
-    exp = stix2.ObservationExpression(and_exp)
-    assert str(exp) == "[artifact:mime_type = 'application/vnd.tcpdump.pcap' AND artifact:payload_bin MATCHES '\\\\xd4\\\\xc3\\\\xb2\\\\xa1\\\\x02\\\\x00\\\\x04\\\\x00']"  # noqa
+    and_exp = stix2.ObservationExpression(stix2.AndBooleanExpression([exp1, exp2]))
+    assert str(and_exp) == "[artifact:mime_type = 'application/vnd.tcpdump.pcap' AND artifact:payload_bin MATCHES '\\\\xd4\\\\xc3\\\\xb2\\\\xa1\\\\x02\\\\x00\\\\x04\\\\x00']"  # noqa
 
 
 def test_greater_than_python_constant():
-    exp1 = stix2.GreaterThanComparisonExpression("file:extensions.windows-pebinary-ext.sections[*].entropy",
-                                                 7.0)
+    exp1 = stix2.GreaterThanComparisonExpression("file:extensions.windows-pebinary-ext.sections[*].entropy", 7.0)
     exp = stix2.ObservationExpression(exp1)
     assert str(exp) == "[file:extensions.windows-pebinary-ext.sections[*].entropy > 7.0]"
 
@@ -129,14 +128,14 @@ def test_greater_than():
 
 
 def test_less_than():
-    exp = stix2.LessThanComparisonExpression("file:size",
-                                             1024)
+    exp = stix2.LessThanComparisonExpression("file:size", 1024)
     assert str(exp) == "file:size < 1024"
 
 
 def test_greater_than_or_equal():
     exp = stix2.GreaterThanEqualComparisonExpression("file:size",
                                                      1024)
+
     assert str(exp) == "file:size >= 1024"
 
 
@@ -261,7 +260,7 @@ def test_invalid_integer_constant():
 def test_invalid_timestamp_constant():
     with pytest.raises(ValueError) as excinfo:
         stix2.TimestampConstant('foo')
-    assert 'must be a datetime object or timestamp string' in str(excinfo)
+    assert 'Must be a datetime object or timestamp string' in str(excinfo)
 
 
 def test_invalid_float_constant():

pip install -e .[dev]
parse_log_pytest
requirements.txt
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
alabaster==0.7.13
antlr4-python3-runtime==4.9.3
async-generator==1.10
attrs==22.2.0
Babel==2.11.0
backcall==0.2.0
bleach==4.1.0
bump2version==1.0.1
bumpversion==0.6.0
certifi==2021.5.30
cfgv==3.3.1
charset-normalizer==2.0.12
coverage==6.2
decorator==5.1.1
defusedxml==0.7.1
distlib==0.3.9
docutils==0.18.1
entrypoints==0.4
filelock==3.4.1
identify==2.4.4
idna==3.10
imagesize==1.4.1
importlib-metadata==4.8.3
importlib-resources==5.2.3
iniconfig==1.1.1
ipython==7.16.3
ipython-genutils==0.2.0
jedi==0.17.2
Jinja2==3.0.3
jsonschema==3.2.0
jupyter-client==7.1.2
jupyter-core==4.9.2
jupyterlab-pygments==0.1.2
MarkupSafe==2.0.1
mistune==0.8.4
nbclient==0.5.9
nbconvert==6.0.7
nbformat==5.1.3
nbsphinx==0.3.2
nest-asyncio==1.6.0
nodeenv==1.6.0
packaging==21.3
pandocfilters==1.5.1
parso==0.7.1
pexpect==4.9.0
pickleshare==0.7.5
platformdirs==2.4.0
pluggy==1.0.0
pre-commit==2.17.0
prompt-toolkit==3.0.36
ptyprocess==0.7.0
py==1.11.0
Pygments==2.14.0
pyparsing==3.1.4
pyrsistent==0.18.0
pytest==7.0.1
pytest-cov==4.0.0
python-dateutil==2.9.0.post0
pytz==2025.2
PyYAML==6.0.1
pyzmq==25.1.2
requests==2.27.1
simplejson==3.20.1
six==1.17.0
snowballstemmer==2.2.0
Sphinx==1.5.6
sphinx-prompt==1.5.0
-e git+https://github.com/oasis-open/cti-python-stix2.git@b5a301ff28b6a73054741d5cd9a90a0036a7f6e5#egg=stix2
stix2-patterns==2.0.0
testpath==0.6.0
toml==0.10.2
tomli==1.2.3
tornado==6.1
tox==3.28.0
traitlets==4.3.3
typing_extensions==4.1.1
urllib3==1.26.20
virtualenv==20.16.2
wcwidth==0.2.13
webencodings==0.5.1
zipp==3.6.0

name: cti-python-stix2
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:
      - alabaster==0.7.13
      - antlr4-python3-runtime==4.9.3
      - async-generator==1.10
      - attrs==22.2.0
      - babel==2.11.0
      - backcall==0.2.0
      - bleach==4.1.0
      - bump2version==1.0.1
      - bumpversion==0.6.0
      - cfgv==3.3.1
      - charset-normalizer==2.0.12
      - coverage==6.2
      - decorator==5.1.1
      - defusedxml==0.7.1
      - distlib==0.3.9
      - docutils==0.18.1
      - entrypoints==0.4
      - filelock==3.4.1
      - identify==2.4.4
      - idna==3.10
      - imagesize==1.4.1
      - importlib-metadata==4.8.3
      - importlib-resources==5.2.3
      - iniconfig==1.1.1
      - ipython==7.16.3
      - ipython-genutils==0.2.0
      - jedi==0.17.2
      - jinja2==3.0.3
      - jsonschema==3.2.0
      - jupyter-client==7.1.2
      - jupyter-core==4.9.2
      - jupyterlab-pygments==0.1.2
      - markupsafe==2.0.1
      - mistune==0.8.4
      - nbclient==0.5.9
      - nbconvert==6.0.7
      - nbformat==5.1.3
      - nbsphinx==0.3.2
      - nest-asyncio==1.6.0
      - nodeenv==1.6.0
      - packaging==21.3
      - pandocfilters==1.5.1
      - parso==0.7.1
      - pexpect==4.9.0
      - pickleshare==0.7.5
      - platformdirs==2.4.0
      - pluggy==1.0.0
      - pre-commit==2.17.0
      - prompt-toolkit==3.0.36
      - ptyprocess==0.7.0
      - py==1.11.0
      - pygments==2.14.0
      - pyparsing==3.1.4
      - pyrsistent==0.18.0
      - pytest==7.0.1
      - pytest-cov==4.0.0
      - python-dateutil==2.9.0.post0
      - pytz==2025.2
      - pyyaml==6.0.1
      - pyzmq==25.1.2
      - requests==2.27.1
      - simplejson==3.20.1
      - six==1.17.0
      - snowballstemmer==2.2.0
      - sphinx==1.5.6
      - sphinx-prompt==1.5.0
      - stix2-patterns==2.0.0
      - testpath==0.6.0
      - toml==0.10.2
      - tomli==1.2.3
      - tornado==6.1
      - tox==3.28.0
      - traitlets==4.3.3
      - typing-extensions==4.1.1
      - urllib3==1.26.20
      - virtualenv==20.16.2
      - wcwidth==0.2.13
      - webencodings==0.5.1
      - zipp==3.6.0
prefix: /opt/conda/envs/cti-python-stix2

stix2/test/test_pattern_expressions.py::test_invalid_timestamp_constant
stix2/test/test_pattern_expressions.py::test_create_comparison_expression stix2/test/test_pattern_expressions.py::test_boolean_expression stix2/test/test_pattern_expressions.py::test_boolean_expression_with_parentheses stix2/test/test_pattern_expressions.py::test_hash_followed_by_registryKey_expression_python_constant stix2/test/test_pattern_expressions.py::test_hash_followed_by_registryKey_expression stix2/test/test_pattern_expressions.py::test_file_observable_expression stix2/test/test_pattern_expressions.py::test_multiple_file_observable_expression[AndObservationExpression-AND] stix2/test/test_pattern_expressions.py::test_multiple_file_observable_expression[OrObservationExpression-OR] stix2/test/test_pattern_expressions.py::test_root_types stix2/test/test_pattern_expressions.py::test_artifact_payload stix2/test/test_pattern_expressions.py::test_greater_than_python_constant stix2/test/test_pattern_expressions.py::test_greater_than stix2/test/test_pattern_expressions.py::test_less_than stix2/test/test_pattern_expressions.py::test_greater_than_or_equal stix2/test/test_pattern_expressions.py::test_less_than_or_equal stix2/test/test_pattern_expressions.py::test_not stix2/test/test_pattern_expressions.py::test_and_observable_expression stix2/test/test_pattern_expressions.py::test_invalid_and_observable_expression stix2/test/test_pattern_expressions.py::test_hex stix2/test/test_pattern_expressions.py::test_multiple_qualifiers stix2/test/test_pattern_expressions.py::test_set_op stix2/test/test_pattern_expressions.py::test_timestamp stix2/test/test_pattern_expressions.py::test_boolean stix2/test/test_pattern_expressions.py::test_binary stix2/test/test_pattern_expressions.py::test_list stix2/test/test_pattern_expressions.py::test_list2 stix2/test/test_pattern_expressions.py::test_invalid_constant_type stix2/test/test_pattern_expressions.py::test_invalid_integer_constant stix2/test/test_pattern_expressions.py::test_invalid_float_constant stix2/test/test_pattern_expressions.py::test_boolean_constant[True-True0] stix2/test/test_pattern_expressions.py::test_boolean_constant[False-False0] stix2/test/test_pattern_expressions.py::test_boolean_constant[True-True1] stix2/test/test_pattern_expressions.py::test_boolean_constant[False-False1] stix2/test/test_pattern_expressions.py::test_boolean_constant[true-True] stix2/test/test_pattern_expressions.py::test_boolean_constant[false-False] stix2/test/test_pattern_expressions.py::test_boolean_constant[t-True] stix2/test/test_pattern_expressions.py::test_boolean_constant[f-False] stix2/test/test_pattern_expressions.py::test_boolean_constant[T-True] stix2/test/test_pattern_expressions.py::test_boolean_constant[F-False] stix2/test/test_pattern_expressions.py::test_boolean_constant[1-True] stix2/test/test_pattern_expressions.py::test_boolean_constant[0-False] stix2/test/test_pattern_expressions.py::test_invalid_boolean_constant stix2/test/test_pattern_expressions.py::test_invalid_hash_constant[MD5-zzz] stix2/test/test_pattern_expressions.py::test_invalid_hash_constant[ssdeep-zzz==] stix2/test/test_pattern_expressions.py::test_invalid_hex_constant stix2/test/test_pattern_expressions.py::test_invalid_binary_constant stix2/test/test_pattern_expressions.py::test_escape_quotes_and_backslashes stix2/test/test_pattern_expressions.py::test_like stix2/test/test_pattern_expressions.py::test_issuperset stix2/test/test_pattern_expressions.py::test_repeat_qualifier stix2/test/test_pattern_expressions.py::test_invalid_repeat_qualifier stix2/test/test_pattern_expressions.py::test_invalid_within_qualifier stix2/test/test_pattern_expressions.py::test_startstop_qualifier stix2/test/test_pattern_expressions.py::test_invalid_startstop_qualifier stix2/test/test_pattern_expressions.py::test_make_constant_already_a_constant
README.rst docs/guide/patterns.ipynb stix2/patterns.py docs/guide/creating.ipynb
README.rst docs/guide/patterns.ipynb stix2/patterns.py docs/guide/creating.ipynb