Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package python-pyorthanc for 
openSUSE:Factory checked in at 2026-04-04 19:07:55
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-pyorthanc (Old)
 and      /work/SRC/openSUSE:Factory/.python-pyorthanc.new.21863 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-pyorthanc"

Sat Apr  4 19:07:55 2026 rev:2 rq:1344562 version:1.22.1

Changes:
--------
--- /work/SRC/openSUSE:Factory/python-pyorthanc/python-pyorthanc.changes        
2025-05-13 20:05:52.867868174 +0200
+++ 
/work/SRC/openSUSE:Factory/.python-pyorthanc.new.21863/python-pyorthanc.changes 
    2026-04-04 19:09:50.947107069 +0200
@@ -1,0 +2,19 @@
+Sat Apr  4 08:13:32 UTC 2026 - Dirk Müller <[email protected]>
+
+- update to 1.22.1:
+  * Remove warning when importing pyorthanc
+- update to 1.22.0:
+  * `upload()` now returns a list the uploaded instances
+    `list[pyorthanc.Instance]`
+  * Add `get_internal_client()` to retrieve an Orthanc client
+    inside an orthanc script
+  * `upload()` now allows uploading DICOM files from a zip or
+    directory
+  * Add check_before_upload option
+- update to 1.21.0:
+  * Add URL properties
+- update to 1.20.1:
+  * Allows pydicom=3.x.x versions
+  * Add kwargs to find_patient/study/series/instance
+
+-------------------------------------------------------------------

Old:
----
  pyorthanc-1.20.0.tar.gz

New:
----
  pyorthanc-1.22.1.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ python-pyorthanc.spec ++++++
--- /var/tmp/diff_new_pack.DzfsN1/_old  2026-04-04 19:09:51.843143807 +0200
+++ /var/tmp/diff_new_pack.DzfsN1/_new  2026-04-04 19:09:51.847143971 +0200
@@ -1,6 +1,7 @@
 #
-# spec file
+# spec file for package python-pyorthanc
 #
+# Copyright (c) 2026 SUSE LLC and contributors
 # Copyright (c) 2025 Dr. Axel Braun <[email protected]>
 #
 # All modifications and additions to the file contributed by third parties
@@ -15,28 +16,26 @@
 # Please submit bugfixes or comments via https://bugs.opensuse.org/
 #
 
-%{?sle15_python_module_pythons}
 
 %define modname pyorthanc
+%{?sle15_python_module_pythons}
 Name:           python-%{modname}
-Version:        1.20.0
+Version:        1.22.1
 Release:        0
 Summary:        A comprehensive Python client for Orthanc
 License:        MIT
 URL:            https://github.com/gacou54/pyorthanc
 Source0:        
https://files.pythonhosted.org/packages/source/p/pyorthanc/%{modname}-%{version}.tar.gz
-BuildRequires:  %{python_module setuptools}
-BuildRequires:  %{python_module pip}
 BuildRequires:  %{python_module devel}
+BuildRequires:  %{python_module pip}
+BuildRequires:  %{python_module poetry-core >= 1.0.0}
+BuildRequires:  %{python_module setuptools}
 BuildRequires:  %{python_module wheel}
-BuildRequires:  %{python_module poetry-core}
 BuildRequires:  fdupes
 BuildRequires:  python-rpm-macros
+Requires:       python-httpx >= 0.24.1
+Requires:       python-pydicom >= 2.3.0
 BuildArch:      noarch
-
-Requires:        python-pydicom
-Requires:        python-httpx
-
 %python_subpackages
 
 %description

++++++ pyorthanc-1.20.0.tar.gz -> pyorthanc-1.22.1.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pyorthanc-1.20.0/PKG-INFO 
new/pyorthanc-1.22.1/PKG-INFO
--- old/pyorthanc-1.20.0/PKG-INFO       1970-01-01 01:00:00.000000000 +0100
+++ new/pyorthanc-1.22.1/PKG-INFO       1970-01-01 01:00:00.000000000 +0100
@@ -1,8 +1,7 @@
-Metadata-Version: 2.1
+Metadata-Version: 2.3
 Name: pyorthanc
-Version: 1.20.0
+Version: 1.22.1
 Summary: Orthanc REST API python wrapper with additional utilities
-Home-page: https://gacou54.github.io/pyorthanc/
 License: MIT
 Keywords: Orthanc,DICOM,Medical-Imaging
 Author: Gabriel Couture
@@ -15,11 +14,13 @@
 Classifier: Programming Language :: Python :: 3.10
 Classifier: Programming Language :: Python :: 3.11
 Classifier: Programming Language :: Python :: 3.12
+Classifier: Programming Language :: Python :: 3.13
 Provides-Extra: all
 Provides-Extra: progress
 Requires-Dist: httpx (>=0.24.1,<1.0.0)
-Requires-Dist: pydicom (>=2.3.0,<3.0.0)
+Requires-Dist: pydicom (>=2.3.0,<4.0.0)
 Requires-Dist: tqdm (>=4.66.1,<5.0.0) ; extra == "progress" or extra == "all"
+Project-URL: Homepage, https://gacou54.github.io/pyorthanc/
 Project-URL: Repository, https://github.com/gacou54/pyorthanc
 Description-Content-Type: text/markdown
 
@@ -78,8 +79,13 @@
 patient_ids = client.get_patients()
 studies = client.get_studies() 
 
-# Upload DICOM file
-upload(client, 'image_path.dcm')
+# Upload DICOM files
+upload(client, 'image.dcm')  # From a file
+upload(client, 'dicom_files.zip')  # From a zip
+upload(client, 'path/to/directory')  # Upload all dicom files in a directory
+upload(client, 'path/to/directory', recursive=True)  # Upload all dicom files 
in a directory recursively
+# Check if dicom is in Orthanc before upload
+upload(client, 'path/to/directory', recursive=True, check_before_upload=True)
 ```
 
 ## Working with DICOM Modalities
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pyorthanc-1.20.0/README.md 
new/pyorthanc-1.22.1/README.md
--- old/pyorthanc-1.20.0/README.md      2025-03-28 15:08:18.035561600 +0100
+++ new/pyorthanc-1.22.1/README.md      1970-01-01 01:00:00.000000000 +0100
@@ -53,8 +53,13 @@
 patient_ids = client.get_patients()
 studies = client.get_studies() 
 
-# Upload DICOM file
-upload(client, 'image_path.dcm')
+# Upload DICOM files
+upload(client, 'image.dcm')  # From a file
+upload(client, 'dicom_files.zip')  # From a zip
+upload(client, 'path/to/directory')  # Upload all dicom files in a directory
+upload(client, 'path/to/directory', recursive=True)  # Upload all dicom files 
in a directory recursively
+# Check if dicom is in Orthanc before upload
+upload(client, 'path/to/directory', recursive=True, check_before_upload=True)
 ```
 
 ## Working with DICOM Modalities
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pyorthanc-1.20.0/pyorthanc/__init__.py 
new/pyorthanc-1.22.1/pyorthanc/__init__.py
--- old/pyorthanc-1.20.0/pyorthanc/__init__.py  2025-01-23 16:22:02.515639300 
+0100
+++ new/pyorthanc-1.22.1/pyorthanc/__init__.py  1970-01-01 01:00:00.000000000 
+0100
@@ -5,6 +5,7 @@
 from . import errors, util
 from ._filtering import find, trim_patients
 from ._find import find_instances, find_patients, find_series, find_studies, 
query_orthanc
+from ._internal_client import get_internal_client
 from ._modality import Modality, RemoteModality
 from ._resources import Instance, Patient, Series, Study
 from ._upload import async_upload, upload
@@ -31,6 +32,7 @@
     'find_studies',
     'find_series',
     'find_instances',
+    'get_internal_client',
     'query_orthanc',
     'Job',
     'retrieve_and_write_patients',
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pyorthanc-1.20.0/pyorthanc/_find.py 
new/pyorthanc-1.22.1/pyorthanc/_find.py
--- old/pyorthanc-1.20.0/pyorthanc/_find.py     2025-01-16 20:46:34.417057000 
+0100
+++ new/pyorthanc-1.22.1/pyorthanc/_find.py     1970-01-01 01:00:00.000000000 
+0100
@@ -14,7 +14,8 @@
 def find_patients(client: Orthanc,
                   query: Dict[str, str] = None,
                   labels: Union[List[str], str] = None,
-                  labels_constraint: str = 'All') -> List[Patient]:
+                  labels_constraint: str = 'All',
+                  **kwargs) -> List[Patient]:
     """Finds patients in Orthanc according to queries and labels
 
     Parameters
@@ -27,6 +28,8 @@
         List of strings specifying which labels to look for in the resources.
     labels_constraint
         Constraint on the labels, can be 'All', 'Any', or 'None'.
+    **kwargs
+        Additional keyword arguments passed to `query_orthanc`
 
     Returns
     -------
@@ -51,14 +54,16 @@
         level='Patient',
         query=query,
         labels=labels,
-        labels_constraint=labels_constraint
+        labels_constraint=labels_constraint,
+        **kwargs
     )
 
 
 def find_studies(client: Orthanc,
                  query: Dict[str, str] = None,
                  labels: Union[List[str], str] = None,
-                 labels_constraint: str = 'All') -> List[Study]:
+                 labels_constraint: str = 'All',
+                 **kwargs) -> List[Study]:
     """Finds studies in Orthanc according to queries and labels
 
     Parameters
@@ -71,6 +76,8 @@
         List of strings specifying which labels to look for in the resources.
     labels_constraint
         Constraint on the labels, can be 'All', 'Any', or 'None'.
+    **kwargs
+        Additional keyword arguments passed to `query_orthanc`
 
     Returns
     -------
@@ -96,14 +103,16 @@
         level='Study',
         query=query,
         labels=labels,
-        labels_constraint=labels_constraint
+        labels_constraint=labels_constraint,
+        **kwargs
     )
 
 
 def find_series(client: Orthanc,
                 query: Dict[str, str] = None,
                 labels: Union[List[str], str] = None,
-                labels_constraint: str = 'All') -> List[Series]:
+                labels_constraint: str = 'All',
+                **kwargs) -> List[Series]:
     """Finds series in Orthanc according to queries and labels
 
     Parameters
@@ -116,6 +125,8 @@
         List of strings specifying which labels to look for in the resources.
     labels_constraint
         Constraint on the labels, can be 'All', 'Any', or 'None'.
+    **kwargs
+        Additional keyword arguments passed to `query_orthanc`
 
     Returns
     -------
@@ -140,14 +151,16 @@
         level='Series',
         query=query,
         labels=labels,
-        labels_constraint=labels_constraint
+        labels_constraint=labels_constraint,
+        **kwargs
     )
 
 
 def find_instances(client: Orthanc,
                    query: Dict[str, str] = None,
                    labels: Union[List[str], str] = None,
-                   labels_constraint: str = 'All') -> List[Instance]:
+                   labels_constraint: str = 'All',
+                   **kwargs) -> List[Instance]:
     """Finds instances in Orthanc according to queries and labels
 
     Parameters
@@ -160,6 +173,8 @@
         List of strings specifying which labels to look for in the resources.
     labels_constraint
         Constraint on the labels, can be 'All', 'Any', or 'None'.
+    **kwargs
+        Additional keyword arguments passed to `query_orthanc`
 
     Returns
     -------
@@ -184,7 +199,8 @@
         level='Instance',
         query=query,
         labels=labels,
-        labels_constraint=labels_constraint
+        labels_constraint=labels_constraint,
+        **kwargs
     )
 
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pyorthanc-1.20.0/pyorthanc/_internal_client.py 
new/pyorthanc-1.22.1/pyorthanc/_internal_client.py
--- old/pyorthanc-1.20.0/pyorthanc/_internal_client.py  1970-01-01 
01:00:00.000000000 +0100
+++ new/pyorthanc-1.22.1/pyorthanc/_internal_client.py  1970-01-01 
01:00:00.000000000 +0100
@@ -0,0 +1,49 @@
+import json
+
+from . import Orthanc
+from .errors import NotInInternalEnvironmentError
+
+
+def get_internal_client() -> Orthanc:
+    """
+    Returns an Orthanc client that can be used inside the Orthanc server in 
python scripts.
+
+    Examples
+    -------
+    This example shows how to use the Orthanc REST API inside an Orthanc 
Python Script.
+    >>> from pyorthanc import get_internal_client, find_series, orthanc_sdk
+    >>> def get_modalities_in_orthanc(output: orthanc_sdk.RestOutput, *_, 
**__):
+    ...     '''This function returns all modalities in Orthanc.'''
+    ...     client = get_internal_client()
+    ...
+    ...     series = find_series(client)
+    ...     modalities_in_orthanc = set([s.modality for s in series])
+    ...
+    ...     output.AnswerBuffer(
+    ...         json.dumps({'modalities': list(modalities_in_orthanc)}),
+    ...         'application/json'
+    ...     )
+    ...
+    ... orthanc_sdk.RegisterRestCallback('/get-modalities-in-orthanc', 
get_modalities_in_orthanc)
+    """
+    from pyorthanc import orthanc_sdk
+
+    config = orthanc_sdk.GetConfiguration()
+
+    if not config:
+        raise NotInInternalEnvironmentError(
+            'This function is only available inside Orthanc server. '
+            'Use `pyorthanc.get_internal_client()` inside a Orthanc Python 
Script. '
+            'An `pyorthanc.Orthanc()` instance should be defined when using 
the '
+            'Orthanc REST API outside of an Orthanc Oython Python Script.'
+        )
+
+    port = json.loads(config).get('HttpPort', 8042)
+    url = f'http://localhost:{port}'
+
+    client = Orthanc(url=url)
+
+    token = orthanc_sdk.GenerateRestApiAuthorizationToken()
+    client.headers['Authorization'] = token
+
+    return client
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pyorthanc-1.20.0/pyorthanc/_resources/instance.py 
new/pyorthanc-1.22.1/pyorthanc/_resources/instance.py
--- old/pyorthanc-1.20.0/pyorthanc/_resources/instance.py       2025-01-16 
21:47:24.607246600 +0100
+++ new/pyorthanc-1.22.1/pyorthanc/_resources/instance.py       1970-01-01 
01:00:00.000000000 +0100
@@ -88,6 +88,17 @@
         return self.client.get_instances_id(self.id_)
 
     @property
+    def legacy_viewer_url(self) -> str:
+        """Get Instance (legacy viewer) URL
+
+        Returns
+        -------
+        str
+            URL of instance (legacy viewer)
+        """
+        return f'{self.client.url}/app/explorer.html#instance?uuid={self.id_}'
+
+    @property
     def file_size(self) -> int:
         """Get the file size
 
@@ -138,7 +149,7 @@
     @property
     def acquisition_number(self) -> int:
         return int(self._get_main_dicom_tag_value('AcquisitionNumber'))
-    
+
     @property
     def image_index(self) -> int:
         return int(self._get_main_dicom_tag_value('ImageIndex'))
@@ -158,11 +169,11 @@
     @property
     def image_comments(self) -> str:
         return self._get_main_dicom_tag_value('ImageComments')
-    
+
     @property
     def instance_number(self) -> int:
         return int(self._get_main_dicom_tag_value('InstanceNumber'))
-    
+
     @property
     def number_of_frames(self) -> int:
         return int(self._get_main_dicom_tag_value('NumberOfFrames'))
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pyorthanc-1.20.0/pyorthanc/_resources/patient.py 
new/pyorthanc-1.22.1/pyorthanc/_resources/patient.py
--- old/pyorthanc-1.20.0/pyorthanc/_resources/patient.py        2025-01-16 
20:46:34.419057000 +0100
+++ new/pyorthanc-1.22.1/pyorthanc/_resources/patient.py        1970-01-01 
01:00:00.000000000 +0100
@@ -28,6 +28,17 @@
         return self.client.get_patients_id(self.id_)
 
     @property
+    def legacy_viewer_url(self) -> str:
+        """Get Patient (legacy viewer) URL
+
+        Returns
+        -------
+        str
+            URL of patient (legacy viewer)
+        """
+        return f'{self.client.url}/app/explorer.html#patient?uuid={self.id_}'
+
+    @property
     def patient_id(self) -> str:
         """Get patient ID"""
         return self._get_main_dicom_tag_value('PatientID')
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pyorthanc-1.20.0/pyorthanc/_resources/resource.py 
new/pyorthanc-1.22.1/pyorthanc/_resources/resource.py
--- old/pyorthanc-1.20.0/pyorthanc/_resources/resource.py       2025-01-16 
20:46:34.422057000 +0100
+++ new/pyorthanc-1.22.1/pyorthanc/_resources/resource.py       1970-01-01 
01:00:00.000000000 +0100
@@ -51,6 +51,10 @@
         return self._main_dicom_tags
 
     @abc.abstractmethod
+    def legacy_viewer_url(self):
+        raise NotImplementedError
+
+    @abc.abstractmethod
     def get_main_information(self):
         raise NotImplementedError
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pyorthanc-1.20.0/pyorthanc/_resources/series.py 
new/pyorthanc-1.22.1/pyorthanc/_resources/series.py
--- old/pyorthanc-1.20.0/pyorthanc/_resources/series.py 2025-01-16 
20:46:34.423056800 +0100
+++ new/pyorthanc-1.22.1/pyorthanc/_resources/series.py 1970-01-01 
01:00:00.000000000 +0100
@@ -51,6 +51,17 @@
         return self.client.get_series_id(self.id_)
 
     @property
+    def legacy_viewer_url(self) -> str:
+        """Get Series (legacy viewer) URL
+
+        Returns
+        -------
+        str
+            URL of series (legacy viewer)
+        """
+        return f'{self.client.url}/app/explorer.html#series?uuid={self.id_}'
+
+    @property
     def manufacturer(self) -> str:
         """Get the manufacturer"""
         return self._get_main_dicom_tag_value('Manufacturer')
@@ -116,11 +127,11 @@
     @property
     def body_part_examined(self) -> str:
         return self._get_main_dicom_tag_value('BodyPartExamined')
-    
+
     @property
     def sequence_name(self) -> str:
         return self._get_main_dicom_tag_value('SequenceName')
-    
+
     @property
     def cardiac_number_of_images(self) -> int:
         return int(self._get_main_dicom_tag_value('CardiacNumberOfImages'))
@@ -128,7 +139,7 @@
     @property
     def images_in_acquisition(self) -> int:
         return int(self._get_main_dicom_tag_value('ImagesInAcquisition'))
-    
+
     @property
     def number_of_temporal_positions(self) -> int:
         return int(self._get_main_dicom_tag_value('NumberOfTemporalPositions'))
@@ -150,15 +161,15 @@
     @property
     def series_type(self) -> str:
         return self._get_main_dicom_tag_value('SeriesType')
-    
+
     @property
     def operators_name(self) -> str:
         return self._get_main_dicom_tag_value('OperatorsName')
-    
+
     @property
     def acquisition_device_processing_description(self) -> str:
         return 
self._get_main_dicom_tag_value('AcquisitionDeviceProcessingDescription')
-    
+
     @property
     def contrast_bolus_agent(self) -> str:
         return self._get_main_dicom_tag_value('ContrastBolusAgent')
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pyorthanc-1.20.0/pyorthanc/_resources/study.py 
new/pyorthanc-1.22.1/pyorthanc/_resources/study.py
--- old/pyorthanc-1.20.0/pyorthanc/_resources/study.py  2025-01-16 
20:46:34.423056800 +0100
+++ new/pyorthanc-1.22.1/pyorthanc/_resources/study.py  1970-01-01 
01:00:00.000000000 +0100
@@ -32,6 +32,17 @@
         return self.client.get_studies_id(self.id_)
 
     @property
+    def legacy_viewer_url(self) -> str:
+        """Get Study (legacy viewer) URL
+
+        Returns
+        -------
+        str
+            URL of study (legacy viewer)
+        """
+        return f'{self.client.url}/app/explorer.html#study?uuid={self.id_}'
+
+    @property
     def referring_physician_name(self) -> str:
         """Get referring physician name"""
         return self._get_main_dicom_tag_value('ReferringPhysicianName')
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pyorthanc-1.20.0/pyorthanc/_upload.py 
new/pyorthanc-1.22.1/pyorthanc/_upload.py
--- old/pyorthanc-1.20.0/pyorthanc/_upload.py   2025-01-16 21:47:24.607246600 
+0100
+++ new/pyorthanc-1.22.1/pyorthanc/_upload.py   1970-01-01 01:00:00.000000000 
+0100
@@ -1,14 +1,22 @@
+import glob
+import os
 from io import BytesIO
 from pathlib import Path
-from typing import Dict, List, Union
+from typing import Dict, Generator, List, Tuple, Union
 
 import httpx
 import pydicom
+from pydicom.errors import InvalidDicomError
 
-from pyorthanc import AsyncOrthanc, Orthanc
+from pyorthanc import AsyncOrthanc, Instance, Orthanc
+from pyorthanc.util import ensure_non_raw_response, 
to_orthanc_instance_id_from_ds
 
 
-def upload(client: Orthanc, path_or_ds: Union[str, Path, pydicom.Dataset]) -> 
Union[Dict, httpx.Response]:
+def upload(
+        client: Orthanc,
+        path_or_ds: Union[str, Path, pydicom.Dataset],
+        recursive: bool = False,
+        check_before_upload: bool = False) -> List[Instance]:
     """Upload a DICOM file or dataset to Orthanc synchronously
 
     Parameters
@@ -16,11 +24,52 @@
     client : Orthanc
         The Orthanc client to use for upload
     path_or_ds : Union[str, Path, pydicom.Dataset]
-        Either a path to a DICOM file or a pydicom Dataset object
+        Either a path to a DICOM file, directory, zip file or a pydicom 
Dataset object
+    recursive : bool
+        When `path_or_ds` is a directory, whether to upload recursively all 
the DICOM files in the directory
+    check_before_upload : bool
+         Verify if data is already in Orthanc before sending it. It verifies 
if a file is stored, there is no file comparison.
     """
-    dicom_bytes = _prepare_data_from_ds_or_file(path_or_ds)
+    client = ensure_non_raw_response(client)
+
+    instances = []
+
+    # If path_or_ds is a directory, upload all the DICOM files in the 
directory.
+    if (isinstance(path_or_ds, str) or isinstance(path_or_ds, Path)) and 
os.path.isdir(path_or_ds):
+        for dicom_bytes in _generate_dicom_bytes_from_directory(path_or_ds, 
recursive=recursive):
+            if check_before_upload:
+                data_is_in_orthanc, instance = 
_is_data_already_in_orthanc(client, dicom_bytes)
+
+                # If data is already in Orthanc, skip uploading it and go to 
the next file.
+                if data_is_in_orthanc:
+                    instances.append(instance)
+                    continue
+
+            result = client.post_instances(dicom_bytes)
+            instance = Instance(result['ID'], client)
+            instances.append(instance)
+
+    # If path_or_ds is a DICOM file, zip file or a pydicom Dataset, upload it.
+    else:
+        dicom_bytes = _prepare_data_from_ds_or_file(path_or_ds)
+
+        if check_before_upload:
+            data_in_orthanc, instance = _is_data_already_in_orthanc(client, 
dicom_bytes)
+            # If data is already in Orthanc, returns the instance directly.
+            if data_in_orthanc:
+                instances.append(instance)
+                return instances
+
+        result = client.post_instances(dicom_bytes)
+
+        # When a zip is uploaded, result can be a list of instances if the zip 
contained multiple DICOM files.
+        if isinstance(result, list):
+            instances += [Instance(i['ID'], client) for i in result]
+        else:
+            instance = Instance(result['ID'], client)
+            instances.append(instance)
 
-    return client.post_instances(dicom_bytes)
+    return instances
 
 
 async def async_upload(client: AsyncOrthanc, path_or_ds: Union[str, Path, 
pydicom.Dataset]) -> Union[Dict, httpx.Response]:
@@ -31,11 +80,11 @@
     client : AsyncOrthanc
         The async Orthanc client to use for upload
     path_or_ds : Union[str, Path, pydicom.Dataset]
-        Either a path to a DICOM file or a pydicom Dataset object
+        Either a path to a DICOM file, zip file or a pydicom Dataset object
     """
     dicom_bytes = _prepare_data_from_ds_or_file(path_or_ds)
 
-    return await client.post_instances()
+    return await client.post_instances(dicom_bytes)
 
 
 def _prepare_data_from_ds_or_file(path_or_ds: Union[str, Path, 
pydicom.Dataset]) -> bytes:
@@ -43,11 +92,49 @@
     if isinstance(path_or_ds, str) or isinstance(path_or_ds, Path):
         with open(path_or_ds, 'rb') as f:
             dicom_bytes = f.read()
+
     elif isinstance(path_or_ds, pydicom.Dataset):
         buffer = BytesIO()
         path_or_ds.save_as(buffer)
         dicom_bytes = buffer.getvalue()
+
     else:
-        raise TypeError("path_or_ds must be either a file path or pydicom 
Dataset")
+        raise TypeError('path_or_ds must be either a file path or pydicom 
Dataset')
 
     return dicom_bytes
+
+
+def _generate_dicom_bytes_from_directory(directory: str, recursive: bool) -> 
Generator[bytes, None, None]:
+    filepaths = glob.glob(os.path.join(directory, '*.dcm'))
+    filepaths += glob.glob(os.path.join(directory, '*.DCM'))
+    filepaths += glob.glob(os.path.join(directory, '*.dcm.gz'))
+    filepaths += glob.glob(os.path.join(directory, '*.DCM.gz'))
+
+    if recursive is True:
+        filepaths += glob.glob(os.path.join(directory, '**', '*.dcm'), 
recursive=recursive)
+        filepaths += glob.glob(os.path.join(directory, '**', '*.DCM'), 
recursive=recursive)
+        filepaths += glob.glob(os.path.join(directory, '**', '*.dcm.gz'), 
recursive=recursive)
+        filepaths += glob.glob(os.path.join(directory, '**', '*.DCM.gz'), 
recursive=recursive)
+
+    filepaths = set(filepaths)
+
+    for filepath in filepaths:
+        yield _prepare_data_from_ds_or_file(filepath)
+
+
+def _is_data_already_in_orthanc(client: Orthanc, dicom_bytes: bytes) -> 
Tuple[bool, Union[Instance, None]]:
+    try:
+        dicom_file_like = BytesIO(dicom_bytes)
+        ds = pydicom.dcmread(dicom_file_like)
+        orthanc_id = to_orthanc_instance_id_from_ds(ds)
+    except InvalidDicomError:
+        # If the file is not a valid DICOM file, likely it is a zip file. It 
will be uploaded.
+        return False, None
+
+    try:
+        # Attempt to get metadata to verify if data is already in Orthanc.
+        client.get_instances_id_metadata(id_=orthanc_id)
+        return True, Instance(orthanc_id, client)
+
+    except httpx.HTTPError:
+        return False, None
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pyorthanc-1.20.0/pyorthanc/errors.py 
new/pyorthanc-1.22.1/pyorthanc/errors.py
--- old/pyorthanc-1.20.0/pyorthanc/errors.py    2025-01-16 20:46:34.433056800 
+0100
+++ new/pyorthanc-1.22.1/pyorthanc/errors.py    1970-01-01 01:00:00.000000000 
+0100
@@ -4,3 +4,7 @@
 
 class ModificationError(Exception):
     pass
+
+
+class NotInInternalEnvironmentError(Exception):
+    pass
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pyorthanc-1.20.0/pyorthanc/util.py 
new/pyorthanc-1.22.1/pyorthanc/util.py
--- old/pyorthanc-1.20.0/pyorthanc/util.py      2025-01-23 16:22:02.515639300 
+0100
+++ new/pyorthanc-1.22.1/pyorthanc/util.py      1970-01-01 01:00:00.000000000 
+0100
@@ -93,6 +93,15 @@
     return _make_orthanc_id(patient_id, study_uid, series_uid, instance_uid)
 
 
+def to_orthanc_instance_id_from_ds(ds: pydicom.Dataset) -> str:
+    return _make_orthanc_id(
+        patient_id=ds.PatientID,
+        study_uid=ds.StudyInstanceUID,
+        series_uid=ds.SeriesInstanceUID,
+        instance_uid=ds.SOPInstanceUID
+    )
+
+
 def _make_orthanc_id(patient_id: str, study_uid: str = None, series_uid: str = 
None, instance_uid: str = None) -> str:
     ids = [patient_id, study_uid, series_uid, instance_uid]
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/pyorthanc-1.20.0/pyproject.toml 
new/pyorthanc-1.22.1/pyproject.toml
--- old/pyorthanc-1.20.0/pyproject.toml 2025-03-28 15:08:18.057561400 +0100
+++ new/pyorthanc-1.22.1/pyproject.toml 1970-01-01 01:00:00.000000000 +0100
@@ -1,6 +1,6 @@
 [tool.poetry]
 name = "pyorthanc"
-version = "1.20.0"
+version = "1.22.1"
 description = "Orthanc REST API python wrapper with additional utilities"
 authors = [
     "Gabriel Couture <[email protected]>",
@@ -15,7 +15,7 @@
 [tool.poetry.dependencies]
 python = "^3.8"
 httpx = ">=0.24.1,<1.0.0"
-pydicom = "^2.3.0"
+pydicom = ">=2.3.0,<4.0.0"
 tqdm = { version = "^4.66.1", optional = true }
 
 [tool.poetry.extras]

Reply via email to