Skip to main content

API access for the Southern African Large elescope (SALT).

Project description

PyAstroSALT

PyAstroSALT is a wrapper around the RESTful API for observations with the South African Astronomical Observatory (SALT).

It can be used for

  • validating proposals
  • submitting proposals
  • making any API request

Installation

PyAstroSALT can be installed from PyPI:

pip install pyastrosalt

Validating proposal content

SALT proposal content is stored as zip files, whose content and structure are discussed in the next subsection.

The following assumes that you have a filre content.zip with the proposal content to validate in the working directory and that the proposal code is 2026-1-SCI-042.

You start by creating a session and authenticating with your SALT user credentials. For testing, you can use the playground instead of the production server:

from getpass import getpass
from pyastrosalt.session import Session

username = input("Your SALT username: ")
password = getpass("Your SALT password: ")
session = Session()
session.use_playground()
session.login(username=username, password=password)

You can now validate the proposal content:

from pyastrosalt.submission import validate

valid, errors = validate(session, "content.zip", "2026-1-SCI-042")
if valid:
    print("The proposal is valid.")
else:
    print("Validation failed with the following error(s):")
    for error in errors:
        print(f"- {error}")

Creating a zip file with proposal content

While in principle PyAstroSALT can be used to submit entire proposals, this is not the intended use really - use the PIPT instead. But if you wanted to submit whole proposals, here is a recipe:

  1. Create a proposal in the PIPT.
  2. Export the proposal as a zip file (using the menu option File | Export as Zip File...).
  3. Look at the exported file to see what the proposal content file would have to look like.

In this section we discuss the case of submitting one or several blocks for an existing proposal code, assuming that the corresponding targets do not exist in the proposal already.

The first step is to create a block in the PIPT which looks as similar as possible to the ones you are planning to submit later on. If you want to add a finder chart of your own, make sure you add or generate a finder chart to the block on its Acquisition tab.

Screenshot of the acquisition tab, with the finder chart section highlighted

Make sure the proposal with the block is valid (using the menu item Proposal | Validate). Then go to the Block tab and scroll to the bottom. Unselect the checkbox about an existing target and export the block as a template by clicking the respective button. (You may choose .txt or .xml as the file extension.)

Screenshot of the block tab, with the template export section highlighted

The content of the exported file looks like the following:

<Block>
    <Name>---INSERT NAME---</Name>  
    <BlockCode>a88af68c-2347-4274-9b4b-1e1a9da6d790</BlockCode>
    ... many more elements ...
</Block>

The following details need to be modified:

  • The various instances of the string ---INSERT NAME--- must be replaced with a meaningful name. Note that block names and target names must be unique within a proposal.
  • The content of the BlockCode element must be replaced with a completely random string.
  • The id attribute value of the Target element must be replaced with a completely random string.
  • The content of the TargetCode element must be replaced with a completely random string.
  • Update the other target details as required.

If you are including a finder chart, you also have to replace the content of the Path element with the string Included/<filename>, where <filename> is the filename of your finder chart.

Surround the resulting string with <Blocks> and </Blocks>, and save the result in a file Blocks.xml. (If you wanted to submit multiple blocks, the Blocks element would have a Block child for each of your blocks.)

Now create a directory Included. If you have a finder chart, move it into this directory.

Finally, create a zip file with Blocks.xml and Included. This is the file to submit.

The following example illustrates how you may create a file with proposal content with a Python script:

# Simple example of creating a zip file with block content.
#
# Assumptions:
# - There exists a file BlockTemplate.xml in the working directory, as exported from the
#   PIPT.
# - The target name in this file is Dummy Target.
# - There exists a finder chart called FinderChart.pdf in the working directory.
# - There exists no file content.zip in the working directory.
#
# You may have to tweak this example to fit your use case!

import dataclasses
import pathlib
import re
import zipfile
from typing import Literal
from uuid import uuid4


@dataclasses.dataclass()
class Target:
    name: str
    ra_hours: int
    ra_minutes: int
    ra_seconds: float
    dec_sign: Literal["+", "-"]
    dec_degrees: int
    dec_arcminutes: int
    dec_arcseconds: float
    min_magnitude: float
    max_magnitude: float


def main():
    """Create the content zip file."""
    # Collect the data for the new block.
    target = Target(
        name="Example Target 2",
        ra_hours=7,
        ra_minutes=8,
        ra_seconds=9.1,
        dec_sign="-",
        dec_degrees=-50,
        dec_arcminutes=6,
        dec_arcseconds=16.8,
        min_magnitude=18,
        max_magnitude=18,
    )
    finder_chart = pathlib.Path("FinderChart.pdf")
    finder_chart = None  # Comment this line out if there is a finder chart.

    # Get the template and make the necessary modifications.
    block_template = pathlib.Path("BlockTemplate.xml")
    block = block_template.read_text()
    block = modify_block(block, target, finder_chart)

    # Create the zip file to submit.
    blocks = f"<Blocks>\n{block}\n</Blocks>"
    content = pathlib.Path("content.zip")
    if content.exists():
        raise IOError(f"The output file ({content.absolute()}) exists already.")
    with zipfile.ZipFile(content, "w") as archive:
        archive.writestr("Blocks.xml", blocks)
        if finder_chart:
            archive.write(finder_chart, f"Included/{finder_chart.name}")


def modify_block(block: str, target: Target, finder_chart: pathlib.Path | None):
    """Update the block template."""
    block = block.replace(
        "<Name>---INSERT NAME---</Name>", f"<Name>{target.name}</Name>"
    )
    block = re.sub(
        r"<BlockCode>[^<]*</BlockCode>", f"<BlockCode>{uuid4()}</BlockCode>", block
    )
    block = re.sub(r'<Target id="[^"]*">', f'<Target id="{uuid4()}">', block)
    block = re.sub(
        r"<TargetCode>[^<]*</TargetCode>", f"<TargetCode>{uuid4()}</TargetCode>", block
    )
    block = block.replace("<Name>Dummy Target</Name>", f"<Name>{target.name}</Name>")
    if finder_chart:
        block = block.replace(
            "<Path>---INSERT ABSOLUTE FILE PATH OR auto-generated---</Path>",
            f"<Path>Included/{finder_chart.name}</Path>",
        )
    return block


if __name__ == "__main__":
    main()

Submitting a proposal

As in the previous section, the following example assumes that you have a filr content.zip with the proposal content to submit in the working directory and that the proposal code is 2026-1-SCI-042.

If you want to submit the proposal, you start by creating a session and authenticating. For testing, you can use the playground instead of the production server:

from getpass import getpass
from time import sleep

from pyastrosalt.session import Session

username = input("Your SALT username: ")
password = getpass("Your SALT password: ")
session = Session()
session.use_playground()
session.login(username=username, password=password)

You can now call the submit function. This function takes a zip file with the proposal content (as explained above) and the proposal code (or None if a competely new proposal is submitted), and it returns a Submission instance, which you can poll for the submission status, proposal code and (if applicable) submission error:

from time import sleep
from pyastrosalt.submission import submit, SubmissionStatus

submission = submit(
    session, "content.zip", "2026-1-SCI-042"
)
while submission.status == SubmissionStatus.IN_PROGRESS:
    print("Waiting for submission to finish...")
    sleep(10)
    
if submission.status == SubmissionStatus.SUCCESS:
    print(f"Success! The proposal code is {submission.proposal_code}.")
else:
    print(f"The validation failed with the following error:\n{submission.error}")

Using the SALT API

To make a request to the SALT API, you can create a session, authenticate and issue an HTTP request. Refer to the Requests documentation for details on the available request methods (get, post, put, patch, delete and request).

The following example shows how you can get details about your SALT propoals:

from getpass import getpass

from pyastrosalt.session import Session

# Get a session and authenticate
username = input("Your SALT username: ")
password = getpass("Your SALT password: ")
session = Session()
session.use_playground()
session.login(username=username, password=password)

# Make the API request
response = session.get("/proposals/")
if not response.ok:
    raise Exception("The API request failed.")

# Use the response from the API
proposals = response.json()
for proposal in proposals:
    print(f"{proposal['proposal_code']}: {proposal['title']}")

You can find the available API endpoints on the SALT API documentation page.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

pyastrosalt-0.2.2.tar.gz (18.4 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

pyastrosalt-0.2.2-py3-none-any.whl (14.2 kB view details)

Uploaded Python 3

File details

Details for the file pyastrosalt-0.2.2.tar.gz.

File metadata

  • Download URL: pyastrosalt-0.2.2.tar.gz
  • Upload date:
  • Size: 18.4 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.6 {"installer":{"name":"uv","version":"0.11.6","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for pyastrosalt-0.2.2.tar.gz
Algorithm Hash digest
SHA256 ae6debbc552f3592260fde1504e59d46ca56dd82400cd59e3a03f9a4b324e637
MD5 d1279cf5b1463a3512c3d67f02992bbd
BLAKE2b-256 8d1c27756fc0b4e058fc9f9d6b5246661a285c671e9e8008236a6fecb3e17220

See more details on using hashes here.

File details

Details for the file pyastrosalt-0.2.2-py3-none-any.whl.

File metadata

  • Download URL: pyastrosalt-0.2.2-py3-none-any.whl
  • Upload date:
  • Size: 14.2 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.6 {"installer":{"name":"uv","version":"0.11.6","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for pyastrosalt-0.2.2-py3-none-any.whl
Algorithm Hash digest
SHA256 7ed089864166b3fd1fe5c79936e2f8f3b547aa696c899bf86d701368a82590e7
MD5 5029a2d569498711514e9444e91d9805
BLAKE2b-256 689f469888b597ad0017f78228efef10017c9cd7c1f5077c2fa20c9805734dd0

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page