glitchr / omnilab
Omnilab: one contract for the public science-data APIs - entities (a compound, a gene, a protein, a species, a star, an earthquake) with their properties, units and cross-references, series (a variable measured at a place over time: weather, climate, air) and catalogued products (a satellite scene,
Requires
- php: >=8.2
- symfony/http-client-contracts: ^3.0
Requires (Dev)
- phpunit/phpunit: ^11.0
- symfony/config: ^6.4|^7.0|^8.0
- symfony/dependency-injection: ^6.4|^7.0|^8.0
- symfony/http-client: ^6.4|^7.0|^8.0
- symfony/http-kernel: ^6.4|^7.0|^8.0
Suggests
- omnilab/chebi: ChEBI: chemical entities by ChEBI id, InChIKey, CAS, KEGG or name, their structure, ontology (classes, roles) and cross-references
- omnilab/copernicus: Copernicus Data Space: Sentinel, Landsat and Copernicus service products from its STAC catalogue
- omnilab/open-meteo: Open-Meteo: hourly and daily weather series at a point, forecast and historical archive
- omnilab/pubchem: PubChem: compounds by CID, name, InChIKey, CAS or SMILES, their computed properties, synonyms, structure image and cross-references
- omnilab/wikidata: Wikidata: any item by Q-number, by a science base's identifier or by label, its labels in a language, its cross-references and a few properties with units
- symfony/config: In a Symfony application: the configuration of Omnilab\Bridge\Symfony\OmnilabBundle
- symfony/dependency-injection: In a Symfony application: the services of Omnilab\Bridge\Symfony\OmnilabBundle
- symfony/http-kernel: In a Symfony application: Omnilab\Bridge\Symfony\OmnilabBundle itself
Provides
None
Conflicts
None
Replaces
None
This package is auto-updated.
Last update: 2026-10-15 01:56:23 UTC
README
One contract for the public science-data APIs, whatever the domain - chemistry, biology, species, astronomy, the Earth and its weather: what science knows of a thing, what it measures at a place, what it has catalogued over an area. Publications are not here: they are glitchr/omnischolar's.
$caffeine = $registry->get('pubchem')->entity('caffeine'); $caffeine->property('molecular_weight'); // 194.19 g/mol, from pubchem $caffeine->identifier(Scheme::CHEBI); // 27732: a cross-reference the Collector can follow $caffeine = $collector->collect('pubchem', 'caffeine'); // then ChEBI and Wikidata, by its InChIKey and its CID $caffeine->properties('molecular_weight'); // 194.19 g/mol from pubchem, 194.194 g/mol from chebi $caffeine->property('melting_point'); // 234 °C, from wikidata [$temperature] = $registry->get('open-meteo')->series(new SeriesQuery(['temperature_2m'], new Point(48.5734, 7.7521), $tomorrow, $tomorrow)); $temperature->max(); // 20.4, in $temperature->unit: °C $scenes = $registry->get('copernicus')->products(new ProductQuery(['sentinel-2-l2a'], $strasbourg, new \DateTimeImmutable('-10 days'), maxCloudCover: 30)); $scenes->first()->preview; // the scene's thumbnail; its assets keep their hrefs
Every answer takes one of three shapes, not one per domain:
- an entity (
Model\Entity): a thing named and identified - a compound, a gene, a protein, a species, a star, an asteroid, an earthquake - its properties with their units (Property,Quantity), its synonyms, and its identifiers in other bases (Identifier,Scheme: PubChem CID, ChEBI, ChEMBL, InChIKey, CAS, UniProt, NCBI Gene and Taxonomy, Ensembl, PDB, GBIF, iNaturalist, SIMBAD, JPL SBDB, USGS events, Wikidata...); a time and a place for an event; - a series (
Model\Series): one variable at aPointor over aBBox, its points in time, its unit, its step and aggregation; - a product (
Model\Product, in the shape of a STAC item): a catalogued scene or file, its datetime, footprint, properties and assets.
This package holds the contract (Source\SourceInterface, Source\SourceFactory, Registry), the
models, the Collector that follows an entity's cross-references into the other sources and the
Merger that makes one entity of several, keeping the source of every property; and a bridge for
Symfony. It needs no framework: it requires nothing but symfony/http-client-contracts, each
source package symfony/http-client. Each source is a package of its own:
| Package | Source | Shape | Key |
|---|---|---|---|
omnilab/pubchem |
PubChem (NCBI): compounds by CID, name, InChIKey, CAS, SMILES; computed properties, synonyms, 2D structure, cross-references | entity | none (5 calls a second at most) |
omnilab/open-meteo |
Open-Meteo: hourly, 15-minute and daily weather at a point, forecast and archive (from 1940) | series | none for non-commercial use |
omnilab/copernicus |
Copernicus Data Space: the STAC catalogue of Sentinel, Landsat, MODIS, the DEM and the Copernicus services | product | none to search (a CDSE token to download) |
omnilab/chebi |
ChEBI (EMBL-EBI): chemical entities by ChEBI id, InChIKey, CAS, KEGG or name; masses, structure, ontology (classes, roles), cross-references | entity | none |
omnilab/wikidata |
Wikidata: any item by Q-number, by a science base's identifier (PubChem, ChEBI, CAS, UniProt, NCBI...) or by label; labels in a language, cross-references, a few properties with units | entity | none (a User-Agent that names the caller; 200 calls a minute) |
omnilab/chembl |
ChEMBL (EMBL-EBI): bioactive molecules by ChEMBL id, InChIKey or name; development phase, computed properties, mechanisms of action and their targets, cross-references from UniChem | entity | none |
omnilab/uniprot |
UniProtKB: proteins by accession or entry name; function, gene, organism, sequence, mass, cofactors, ligands; their genes in NCBI and Ensembl | entity | none |
omnilab/gbif |
GBIF's taxonomy (the Catalogue of Life eXtended Release since 2026): taxa by key, legacy Backbone number, NCBI id or scientific name; status, IUCN category, lineage; NCBI cross-reference | entity | none |
omnilab/ncbi |
NCBI Gene and Taxonomy (Datasets v2, E-utilities): genes by GeneID, taxa by id or name; location, summary, lineage; UniProt, Ensembl and GBIF ids | entity | none (3 calls a second; 10 with an API key) |
A source answers what it can - capabilities() says what - and throws NotSupportedException
for the rest, before any call. Unknown is null, an empty page or a series without points; a
service down or rate limited is an UnavailableException (RateLimitedException), which a caller
never caches as "nothing".
Documentation
- Installation and first calls
- Sources: what each can and cannot do
- Models and identifiers
- Following cross-references, merging
- Symfony
- The Docker harness
Plain PHP
composer require glitchr/omnilab omnilab/pubchem omnilab/open-meteo omnilab/copernicus omnilab/chebi omnilab/wikidata
use Omnilab\Chebi\ChebiSourceFactory; use Omnilab\Copernicus\CopernicusSourceFactory; use Omnilab\OpenMeteo\OpenMeteoSourceFactory; use Omnilab\PubChem\PubChemSourceFactory; use Omnilab\Registry; use Omnilab\Wikidata\WikidataSourceFactory; use Symfony\Component\HttpClient\HttpClient; $http = HttpClient::create(); // or the application's client; a MockHttpClient in a test $registry = new Registry([new PubChemSourceFactory($http), new ChebiSourceFactory($http), new WikidataSourceFactory($http), new OpenMeteoSourceFactory($http), new CopernicusSourceFactory($http)], [ 'pubchem' => ['factory' => 'pubchem', 'options' => ['mailto' => 'you@example.org']], 'chebi' => ['factory' => 'chebi', 'options' => ['mailto' => 'you@example.org']], 'wikidata' => ['factory' => 'wikidata', 'options' => ['mailto' => 'you@example.org', 'language' => 'fr']], 'open-meteo' => ['factory' => 'open-meteo'], 'copernicus' => ['factory' => 'copernicus'], ]);
No bundle, no container: a factory per source package, the registry built by hand. docs/installation.md opens on a whole script that runs as it is, against the real services.
Symfony
Omnilab\Bridge\Symfony\OmnilabBundle does that wiring in a Symfony application
(docs/symfony.md); its components (symfony/config,
symfony/dependency-injection, symfony/http-kernel) are not required by this package.
omnilab: sources: pubchem: { factory: pubchem, options: { mailto: '%env(OMNILAB_MAILTO)%' } } chebi: { factory: chebi, options: { mailto: '%env(OMNILAB_MAILTO)%' } } wikidata: { factory: wikidata, options: { mailto: '%env(OMNILAB_MAILTO)%', language: fr } } meteo: { factory: open-meteo } copernicus: { factory: copernicus }
public function __construct(SourceInterface $pubchem, SourceInterface $meteo, Collector $collector) {}
Docker: every source, for real
cd docker && cp .env.dist .env docker compose run --rm omnilab sources docker compose run --rm omnilab entity pubchem caffeine docker compose run --rm omnilab collect pubchem caffeine # then ChEBI and Wikidata, merged docker compose run --rm omnilab series open-meteo 48.5734,7.7521 temperature_2m --from tomorrow --to tomorrow docker compose run --rm omnilab products copernicus --point 48.5734,7.7521 --from=-10days --cloud 30 docker compose run --rm omnilab bare # plain PHP: no bundle, no container, and what PHP loaded docker compose run --rm omnilab test
License: MIT.