lsst-felis 24.1.6rc1__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of lsst-felis might be problematic. Click here for more details.

@@ -0,0 +1,134 @@
1
+ """Provides a temporary Postgresql instance for testing."""
2
+
3
+ # This file is part of felis.
4
+ #
5
+ # Developed for the LSST Data Management System.
6
+ # This product includes software developed by the LSST Project
7
+ # (https://www.lsst.org).
8
+ # See the COPYRIGHT file at the top-level directory of this distribution
9
+ # for details of code ownership.
10
+ #
11
+ # This program is free software: you can redistribute it and/or modify
12
+ # it under the terms of the GNU General Public License as published by
13
+ # the Free Software Foundation, either version 3 of the License, or
14
+ # (at your option) any later version.
15
+ #
16
+ # This program is distributed in the hope that it will be useful,
17
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
18
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19
+ # GNU General Public License for more details.
20
+ #
21
+ # You should have received a copy of the GNU General Public License
22
+ # along with this program. If not, see <https://www.gnu.org/licenses/>.
23
+
24
+ import gc
25
+ import unittest
26
+ from collections.abc import Iterator
27
+ from contextlib import contextmanager
28
+
29
+ from sqlalchemy import text
30
+ from sqlalchemy.engine import Connection, Engine, create_engine
31
+
32
+ try:
33
+ from testing.postgresql import Postgresql # type: ignore
34
+ except ImportError:
35
+ Postgresql = None
36
+
37
+ __all__ = ["TemporaryPostgresInstance", "setup_postgres_test_db"]
38
+
39
+
40
+ class TemporaryPostgresInstance:
41
+ """Wrapper for a temporary Postgres database.
42
+
43
+ Parameters
44
+ ----------
45
+ server
46
+ The ``testing.postgresql.Postgresql`` instance.
47
+ engine
48
+ The SQLAlchemy engine for the temporary database server.
49
+
50
+ Notes
51
+ -----
52
+ This class was copied and modified from
53
+ ``lsst.daf.butler.tests.postgresql``.
54
+ """
55
+
56
+ def __init__(self, server: Postgresql, engine: Engine) -> None:
57
+ """Initialize the temporary Postgres database instance."""
58
+ self._server = server
59
+ self._engine = engine
60
+
61
+ @property
62
+ def url(self) -> str:
63
+ """Return connection URL for the temporary database server.
64
+
65
+ Returns
66
+ -------
67
+ str
68
+ The connection URL.
69
+ """
70
+ return self._server.url()
71
+
72
+ @property
73
+ def engine(self) -> Engine:
74
+ """Return the SQLAlchemy engine for the temporary database server.
75
+
76
+ Returns
77
+ -------
78
+ `~sqlalchemy.engine.Engine`
79
+ The SQLAlchemy engine.
80
+ """
81
+ return self._engine
82
+
83
+ @contextmanager
84
+ def begin(self) -> Iterator[Connection]:
85
+ """Return a SQLAlchemy connection to the test database.
86
+
87
+ Returns
88
+ -------
89
+ `~sqlalchemy.engine.Connection`
90
+ The SQLAlchemy connection.
91
+ """
92
+ with self._engine.begin() as connection:
93
+ yield connection
94
+
95
+ def print_info(self) -> None:
96
+ """Print information about the temporary database server."""
97
+ print("\n\n---- PostgreSQL URL ----")
98
+ print(self.url)
99
+ self._engine = create_engine(self.url)
100
+ with self.begin() as conn:
101
+ print("\n---- PostgreSQL Version ----")
102
+ res = conn.execute(text("SELECT version()")).fetchone()
103
+ if res:
104
+ print(res[0])
105
+ print("\n")
106
+
107
+
108
+ @contextmanager
109
+ def setup_postgres_test_db() -> Iterator[TemporaryPostgresInstance]:
110
+ """Set up a temporary Postgres database instance that can be used for
111
+ testing.
112
+
113
+ Returns
114
+ -------
115
+ TemporaryPostgresInstance
116
+ The temporary Postgres database instance.
117
+
118
+ Raises
119
+ ------
120
+ unittest.SkipTest
121
+ Raised if the ``testing.postgresql`` module is not available.
122
+ """
123
+ if Postgresql is None:
124
+ raise unittest.SkipTest("testing.postgresql module not available.")
125
+
126
+ with Postgresql() as server:
127
+ engine = create_engine(server.url())
128
+ instance = TemporaryPostgresInstance(server, engine)
129
+ yield instance
130
+
131
+ # Clean up any lingering SQLAlchemy engines/connections
132
+ # so they're closed before we shut down the server.
133
+ gc.collect()
134
+ engine.dispose()
felis/tests/utils.py ADDED
@@ -0,0 +1,122 @@
1
+ """Test utility functions."""
2
+
3
+ # This file is part of felis.
4
+ #
5
+ # Developed for the LSST Data Management System.
6
+ # This product includes software developed by the LSST Project
7
+ # (https://www.lsst.org).
8
+ # See the COPYRIGHT file at the top-level directory of this distribution
9
+ # for details of code ownership.
10
+ #
11
+ # This program is free software: you can redistribute it and/or modify
12
+ # it under the terms of the GNU General Public License as published by
13
+ # the Free Software Foundation, either version 3 of the License, or
14
+ # (at your option) any later version.
15
+ #
16
+ # This program is distributed in the hope that it will be useful,
17
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
18
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19
+ # GNU General Public License for more details.
20
+ #
21
+ # You should have received a copy of the GNU General Public License
22
+ # along with this program. If not, see <https://www.gnu.org/licenses/>.
23
+
24
+ import logging
25
+ import os
26
+ import shutil
27
+ import tempfile
28
+ from collections.abc import Iterator
29
+ from contextlib import contextmanager
30
+ from typing import IO
31
+
32
+ __all__ = ["open_test_file", "mk_temp_dir", "rm_temp_dir"]
33
+
34
+ TEST_DATA_DIR = os.path.normpath(os.path.join(os.path.dirname(__file__), "..", "..", "..", "tests", "data"))
35
+ """The directory containing test data files."""
36
+
37
+ TEST_TMP_DIR = os.path.normpath(os.path.join(TEST_DATA_DIR, ".."))
38
+ """The directory for temporary files."""
39
+
40
+ __all__ = ["open_test_file", "mk_temp_dir", "rm_temp_dir"]
41
+
42
+ logger = logging.getLogger(__name__)
43
+
44
+
45
+ def get_test_file_path(file_name: str) -> str:
46
+ """Return the path to a test file.
47
+
48
+ Parameters
49
+ ----------
50
+ file_name
51
+ The name of the test file.
52
+
53
+ Returns
54
+ -------
55
+ str
56
+ The path to the test file.
57
+
58
+ Raises
59
+ ------
60
+ FileNotFoundError
61
+ Raised if the file does not exist.
62
+ """
63
+ file_path = os.path.join(TEST_DATA_DIR, file_name)
64
+ if not os.path.exists(file_path):
65
+ raise FileNotFoundError(file_path)
66
+ return file_path
67
+
68
+
69
+ @contextmanager
70
+ def open_test_file(file_name: str) -> Iterator[IO[str]]:
71
+ """Return a file object for a test file using a context manager.
72
+
73
+ Parameters
74
+ ----------
75
+ file_name
76
+ The name of the test file.
77
+
78
+ Returns
79
+ -------
80
+ `Iterator` [ `IO` [ `str` ] ]
81
+ A file object for the test file.
82
+
83
+ Raises
84
+ ------
85
+ FileNotFoundError
86
+ Raised if the file does not exist.
87
+ """
88
+ logger.debug("Opening test file: %s", file_name)
89
+ file_path = get_test_file_path(file_name)
90
+ file = open(file_path)
91
+ try:
92
+ yield file
93
+ finally:
94
+ file.close()
95
+
96
+
97
+ def mk_temp_dir(parent_dir: str = TEST_TMP_DIR) -> str:
98
+ """Create a temporary directory for testing.
99
+
100
+ Parameters
101
+ ----------
102
+ parent_dir
103
+ The parent directory for the temporary directory.
104
+
105
+ Returns
106
+ -------
107
+ str
108
+ The path to the temporary directory.
109
+ """
110
+ return tempfile.mkdtemp(dir=parent_dir)
111
+
112
+
113
+ def rm_temp_dir(temp_dir: str) -> None:
114
+ """Remove a temporary directory.
115
+
116
+ Parameters
117
+ ----------
118
+ temp_dir
119
+ The path to the temporary directory.
120
+ """
121
+ logger.debug("Removing temporary directory: %s", temp_dir)
122
+ shutil.rmtree(temp_dir, ignore_errors=True)
felis/types.py ADDED
@@ -0,0 +1,185 @@
1
+ """Define the supported Felis datatypes."""
2
+
3
+ # This file is part of felis.
4
+ #
5
+ # Developed for the LSST Data Management System.
6
+ # This product includes software developed by the LSST Project
7
+ # (https://www.lsst.org).
8
+ # See the COPYRIGHT file at the top-level directory of this distribution
9
+ # for details of code ownership.
10
+ #
11
+ # This program is free software: you can redistribute it and/or modify
12
+ # it under the terms of the GNU General Public License as published by
13
+ # the Free Software Foundation, either version 3 of the License, or
14
+ # (at your option) any later version.
15
+ #
16
+ # This program is distributed in the hope that it will be useful,
17
+ # but WITHOUT ANY WARRANTY; without even the implied warranty of
18
+ # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19
+ # GNU General Public License for more details.
20
+ #
21
+ # You should have received a copy of the GNU General Public License
22
+ # along with this program. If not, see <https://www.gnu.org/licenses/>.
23
+
24
+ from __future__ import annotations
25
+
26
+ from typing import Any
27
+
28
+ __all__ = [
29
+ "FelisType",
30
+ "Boolean",
31
+ "Byte",
32
+ "Short",
33
+ "Int",
34
+ "Long",
35
+ "Float",
36
+ "Double",
37
+ "Char",
38
+ "String",
39
+ "Unicode",
40
+ "Text",
41
+ "Binary",
42
+ "Timestamp",
43
+ ]
44
+
45
+
46
+ class FelisType:
47
+ """Base class for a representation of Felis column types.
48
+
49
+ Notes
50
+ -----
51
+ This class plays a role of a metaclass without being an actual metaclass.
52
+ It provides a method to retrieve a class (type) given Felis type name.
53
+ There should be no instances of this class (or sub-classes), the utility
54
+ of the class hierarchy is in the type system itself.
55
+ """
56
+
57
+ felis_name: str
58
+ """Name of the type as defined in the Felis schema."""
59
+
60
+ votable_name: str
61
+ """Name of the type as defined in VOTable."""
62
+
63
+ is_numeric: bool
64
+ """Flag indicating if the type is numeric."""
65
+
66
+ is_sized: bool
67
+ """Flag indicating if the type is sized, meaning it requires a length."""
68
+
69
+ is_timestamp: bool
70
+ """Flag indicating if the type is a timestamp."""
71
+
72
+ _types: dict[str, type[FelisType]] = {}
73
+ """Dictionary of all known Felis types."""
74
+
75
+ @classmethod
76
+ def __init_subclass__(
77
+ cls,
78
+ /,
79
+ felis_name: str,
80
+ votable_name: str,
81
+ is_numeric: bool = False,
82
+ is_sized: bool = False,
83
+ is_timestamp: bool = False,
84
+ **kwargs: Any,
85
+ ):
86
+ """Register a new Felis type.
87
+
88
+ Parameters
89
+ ----------
90
+ felis_name
91
+ Name of the type.
92
+ votable_name
93
+ Name of the type as defined in VOTable.
94
+ is_numeric
95
+ Flag indicating if the type is numeric.
96
+ is_sized
97
+ Flag indicating if the type is sized.
98
+ is_timestamp
99
+ Flag indicating if the type is a timestamp.
100
+ kwargs
101
+ Additional keyword arguments.
102
+ """
103
+ super().__init_subclass__(**kwargs)
104
+ cls.felis_name = felis_name
105
+ cls.votable_name = votable_name
106
+ cls.is_numeric = is_numeric
107
+ cls.is_sized = is_sized
108
+ cls.is_timestamp = is_timestamp
109
+ cls._types[felis_name] = cls
110
+
111
+ @classmethod
112
+ def felis_type(cls, felis_name: str) -> type[FelisType]:
113
+ """Return specific Felis type for a given name.
114
+
115
+ Parameters
116
+ ----------
117
+ felis_name
118
+ Name of the felis type as defined in felis schema.
119
+
120
+ Returns
121
+ -------
122
+ `type` [ `FelisType` ]
123
+ A specific Felis type class.
124
+
125
+ Raises
126
+ ------
127
+ TypeError
128
+ Raised if ``felis_name`` does not correspond to a known type.
129
+ """
130
+ try:
131
+ return cls._types[felis_name]
132
+ except KeyError:
133
+ raise TypeError(f"Unknown felis type {felis_name!r}") from None
134
+
135
+
136
+ class Boolean(FelisType, felis_name="boolean", votable_name="boolean", is_numeric=False):
137
+ """Felis definition of boolean type."""
138
+
139
+
140
+ class Byte(FelisType, felis_name="byte", votable_name="unsignedByte", is_numeric=True):
141
+ """Felis definition of byte type."""
142
+
143
+
144
+ class Short(FelisType, felis_name="short", votable_name="short", is_numeric=True):
145
+ """Felis definition of short integer type."""
146
+
147
+
148
+ class Int(FelisType, felis_name="int", votable_name="int", is_numeric=True):
149
+ """Felis definition of integer type."""
150
+
151
+
152
+ class Long(FelisType, felis_name="long", votable_name="long", is_numeric=True):
153
+ """Felis definition of long integer type."""
154
+
155
+
156
+ class Float(FelisType, felis_name="float", votable_name="float", is_numeric=True):
157
+ """Felis definition of single precision floating point type."""
158
+
159
+
160
+ class Double(FelisType, felis_name="double", votable_name="double", is_numeric=True):
161
+ """Felis definition of double precision floating point type."""
162
+
163
+
164
+ class Char(FelisType, felis_name="char", votable_name="char", is_sized=True):
165
+ """Felis definition of character type."""
166
+
167
+
168
+ class String(FelisType, felis_name="string", votable_name="char", is_sized=True):
169
+ """Felis definition of string type."""
170
+
171
+
172
+ class Unicode(FelisType, felis_name="unicode", votable_name="unicodeChar", is_sized=True):
173
+ """Felis definition of unicode string type."""
174
+
175
+
176
+ class Text(FelisType, felis_name="text", votable_name="char"):
177
+ """Felis definition of text type."""
178
+
179
+
180
+ class Binary(FelisType, felis_name="binary", votable_name="unsignedByte", is_sized=True):
181
+ """Felis definition of binary type."""
182
+
183
+
184
+ class Timestamp(FelisType, felis_name="timestamp", votable_name="char", is_timestamp=True):
185
+ """Felis definition of timestamp type."""
felis/version.py ADDED
@@ -0,0 +1,2 @@
1
+ __all__ = ["__version__"]
2
+ __version__ = "24.1.6rc1"
@@ -0,0 +1 @@
1
+ Copyright 2018-2024 The Board of Trustees of the Leland Stanford Junior University, through SLAC National Accelerator Laboratory