From fb00505696e8dc8c3ccb6508e676c03f066f1bb1 Mon Sep 17 00:00:00 2001 From: Jan Bolting Date: Mon, 2 Feb 2026 13:43:14 +0100 Subject: [PATCH 1/4] handle running as a package + indicate whether working tree == HEAD --- src/prx/main.py | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/src/prx/main.py b/src/prx/main.py index 7a682e29..404aa9a2 100644 --- a/src/prx/main.py +++ b/src/prx/main.py @@ -144,9 +144,15 @@ def build_metadata(input_files): } for file in files ] - prx_metadata["prx_git_commit_id"] = git.Repo( - path=Path(__file__).parent, search_parent_directories=True - ).head.object.hexsha + try: + repo = prx_metadata["prx_git_commit_id"] = git.Repo( + path=Path(__file__).parents[2], search_parent_directories=False + ) + prx_metadata["prx_git_commit_id"] = ( + f"{repo.head.object.hexsha}_{'dirty' if repo.is_dirty() else ''}" + ) + except git.exc.InvalidGitRepositoryError: + prx_metadata["prx_git_commit_id"] = "not_a_git_repository" return prx_metadata From a01fcfb57daf4dcb42828fceb09b3845fdee6204 Mon Sep 17 00:00:00 2001 From: Jan Bolting Date: Mon, 2 Feb 2026 13:44:51 +0100 Subject: [PATCH 2/4] fix --- src/prx/main.py | 48 ++++++++++++++++++++++++------------------------ 1 file changed, 24 insertions(+), 24 deletions(-) diff --git a/src/prx/main.py b/src/prx/main.py index 404aa9a2..6bf66bf9 100644 --- a/src/prx/main.py +++ b/src/prx/main.py @@ -22,9 +22,9 @@ @prx.util.timeit def write_prx_file( - prx_header: dict, - prx_records: pd.DataFrame, - file_name_without_extension: Path, + prx_header: dict, + prx_records: pd.DataFrame, + file_name_without_extension: Path, ): output_file = Path(f"{str(file_name_without_extension)}.csv") prx_records["sat_elevation_deg"] = np.rad2deg(prx_records.elevation_rad.to_numpy()) @@ -43,7 +43,7 @@ def write_prx_file( obs = prx_records.loc[ (prx_records.observation_type.str.startswith(obs_type)) & (prx_records.observation_type.str.len() == 3) - ][ + ][ [ "satellite", "time_of_reception_in_receiver_time", @@ -149,7 +149,7 @@ def build_metadata(input_files): path=Path(__file__).parents[2], search_parent_directories=False ) prx_metadata["prx_git_commit_id"] = ( - f"{repo.head.object.hexsha}_{'dirty' if repo.is_dirty() else ''}" + f"{repo.head.object.hexsha}{'_dirty' if repo.is_dirty() else ''}" ) except git.exc.InvalidGitRepositoryError: prx_metadata["prx_git_commit_id"] = "not_a_git_repository" @@ -157,7 +157,7 @@ def build_metadata(input_files): def check_assumptions( - rinex_3_obs_file, + rinex_3_obs_file, ): obs_header = georinex.rinexheader(rinex_3_obs_file) if "RCV CLOCK OFFS APPL" in obs_header.keys(): @@ -185,11 +185,11 @@ def warm_up_parser_cache(rinex_files): @prx.util.timeit def build_records_levels_12( - rinex_3_obs_file, - rinex_3_ephemerides_files, - approximate_receiver_ecef_position_m, - prx_level, - model_tropo, + rinex_3_obs_file, + rinex_3_ephemerides_files, + approximate_receiver_ecef_position_m, + prx_level, + model_tropo, ): """ Creates a flat_obs dataframe including columns for prx processing levels 1 and 2. @@ -253,7 +253,7 @@ def build_records_levels_12( # As error terms are tens of nanoseconds here, and the receiver clock is integer-second aligned to GPST, we # already have times-of-emission that are integer-second aligned GPST here. per_sat["time_of_emission_isagpst"] = ( - per_sat["time_of_reception_in_receiver_time"] - tof_dtrx + per_sat["time_of_reception_in_receiver_time"] - tof_dtrx ) flat_obs = flat_obs.merge( @@ -285,14 +285,14 @@ def build_records_levels_12( doy = int(file.name[16:19]) day_query = query.loc[ ( - query.query_time_isagpst - >= pd.Timestamp(year=year, month=1, day=1) + pd.Timedelta(days=doy - 1) + query.query_time_isagpst + >= pd.Timestamp(year=year, month=1, day=1) + pd.Timedelta(days=doy - 1) ) & ( - query.query_time_isagpst - < pd.Timestamp(year=year, month=1, day=1) + pd.Timedelta(days=doy) + query.query_time_isagpst + < pd.Timestamp(year=year, month=1, day=1) + pd.Timedelta(days=doy) ) - ] + ] if day_query.empty: continue @@ -354,7 +354,7 @@ def build_records_levels_12( glo_cdma = flat_obs[ (flat_obs.satellite.str[0] == "R") & (flat_obs["observation_type"].str[1].astype(int) > 2) - ] + ] flat_obs.loc[glo_cdma.index, "frequency_slot"] = int(1) def assign_carrier_frequencies(flat_obs): @@ -363,11 +363,11 @@ def assign_carrier_frequencies(flat_obs): )[0] assignable = flat_obs.frequency_slot.notna() keys = ( - flat_obs.satellite[assignable].str[0] - + "_L" - + flat_obs["observation_type"][assignable].str[1] - + "_" - + flat_obs.frequency_slot[assignable].astype(int).astype(str) + flat_obs.satellite[assignable].str[0] + + "_L" + + flat_obs["observation_type"][assignable].str[1] + + "_" + + flat_obs.frequency_slot[assignable].astype(int).astype(str) ) flat_obs.loc[:, "carrier_frequency_hz"] = keys.map(freq_dict) return flat_obs @@ -430,7 +430,7 @@ def process(observation_file_path: Path, prx_level=2, model_tropo="saastamoinen" parser = argparse.ArgumentParser( prog="prx", description="prx processes RINEX observations, computes a few useful things such as satellite position, " - "relativistic effects etc. and outputs everything to a text file in a convenient format.", + "relativistic effects etc. and outputs everything to a text file in a convenient format.", epilog="P.S. GNSS rules!", ) parser.add_argument( From 5eee71df36b39f6920526659cfdfb48998fa714d Mon Sep 17 00:00:00 2001 From: Jan Bolting Date: Mon, 2 Feb 2026 13:45:07 +0100 Subject: [PATCH 3/4] format --- src/prx/main.py | 46 +++++++++++++++++++++++----------------------- 1 file changed, 23 insertions(+), 23 deletions(-) diff --git a/src/prx/main.py b/src/prx/main.py index 6bf66bf9..14514d06 100644 --- a/src/prx/main.py +++ b/src/prx/main.py @@ -22,9 +22,9 @@ @prx.util.timeit def write_prx_file( - prx_header: dict, - prx_records: pd.DataFrame, - file_name_without_extension: Path, + prx_header: dict, + prx_records: pd.DataFrame, + file_name_without_extension: Path, ): output_file = Path(f"{str(file_name_without_extension)}.csv") prx_records["sat_elevation_deg"] = np.rad2deg(prx_records.elevation_rad.to_numpy()) @@ -43,7 +43,7 @@ def write_prx_file( obs = prx_records.loc[ (prx_records.observation_type.str.startswith(obs_type)) & (prx_records.observation_type.str.len() == 3) - ][ + ][ [ "satellite", "time_of_reception_in_receiver_time", @@ -157,7 +157,7 @@ def build_metadata(input_files): def check_assumptions( - rinex_3_obs_file, + rinex_3_obs_file, ): obs_header = georinex.rinexheader(rinex_3_obs_file) if "RCV CLOCK OFFS APPL" in obs_header.keys(): @@ -185,11 +185,11 @@ def warm_up_parser_cache(rinex_files): @prx.util.timeit def build_records_levels_12( - rinex_3_obs_file, - rinex_3_ephemerides_files, - approximate_receiver_ecef_position_m, - prx_level, - model_tropo, + rinex_3_obs_file, + rinex_3_ephemerides_files, + approximate_receiver_ecef_position_m, + prx_level, + model_tropo, ): """ Creates a flat_obs dataframe including columns for prx processing levels 1 and 2. @@ -253,7 +253,7 @@ def build_records_levels_12( # As error terms are tens of nanoseconds here, and the receiver clock is integer-second aligned to GPST, we # already have times-of-emission that are integer-second aligned GPST here. per_sat["time_of_emission_isagpst"] = ( - per_sat["time_of_reception_in_receiver_time"] - tof_dtrx + per_sat["time_of_reception_in_receiver_time"] - tof_dtrx ) flat_obs = flat_obs.merge( @@ -285,14 +285,14 @@ def build_records_levels_12( doy = int(file.name[16:19]) day_query = query.loc[ ( - query.query_time_isagpst - >= pd.Timestamp(year=year, month=1, day=1) + pd.Timedelta(days=doy - 1) + query.query_time_isagpst + >= pd.Timestamp(year=year, month=1, day=1) + pd.Timedelta(days=doy - 1) ) & ( - query.query_time_isagpst - < pd.Timestamp(year=year, month=1, day=1) + pd.Timedelta(days=doy) + query.query_time_isagpst + < pd.Timestamp(year=year, month=1, day=1) + pd.Timedelta(days=doy) ) - ] + ] if day_query.empty: continue @@ -354,7 +354,7 @@ def build_records_levels_12( glo_cdma = flat_obs[ (flat_obs.satellite.str[0] == "R") & (flat_obs["observation_type"].str[1].astype(int) > 2) - ] + ] flat_obs.loc[glo_cdma.index, "frequency_slot"] = int(1) def assign_carrier_frequencies(flat_obs): @@ -363,11 +363,11 @@ def assign_carrier_frequencies(flat_obs): )[0] assignable = flat_obs.frequency_slot.notna() keys = ( - flat_obs.satellite[assignable].str[0] - + "_L" - + flat_obs["observation_type"][assignable].str[1] - + "_" - + flat_obs.frequency_slot[assignable].astype(int).astype(str) + flat_obs.satellite[assignable].str[0] + + "_L" + + flat_obs["observation_type"][assignable].str[1] + + "_" + + flat_obs.frequency_slot[assignable].astype(int).astype(str) ) flat_obs.loc[:, "carrier_frequency_hz"] = keys.map(freq_dict) return flat_obs @@ -430,7 +430,7 @@ def process(observation_file_path: Path, prx_level=2, model_tropo="saastamoinen" parser = argparse.ArgumentParser( prog="prx", description="prx processes RINEX observations, computes a few useful things such as satellite position, " - "relativistic effects etc. and outputs everything to a text file in a convenient format.", + "relativistic effects etc. and outputs everything to a text file in a convenient format.", epilog="P.S. GNSS rules!", ) parser.add_argument( From 40f10e8d7ddfc48abe7b7b4251c516cbe60a5f9d Mon Sep 17 00:00:00 2001 From: Paul Thevenon Date: Mon, 2 Feb 2026 21:28:07 +0100 Subject: [PATCH 4/4] - get commit ID when prx is installed as a dependency --- src/prx/main.py | 19 +++++++++---------- src/prx/util.py | 39 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 10 deletions(-) diff --git a/src/prx/main.py b/src/prx/main.py index 14514d06..eae0991c 100644 --- a/src/prx/main.py +++ b/src/prx/main.py @@ -7,8 +7,8 @@ import pandas as pd import numpy as np import git -import prx.util -from prx import atmospheric_corrections as atmo, util +import prx.util as util +from prx import atmospheric_corrections as atmo from prx.constants import carrier_frequencies_hz from prx.rinex_obs.parser import parse_rinex_obs_file from prx.util import is_rinex_3_obs_file, is_rinex_3_nav_file @@ -20,7 +20,7 @@ log = util.get_logger(__name__) -@prx.util.timeit +@util.timeit def write_prx_file( prx_header: dict, prx_records: pd.DataFrame, @@ -145,11 +145,10 @@ def build_metadata(input_files): for file in files ] try: - repo = prx_metadata["prx_git_commit_id"] = git.Repo( - path=Path(__file__).parents[2], search_parent_directories=False - ) prx_metadata["prx_git_commit_id"] = ( - f"{repo.head.object.hexsha}{'_dirty' if repo.is_dirty() else ''}" + util.git_sha_of_this_package() + or util.git_sha_from_dist_info("prx") + or "unknown" ) except git.exc.InvalidGitRepositoryError: prx_metadata["prx_git_commit_id"] = "not_a_git_repository" @@ -183,7 +182,7 @@ def warm_up_parser_cache(rinex_files): _ = [parse_rinex_nav_or_obs_file(file) for file in rinex_files] -@prx.util.timeit +@util.timeit def build_records_levels_12( rinex_3_obs_file, rinex_3_ephemerides_files, @@ -384,7 +383,7 @@ def assign_carrier_frequencies(flat_obs): return flat_obs -@prx.util.timeit +@util.timeit def process(observation_file_path: Path, prx_level=2, model_tropo="saastamoinen"): t0 = pd.Timestamp.now() # We expect a Path, but might get a string here: @@ -393,7 +392,7 @@ def process(observation_file_path: Path, prx_level=2, model_tropo="saastamoinen" f"Starting processing {observation_file_path.name} (full path {observation_file_path})" ) rinex_3_obs_file = converters.anything_to_rinex_3(observation_file_path) - rinex_3_obs_file = prx.util.try_repair_with_gfzrnx(rinex_3_obs_file) + rinex_3_obs_file = util.try_repair_with_gfzrnx(rinex_3_obs_file) prx_file = rinex_3_obs_file.with_suffix("") match prx_level: case 1 | 2: diff --git a/src/prx/util.py b/src/prx/util.py index a4a4fe75..37be1d60 100644 --- a/src/prx/util.py +++ b/src/prx/util.py @@ -1,3 +1,4 @@ +import json import logging import math import os @@ -6,6 +7,8 @@ import subprocess from functools import wraps from pathlib import Path +import importlib.metadata as md +import git import georinex import joblib @@ -482,3 +485,39 @@ def compute_gps_utc_leap_seconds(yyyy: int, doy: int): break assert ~np.isnan(ls), "GPS leap second could not be retrieved" return ls + + +def git_sha_of_this_package() -> str | None: + if (Path(__file__).parent.resolve() / ".git").exists(): + repo = git.Repo(path=Path(__file__).parent) + git_commit_id = ( + f"{repo.head.object.hexsha}{'_dirty' if repo.is_dirty() else ''}" + ) + return git_commit_id + return None # not a git checkout (e.g., installed wheel) + + +def git_sha_from_dist_info(dist_name: str) -> str | None: + try: + dist = md.distribution(dist_name) + except md.PackageNotFoundError: + return None + + # Look for the PEP 610 file + direct_url = Path(dist.locate_file("direct_url.json")) + if not direct_url.exists(): + # some installers nest it inside the dist-info directory + dist_info_dir = Path(dist._path) if hasattr(dist, "_path") else None + if dist_info_dir: + direct_url = dist_info_dir / "direct_url.json" + if not direct_url.exists(): + return None + + try: + data = json.loads(direct_url.read_text(encoding="utf-8")) + # Expected shape for VCS installs: + # {"url": "...", "vcs_info": {"vcs": "git", "requested_revision": "...", "commit_id": "..."}} + vcs_info = data.get("vcs_info") or {} + return vcs_info.get("commit_id") or vcs_info.get("requested_revision") + except Exception: + return None