Skip to main content

Shared enums, email, geopoint, AIS track helpers

Project description

wr-common-lib

Shared enums, email helpers, geospatial coordinates, formatting utilities, and optional PostgreSQL write helpers.

PyPI wr-common-lib
import wr_common_lib

Requires Python 3.12+.

Install

pip install wr-common-lib
pip install "wr-common-lib[resend]"   # ResendProvider (httpx)
pip install "wr-common-lib[db]"       # EmailDbOper, AisDbOper (async-db-tools)
pip install "wr-common-lib[db,resend]"

Package layout

src/wr_common_lib/
├── __init__.py          # __version__
├── email/
│   ├── __init__.py
│   ├── constants.py     # MailFlow, MailStatus, VoyageStatus, PassVia, CustomerRole
│   ├── encoding.py      # file_to_base64, file_to_attachment
│   ├── provider.py      # ResendProvider, SendResult, ReceivedEmail
│   └── db_oper.py       # EmailDbOper, get_email_content_hash, …
├── geo/
│   ├── coordinates.py   # DMS parse/format, longitude continuity
│   └── geo_point.py     # GeoPoint
├── ais/
│   ├── models.py        # AisPoint
│   ├── thin.py          # thin_ais_points(内存回退)
│   └── postgres.py      # AisDbOper(vessel_ais 库内抽稀)
└── utils/
    ├── algorithm.py     # find_nearest_idx(有序序列最近邻)
    ├── convert.py       # as_uuid, round_precision, json/dict, …
    ├── formatters.py    # format_number, format_timestamp, format_timezone_offset
    └── time.py          # ensure_utc, iso_to_datetime, unix_interval_hours, …

Content hash

from wr_common_lib.email import get_email_content_hash

content_hash = get_email_content_hash(
    to="a@example.com,b@example.com",
    subject="...",
    content="...",
    cc="",
    attachments=[{"filename": "report.pdf"}],
)

Normalizes to / cc (comma-separated), strips subject/content, and hashes attachment filenames only (not file bytes).

Inbound parsing helpers

from wr_common_lib.email import extract_ship_candidates, extract_imo_from_content

extract_ship_candidates("MV OCEAN STAR - noon report")
extract_imo_from_content("IMO 9682930")   # "9682930" or None

Attachment encoding

Converts local files to Base64 for Resend attachments[].content.

from wr_common_lib.email import file_to_base64, file_to_attachment

b64 = file_to_base64("/path/to/report.pdf")

att = file_to_attachment("/path/to/report.pdf")
# {"filename": "report.pdf", "content": "<base64>", "content_type": "application/pdf"}

ResendProvider

Requires httpx ([resend] extra). Uses the Resend API.

from wr_common_lib.email import EmailProvider, ResendProvider, file_to_attachment

provider = ResendProvider(
    api_key="re_...",
    default_from="noreply@example.com",
    default_from_name="My App",
)

# Send (attachments from file_to_attachment)
result = await provider.send(
    to="user@example.com",
    cc="",
    subject="Hello",
    content="Plain text body",
    attachments=[file_to_attachment("/path/to/file.pdf")],
    mail_from=None,           # optional override
    email_id=email_id,        # optional, sets X-Email-ID header
)
# result.ok, result.message_id

# Inbound (Receiving API)
mail = await provider.received(message_id)
# mail.ok, mail.subject, mail.text, mail.html, mail.from_addr, mail.to, mail.cc, ...
body = mail.content   # text, else html

EmailDbOper

Requires the [db] extra. Pass an async-db-tools PostgresPool (or compatible pool with fetchval / execute).

from wr_common_lib.email import EmailDbOper, MailFlow, MailStatus, get_email_content_hash

db_oper = EmailDbOper(pool)

Write operations only; reads should use db_oper._db directly (e.g. fetchrow, fetchval).

insert_email

email_id, created = await db_oper.insert_email(
    task,
    MailFlow.OUTBOUND,
    MailStatus.PENDING,
)
# created is True when a new row was inserted, False when content_hash already existed

Task fields (inbound and outbound use the same shape):

Field Required Notes
imo yes
voyage_id yes UUID string
mail_from yes Sender
to yes Recipients (comma-separated allowed)
cc no Default ""
subject no Default ""
content no Default ""
attachments no List of dicts with filename
content_hash no Computed via get_email_content_hash when omitted
created_user_id no Outbound only

On duplicate content_hash, the insert is skipped (ON CONFLICT DO NOTHING); the existing row id is returned and created is False.

update_status / update_parsed_data

await db_oper.update_status(email_id, MailStatus.SENT)
await db_oper.update_parsed_data(email_id, {"parsed": "..."})

Geo

Decimal degrees, DMS parsing/formatting, and anti-meridian track continuity. No extra dependencies.

from wr_common_lib.geo import (
    GeoPoint,
    parse_longitude,
    parse_latitude,
    longitude_to_dms,
    make_longitude_continuous,
)

point = GeoPoint(lng=116.403874, lat=39.914883)
point = GeoPoint.from_string("116°24′14E", "39°54′53N")

point.lng_dms.formatted   # e.g. 116°24′14E
point.to_dict()           # {"lng": ..., "lat": ...}

track = make_longitude_continuous([{"lon": 170, "lat": 30}, {"lon": -170, "lat": 31}])

AIS

读取 vessel_ais 轨迹(可选 PostgreSQL 库内时间桶抽稀)。需 wr-common-lib[db]

from datetime import UTC, datetime

from wr_common_lib.ais import AisDbOper, AisPoint, ais_point_from_row

# 领域模型(与 vessel_ais 行对应)
point = AisPoint(
    imo=9123456,
    tz=datetime(2025, 1, 1, 12, 0, tzinfo=UTC),
    lon=120.0,
    lat=30.0,
    updated_at=int(datetime(2025, 1, 2, 8, 15, 0, tzinfo=UTC).timestamp()),
)
point.position_time   # Unix 秒,由 tz 推导
point.updated_at      # Unix 秒,由 DB updated_at 推导

# 查询行 → AisPoint(updated_at timestamptz → Unix 秒)
point = ais_point_from_row(row_dict)

# PostgreSQL
oper = AisDbOper(db)
track = await oper.fetch_track(imo, start_tz, end_tz, thin_minutes=10)
字段 说明
tz AIS 定位时间(timestamptz,UTC)
updated_at 数据更新时间,UTC Unix 秒(int
position_time 属性,tz 的 Unix 秒

Utils

from wr_common_lib.utils import (
    coerce_to_datetime,
    ensure_utc,
    find_nearest_idx,
    format_number,
    format_timestamp,
    format_timezone_offset,
    iso_to_datetime,
    norm_meteo_source,
    round_precision,
    to_unix_seconds,
    unix_interval_hours,
)

format_number(3.1415, integer_digits=1, decimal_digits=2)  # "3.14"
format_timezone_offset(8.5)                                  # "+08:30"
format_timestamp(1710000000)                                 # ISO UTC string
ensure_utc(datetime.now())                                   # UTC aware

find_nearest_idx([10, 20, 30], 20)   # [1] 精确匹配
find_nearest_idx([10, 20, 30], 15)   # [0, 1] 左右邻居
find_nearest_idx([10, 20, 30], 5)    # [0] 小于最小值
find_nearest_idx([10, 20, 30], 35)   # [2] 大于最大值

Dependencies

install brings in
wr-common-lib enums, geo, utils, encoding, hash/parsing helpers, ResendProvider (needs httpx — use [resend])
wr-common-lib[db] async-db-tools, EmailDbOper, get_imo_and_voyage_id
wr-common-lib[resend] httpx

Your application owns API keys, database URL, and pool lifecycle.

Publish

pip install -e ".[db,resend]"

# 1. Update CHANGELOG.md for the new version
# 2. Bump version and publish
uv version 0.1.15
export UV_PUBLISH_TOKEN=pypi-...
uv build && uv publish

git tag v0.1.15 && git push --tags

PyPI does not allow re-uploading the same version.

License

MIT — see LICENSE.

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

wr_common_lib-0.1.15.tar.gz (18.0 kB view details)

Uploaded Source

Built Distribution

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

wr_common_lib-0.1.15-py3-none-any.whl (22.9 kB view details)

Uploaded Python 3

File details

Details for the file wr_common_lib-0.1.15.tar.gz.

File metadata

  • Download URL: wr_common_lib-0.1.15.tar.gz
  • Upload date:
  • Size: 18.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.2 {"installer":{"name":"uv","version":"0.11.2","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 wr_common_lib-0.1.15.tar.gz
Algorithm Hash digest
SHA256 a3e8d70ac4816189237db2bf5fba9d16df370cda15878b236803f09dac453aaa
MD5 381a9943bf86f202703404de47e3a9ee
BLAKE2b-256 58922ca8da373477b1a2ad7987166838eb2807151b5de6be6feee3bb1879412c

See more details on using hashes here.

File details

Details for the file wr_common_lib-0.1.15-py3-none-any.whl.

File metadata

  • Download URL: wr_common_lib-0.1.15-py3-none-any.whl
  • Upload date:
  • Size: 22.9 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.2 {"installer":{"name":"uv","version":"0.11.2","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 wr_common_lib-0.1.15-py3-none-any.whl
Algorithm Hash digest
SHA256 36bae361ebfa1c62ae3aaf53b4ae5ef1e4390d7d20fe76a8adfa1130fa8e3151
MD5 6519d71df73cf899db787ea07b9655cc
BLAKE2b-256 0afc1c2233dd21a8c9b8997733ea921c14979eb9db543bf99a83ce528cbff76c

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