10.15.0
diff --git a/CHANGELOG.md b/CHANGELOG.md
index f1183857f..8c9ecf60a 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -5,12 +5,13 @@ All notable changes to this project will be documented in this file.
 The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
 and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
 
-## [10.15.0] - Unreleased
+## [10.15.0] - 2021-11-28
 
 ### Added
 
 - Added dynamic_progress.py to examples
 - Added ConsoleOptions.update_height
+- Fixed Padding not respecting height
 
 ### Changed
 
diff --git a/pyproject.toml b/pyproject.toml
index c3ed18ed8..4300beae9 100644
--- a/pyproject.toml
+++ b/pyproject.toml
@@ -2,7 +2,7 @@
 name = "rich"
 homepage = "https://github.com/willmcgugan/rich"
 documentation = "https://rich.readthedocs.io/en/latest/"
-version = "10.15.0-alpha3"
+version = "10.15.0"
 description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal"
 authors = ["Will McGugan <willmcgugan@gmail.com>"]
 license = "MIT"
diff --git a/rich/text.py b/rich/text.py
index c49e152b6..288cb6d11 100644
--- a/rich/text.py
+++ b/rich/text.py
@@ -254,10 +254,10 @@ def from_ansi(
         end: str = "\n",
         tab_size: Optional[int] = 8,
     ) -> "Text":
-        """Create a Text object from pre-formatted ANSI.
+        """Create a Text object from a string containing ANSI escape codes.
 
         Args:
-            text (str): A string containing ANSI color codes.
+            text (str): A string containing escape codes.
             style (Union[str, Style], optional): Base style for text. Defaults to "".
             justify (str, optional): Justify method: "left", "center", "full", "right". Defaults to None.
             overflow (str, optional): Overflow method: "crop", "fold", "ellipsis". Defaults to None.
@@ -267,14 +267,18 @@ def from_ansi(
         """
         from .ansi import AnsiDecoder
 
-        decoded_text = AnsiDecoder().decode_line(text)
-        decoded_text.justify = justify
-        decoded_text.overflow = overflow
-        decoded_text.no_wrap = no_wrap
-        decoded_text.end = end
-        decoded_text.tab_size = tab_size
-        decoded_text.stylize(style)
-        return decoded_text
+        joiner = Text(
+            "\n",
+            justify=justify,
+            overflow=overflow,
+            no_wrap=no_wrap,
+            end=end,
+            tab_size=tab_size,
+            style=style,
+        )
+        decoder = AnsiDecoder()
+        result = joiner.join(line for line in decoder.decode(text))
+        return result
 
     @classmethod
     def styled(
diff --git a/tests/test_pretty.py b/tests/test_pretty.py
index d2e829963..ab83d930d 100644
--- a/tests/test_pretty.py
+++ b/tests/test_pretty.py
@@ -97,6 +97,7 @@ def test_small_width():
     assert result == expected
 
 
+@skip_py36
 def test_broken_repr():
     class BrokenRepr:
         def __repr__(self):
@@ -108,6 +109,7 @@ def __repr__(self):
     assert result == expected
 
 
+@skip_py36
 def test_broken_getattr():
     class BrokenAttr:
         def __getattr__(self, name):
diff --git a/tests/test_repr.py b/tests/test_repr.py
index c47b76b12..3c2f48a46 100644
--- a/tests/test_repr.py
+++ b/tests/test_repr.py
@@ -1,10 +1,22 @@
 import pytest
+import sys
 from typing import Optional
 
 from rich.console import Console
 import rich.repr
 
 
+skip_py36 = pytest.mark.skipif(
+    sys.version_info.minor == 6 and sys.version_info.major == 3,
+    reason="rendered differently on py3.6",
+)
+
+skip_py37 = pytest.mark.skipif(
+    sys.version_info.minor == 7 and sys.version_info.major == 3,
+    reason="rendered differently on py3.7",
+)
+
+
 @rich.repr.auto
 class Foo:
     def __init__(self, foo: str, bar: Optional[int] = None, egg: int = 1):
@@ -59,13 +71,21 @@ def test_rich_repr() -> None:
     assert (repr(Foo("hello", bar=3))) == "Foo('hello', 'hello', bar=3, egg=1)"
 
 
+@skip_py36
+@skip_py37
 def test_rich_repr_positional_only() -> None:
-    @rich.repr.auto
-    class PosOnly:
-        def __init__(self, foo, /):
-            self.foo = 1
-
-    p = PosOnly(1)
+    _locals = locals().copy()
+    exec(
+        """\
+@rich.repr.auto
+class PosOnly:
+    def __init__(self, foo, /):
+        self.foo = 1
+    """,
+        globals(),
+        _locals,
+    )
+    p = _locals["PosOnly"](1)
     assert repr(p) == "PosOnly(1)"
 
 
diff --git a/tests/test_text.py b/tests/test_text.py
index 6eecb9ee7..63f128e52 100644
--- a/tests/test_text.py
+++ b/tests/test_text.py
@@ -97,11 +97,12 @@ def test_from_markup():
 
 def test_from_ansi():
     text = Text.from_ansi("Hello, \033[1mWorld!\033[0m")
-    text2 = Text.from_ansi("Hello, \033[1mWorld!\033[0m", style="red")
     assert str(text) == "Hello, World!"
     assert text._spans == [Span(7, 13, Style(bold=True))]
-    assert str(text2) == "Hello, World!"
-    assert text2._spans == [Span(7, 13, Style(bold=True)), Span(0, 13, "red")]
+
+    text = Text.from_ansi("Hello, \033[1m\nWorld!\033[0m")
+    assert str(text) == "Hello, \nWorld!"
+    assert text._spans == [Span(8, 14, Style(bold=True))]
 
 
 def test_copy():

Fixes https://github.com/willmcgugan/rich/issues/1721
Fixes https://github.com/willmcgugan/rich/issues/1530