Repository: ansys/pydyna

Issue:
Support deepcopies of keywords/decks
### 📝 Description of the feature

In order to create copies by duplicating e.g.  *PART or *SET and later change the PID and SID one has to be able to create deep copies of the keyword classes. 

E.g. 
``from ansys.dyna.keywords import Deck``
``from ansys.dyna.keywords import keywords as kwd``
``import copy``
``material = kwd.Mat001``
``material.mid=99``
``x=Deck()``
``x.extend([material()])``
``y=copy.deepcopy(x)``
``y.keywords[0].mid=100``

should result in
``x.keywords[0].mid``
``99``
``y.keywords[0].mid``
``100``

currently results in
``x.keywords[0].mid``
``100``
``y.keywords[0].mid``
``100``

### 💡 Steps for implementing the feature

Support copy.deepcopy in the keyword class and the classes used by keyword.

### 🔗 Useful links and references

_No response_

Discussion:

Solution patch:
diff --git a/doc/changelog/647.miscellaneous.md b/doc/changelog/647.miscellaneous.md
new file mode 100644
index 00000000..156fd60b
--- /dev/null
+++ b/doc/changelog/647.miscellaneous.md
@@ -0,0 +1 @@
+fix: warn out of bounds characters when reading lines
\ No newline at end of file
diff --git a/doc/changelog/648.miscellaneous.md b/doc/changelog/648.miscellaneous.md
new file mode 100644
index 00000000..d4369e0f
--- /dev/null
+++ b/doc/changelog/648.miscellaneous.md
@@ -0,0 +1 @@
+test: add test for deepcopy
\ No newline at end of file
diff --git a/src/ansys/dyna/core/lib/kwd_line_formatter.py b/src/ansys/dyna/core/lib/kwd_line_formatter.py
index ee28b854..e780900d 100644
--- a/src/ansys/dyna/core/lib/kwd_line_formatter.py
+++ b/src/ansys/dyna/core/lib/kwd_line_formatter.py
@@ -21,6 +21,7 @@
 # SOFTWARE.
 
 import typing
+import warnings
 
 from ansys.dyna.core.lib.parameter_set import ParameterSet
 
@@ -146,6 +147,7 @@ def load_dataline(spec: typing.List[tuple], line_data: str, parameter_set: Param
         data.append(value)
 
     if end_position < len(line_data):
-        raise Exception("Data line is too long!")
+        warning_message = f'Detected out of bound card characters:\n"{line_data[end_position:]}"\n"Ignoring.'
+        warnings.warn(warning_message)
 
     return tuple(data)


Test patch:
diff --git a/tests/test_card.py b/tests/test_card.py
index fdd6ae56..ec2162c0 100644
--- a/tests/test_card.py
+++ b/tests/test_card.py
@@ -40,7 +40,7 @@ def test_load_card_errors(string_utils):
         # cards can only load a readable buffer
         card.read("")
 
-    with pytest.raises(Exception):
+    with pytest.warns(UserWarning, match="Detected out of bound card characters"):
         # error if the line that is too long
         buf = "                                           "
         card.read(string_utils.as_buffer(buf))
diff --git a/tests/test_deck.py b/tests/test_deck.py
index e0f11beb..2fe538c9 100644
--- a/tests/test_deck.py
+++ b/tests/test_deck.py
@@ -146,6 +146,17 @@ def test_deck_006(ref_string):
     assert len(deck5._keywords) == len(deck3._keywords) + len(deck4._keywords)
 
 
+@pytest.mark.keywords
+def test_deck_copy():
+    import copy
+    x = Deck()
+    x.append(kwd.Mat001(mid=99))
+    y = copy.deepcopy(x)
+    y.keywords[0].mid = 100
+    assert x.keywords[0].mid == 99
+    assert y.keywords[0].mid == 100
+
+
 @pytest.mark.keywords
 def test_deck_read_parameters():
     """Test reading a deck with parameters."""
diff --git a/tests/test_keywords.py b/tests/test_keywords.py
index 1e4468eb..b7fb7e92 100644
--- a/tests/test_keywords.py
+++ b/tests/test_keywords.py
@@ -162,6 +162,11 @@ $#   secid      area     thick
     # ensure that it does not throw when the data line is empty
     belt.loads(seatbelt_text)
 
+    seatbelt_text = seatbelt_text + "..."
+    # ensure that it does not throw when extra characters are added
+    with pytest.warns(UserWarning, match="Detected out of bound card characters"):
+        belt.loads(seatbelt_text)
+
 
 @pytest.mark.keywords
 def test_read_nodes(ref_string):
diff --git a/tests/test_load_dataline.py b/tests/test_load_dataline.py
index f9f3944e..bc5c0f62 100644
--- a/tests/test_load_dataline.py
+++ b/tests/test_load_dataline.py
@@ -65,7 +65,7 @@ def test_load_dataline_005():
 @pytest.mark.keywords
 def test_load_dataline_006():
     """test loading a data line that is too long."""
-    with pytest.raises(Exception):
+    with pytest.warns(UserWarning, match="Detected out of bound card characters"):
         load_dataline([(0, 8, int), (8, 8, float), (16, 8, float)], "                                          ")