Skip to content

Commit 9196e10

Browse files
authored
fixes for serverless APIs + tests (#287)
1 parent 83b82bf commit 9196e10

File tree

14 files changed

+1393
-120
lines changed

14 files changed

+1393
-120
lines changed

.github/workflows/ci_cd.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,7 @@ jobs:
152152
if: ${{ !env.ACT }}
153153
with:
154154
library-name: ${{ env.PACKAGE_NAME }}
155+
token: ${{ secrets.GITHUB_TOKEN }}
155156

156157
upload_dev_docs:
157158
name: Upload dev documentation

.gitignore

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -75,4 +75,3 @@ doc/_build
7575
*.tiff
7676
_test_enhanced_images.py
7777
local_tests/
78-
*.gz

README.rst

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,6 +141,81 @@ installation and the test file you are trying to run.
141141
142142
Note that any tests that require Docker will obviously fail.
143143

144+
Creating a Release
145+
------------------
146+
147+
- Before creating a new branch, make sure your local repository is up to date:
148+
149+
.. code-block:: bash
150+
151+
git pull
152+
153+
This ensures you have the latest changes from the default branch (usually ``main`` or ``develop``).
154+
155+
- Create a new branch for the release:
156+
157+
.. code-block:: bash
158+
159+
git checkout -b release/0.10
160+
161+
**Important:**
162+
The release branch must only include the **major** and **minor** version numbers.
163+
Do not include the patch version.
164+
For example, use ``release/0.10``, not ``release/0.10.0``.
165+
166+
- If creating a **patch release**, do not create a new branch.
167+
Instead, reuse the existing ``release/0.10`` branch.
168+
169+
- Update the version number in ``pyproject.toml``:
170+
171+
If the current version is:
172+
173+
.. code-block:: toml
174+
175+
version = "0.10.0.dev0"
176+
177+
bump it to:
178+
179+
.. code-block:: toml
180+
181+
version = "0.10.0"
182+
183+
- **Important:**
184+
Every time you create a development (``dev``) release, you should first release the corresponding stable version on PyPI before bumping the development version.
185+
186+
For example:
187+
188+
- If you are at ``0.10.0.dev0``, first release ``0.10.0`` on PyPI.
189+
- Then, after the release, bump the version to ``0.10.1.dev0``.
190+
191+
Otherwise, it may feel confusing to have a ``dev`` version without a corresponding stable release.
192+
193+
- Create a commit for the version bump:
194+
195+
.. code-block:: bash
196+
197+
git commit -am "MAINT: Bump version to v0.10.0"
198+
199+
- Then push the branch:
200+
201+
.. code-block:: bash
202+
203+
git push --set-upstream origin release/0.10
204+
205+
- Create a tag for the release:
206+
207+
.. code-block:: bash
208+
209+
git tag v0.10.0
210+
git push origin v0.10.0
211+
212+
**Important:**
213+
The release tag must always include the full **major.minor.patch** version number.
214+
Always include the ``v`` prefix.
215+
For example, use ``v0.10.0``, not ``v0.10``.
216+
Creating and pushing the tag automatically triggers the release workflow in GitHub Actions.
217+
218+
144219
Dependencies
145220
------------
146221
To use PyDynamicReporting, you must have a locally installed and licensed copy

pyproject.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ authors = [
1616

1717
maintainers = [
1818
{name = "ANSYS, Inc.", email = "[email protected]"},
19-
{name = "Ansys ADR Team", email = "nexus@ansys.com"},
19+
{name = "Ansys ADR Team", email = "adrteam@ansys.com"},
2020
]
2121
description = "Python interface to Ansys Dynamic Reporting"
2222
readme = "README.rst"
@@ -161,7 +161,7 @@ src_paths = ["doc", "src", "tests"]
161161

162162
[tool.codespell]
163163
ignore-words = "doc/styles/Vocab/ANSYS/accept.txt"
164-
skip = '*.pyc,*.xml,*.gif,*.png,*.jpg,*.js,*.html,doc/source/examples/**/*.ipynb'
164+
skip = '*.pyc,*.xml,*.gif,*.png,*.jpg,*.js,*.html,doc/source/examples/**/*.ipynb,*.json,*.gz'
165165
quiet-level = 3
166166

167167
[tool.bandit]

src/ansys/dynamicreporting/core/__init__.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
# Version
22
# ------------------------------------------------------------------------------
3-
from pathlib import Path
43

54
try:
65
import importlib.metadata as importlib_metadata # type: ignore

src/ansys/dynamicreporting/core/serverless/adr.py

Lines changed: 42 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -415,9 +415,7 @@ def setup(self, collect_static: bool = False) -> None:
415415
if self._debug is not None:
416416
overrides["DEBUG"] = self._debug
417417

418-
# cannot be None
419418
overrides["MEDIA_ROOT"] = str(self._media_directory)
420-
421419
if self._static_directory is not None:
422420
overrides["STATIC_ROOT"] = str(self._static_directory)
423421

@@ -536,14 +534,19 @@ def close(self):
536534
# close db connections
537535
try:
538536
connections.close_all()
539-
except DatabaseError:
537+
except DatabaseError: # pragma: no cover
540538
pass
541539
# cleanup temp files
542540
for tmp_dir in self._tmp_dirs:
543541
tmp_dir.cleanup()
544542

545543
def backup_database(
546-
self, output_directory: str = ".", *, database: str = "default", compress=False
544+
self,
545+
output_directory: str | Path = ".",
546+
*,
547+
database: str = "default",
548+
compress: bool = False,
549+
ignore_primary_keys: bool = False,
547550
) -> None:
548551
if self._in_memory:
549552
raise ADRException("Backup is not available in in-memory mode.")
@@ -555,21 +558,25 @@ def backup_database(
555558
# call django management command to dump the database
556559
timestamp = datetime.now().strftime("%Y%m%d%H%M%S")
557560
file_path = target_dir / f"backup_{timestamp}.json{'.gz' if compress else ''}"
561+
args = [
562+
"dumpdata",
563+
"--all",
564+
"--database",
565+
database,
566+
"--output",
567+
str(file_path),
568+
"--verbosity",
569+
0,
570+
"--natural-foreign",
571+
]
572+
if ignore_primary_keys:
573+
args.append("--natural-primary")
558574
try:
559-
management.call_command(
560-
"dumpdata",
561-
"--all",
562-
"--database",
563-
database,
564-
"--output",
565-
str(file_path),
566-
"--verbosity",
567-
0,
568-
)
575+
management.call_command(*args)
569576
except Exception as e:
570577
raise ADRException(f"Backup failed: {e}")
571578

572-
def restore_database(self, input_file: str, *, database: str = "default") -> None:
579+
def restore_database(self, input_file: str | Path, *, database: str = "default") -> None:
573580
if database != "default" and database not in self._databases:
574581
raise ADRException(f"{database} must be configured first using the 'databases' option.")
575582
backup_file = Path(input_file).resolve(strict=True)
@@ -647,10 +654,9 @@ def session_guid(self) -> uuid.UUID:
647654

648655
def create_item(self, item_type: type[Item], **kwargs: Any) -> Item:
649656
if not issubclass(item_type, Item):
650-
raise TypeError(f"{item_type} is not valid")
657+
raise TypeError(f"{item_type.__name__} is not a subclass of Item")
651658
if not kwargs:
652659
raise ADRException("At least one keyword argument must be provided to create the item.")
653-
kwargs["_in_memory"] = self._in_memory
654660
return item_type.create(
655661
session=kwargs.pop("session", self._session),
656662
dataset=kwargs.pop("dataset", self._dataset),
@@ -660,7 +666,7 @@ def create_item(self, item_type: type[Item], **kwargs: Any) -> Item:
660666
@staticmethod
661667
def create_template(template_type: type[Template], **kwargs: Any) -> Template:
662668
if not issubclass(template_type, Template):
663-
raise TypeError(f"{template_type} is not valid")
669+
raise TypeError(f"{template_type.__name__} is not a subclass of Template")
664670
if not kwargs:
665671
raise ADRException(
666672
"At least one keyword argument must be provided to create the template."
@@ -681,34 +687,30 @@ def get_report(**kwargs) -> Template:
681687
try:
682688
return Template.get(parent=None, **kwargs)
683689
except Exception as e:
684-
raise e
690+
raise ADRException(f"Report not found: {e}")
685691

686692
@staticmethod
687693
def get_reports(*, fields: list | None = None, flat: bool = False) -> ObjectSet | list:
688694
# return list of reports by default.
689695
# if fields are mentioned, return value list
690-
try:
691-
out = Template.filter(parent=None)
692-
if fields:
693-
out = out.values_list(*fields, flat=flat)
694-
except Exception as e:
695-
raise e
696-
696+
out = Template.filter(parent=None)
697+
if fields:
698+
out = out.values_list(*fields, flat=flat)
697699
return out
698700

699-
def get_list_reports(self, *, r_type: str = "name") -> ObjectSet | list:
701+
def get_list_reports(self, r_type: str | None = "name") -> ObjectSet | list:
700702
supported_types = ("name", "report")
701-
if r_type not in supported_types:
703+
if r_type and r_type not in supported_types:
702704
raise ADRException(f"r_type must be one of {supported_types}")
703-
if r_type == "name":
704-
return self.get_reports(
705-
fields=[
706-
r_type,
707-
],
708-
flat=True,
709-
)
710-
else:
705+
if not r_type or r_type == "report":
711706
return self.get_reports()
707+
# if r_type == "name":
708+
return self.get_reports(
709+
fields=[
710+
r_type,
711+
],
712+
flat=True,
713+
)
712714

713715
def render_report(
714716
self, *, context: dict | None = None, item_filter: str = "", **kwargs: Any
@@ -732,7 +734,9 @@ def query(
732734
**kwargs: Any,
733735
) -> ObjectSet:
734736
if not issubclass(query_type, (Item, Template, Session, Dataset)):
735-
raise TypeError(f"{query_type} is not valid")
737+
raise TypeError(
738+
f"'{query_type.__name__}' is not a type of Item, Template, Session, or Dataset"
739+
)
736740
return query_type.find(query=query, **kwargs)
737741

738742
@staticmethod
@@ -744,7 +748,7 @@ def create_objects(
744748
raise ADRException("objects must be an iterable")
745749
count = 0
746750
for obj in objects:
747-
if kwargs.get("using", "default") != obj.db:
751+
if obj.db and kwargs.get("using", "default") != obj.db:
748752
# required if copying across databases
749753
obj.reinit()
750754
obj.save(**kwargs)

src/ansys/dynamicreporting/core/serverless/base.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from collections.abc import Iterable
33
from dataclasses import dataclass, field
44
from dataclasses import fields as dataclass_fields
5+
from enum import Enum
56
import importlib
67
import inspect
78
from itertools import chain
@@ -563,3 +564,13 @@ def __set__(self, obj, value):
563564
@abstractmethod
564565
def process(self, value, obj):
565566
pass # pragma: no cover
567+
568+
569+
class StrEnum(str, Enum):
570+
"""Enum with a str mixin."""
571+
572+
def __str__(self):
573+
return self.value
574+
575+
def __repr__(self):
576+
return self.value

0 commit comments

Comments
 (0)