docs/start: tested Rust and Python CultCache first calls #3
3 changed files with 228 additions and 3 deletions
|
|
@ -18,10 +18,10 @@ this page.
|
|||
| --- | --- | --- | --- | --- | --- |
|
||||
| **C#** | [first call, tested](#c-first-call) | [package](../../packages/cultmath/README.md); snippet not yet | [package](../../src/GameCult.Geometry/README.md); snippet not yet | [package](../../src/GameCult.Networking); snippet not yet | [quickstart](../../src/GameCult.Mesh/docs/durable-node-quickstart.md); run by `scripts/verify-eve-getting-started.ps1` |
|
||||
| **TypeScript** | [first call, tested](#typescript-first-call) | not yet | not yet | [package](../../packages/cultnet-ts/README.md); snippet not yet | [package](../../packages/cultmesh-ts/README.md); snippet not yet |
|
||||
| **Rust** | [package](../../packages/cultcache-rs/README.md); snippet not yet | [native core](../../packages/cultmath/native/cultmath-core); snippet not yet | not yet | [package](../../packages/cultnet-rs/README.md), [examples](../../packages/cultnet-rs/examples); snippet not yet | [package](../../packages/cultmesh-rs); snippet not yet |
|
||||
| **Python** | [package](../../packages/cultcache-py/README.md); snippet not yet | not yet | not yet | [package](../../packages/cultnet-py/README.md); snippet not yet | [package](../../packages/cultmesh-py/README.md); snippet not yet |
|
||||
| **Rust** | [first call, tested](#rust-first-call) | [native core](../../packages/cultmath/native/cultmath-core); snippet not yet | not yet | [package](../../packages/cultnet-rs/README.md), [examples](../../packages/cultnet-rs/examples); snippet not yet | [package](../../packages/cultmesh-rs); snippet not yet |
|
||||
| **Python** | [first call, tested](#python-first-call) | not yet | not yet | [package](../../packages/cultnet-py/README.md); snippet not yet | [package](../../packages/cultmesh-py/README.md); snippet not yet |
|
||||
|
||||
The Rust and Python CultCache first calls are the next cut for this page. For the whole
|
||||
For the whole
|
||||
picture of what each piece is for, see the
|
||||
[package chooser in the top-level README](../../README.md#which-package-do-i-want).
|
||||
|
||||
|
|
@ -109,6 +109,108 @@ const byName = reopened.getByName(itemDocument, "Potion");
|
|||
The test imports from `../src/index`; an application imports `@gamecult/cultcache-ts`. More:
|
||||
`packages/cultcache-ts/README.md`.
|
||||
|
||||
## Rust first call
|
||||
|
||||
Same shape with `derive(DatabaseEntry)`: slots are `#[cultcache(key = N)]`, so the `.cc` payload is the
|
||||
slot-indexed array the C# runtime writes. The source is
|
||||
[`packages/cultcache-rs/tests/first_call.rs`](../../packages/cultcache-rs/tests/first_call.rs), run by
|
||||
`cargo test --test first_call` in `packages/cultcache-rs`; a second test in that file fails if the block
|
||||
below stops matching the file. The Rust runtime has no name lookup, so the read-back is by key. More:
|
||||
`packages/cultcache-rs/README.md`.
|
||||
|
||||
```rust
|
||||
#[derive(Clone, Debug, PartialEq, Eq, DatabaseEntry)]
|
||||
#[cultcache(type = "item")]
|
||||
struct Item {
|
||||
#[cultcache(key = 0)]
|
||||
name: String,
|
||||
#[cultcache(key = 1)]
|
||||
category: String,
|
||||
#[cultcache(key = 2, default)]
|
||||
value: u32,
|
||||
}
|
||||
|
||||
fn first_call() -> anyhow::Result<()> {
|
||||
let dir = tempfile::tempdir()?;
|
||||
let path = dir.path().join("first-call.cc");
|
||||
|
||||
let mut cache = CultCache::new();
|
||||
cache.register_entry_type::<Item>()?;
|
||||
cache.add_generic_backing_store(SingleFileMessagePackBackingStore::new(&path));
|
||||
cache.pull_all_backing_stores()?;
|
||||
cache.put("item:potion", &Item {
|
||||
name: "Potion".to_string(),
|
||||
category: "Consumable".to_string(),
|
||||
value: 50,
|
||||
})?; // written to the .cc file before this returns
|
||||
|
||||
let mut reopened = CultCache::new();
|
||||
reopened.register_entry_type::<Item>()?;
|
||||
reopened.add_generic_backing_store(SingleFileMessagePackBackingStore::new(&path));
|
||||
reopened.pull_all_backing_stores()?; // read it back
|
||||
let potion = reopened.get_required::<Item>("item:potion")?;
|
||||
assert_eq!(potion.name, "Potion");
|
||||
assert_eq!(potion.value, 50);
|
||||
Ok(())
|
||||
}
|
||||
```
|
||||
|
||||
## Python first call
|
||||
|
||||
A document is a definition with an encoder and decoder; `name=` and `indexes=` register the name lookup
|
||||
and indexes. The source is
|
||||
[`packages/cultcache-py/tests/test_first_call.py`](../../packages/cultcache-py/tests/test_first_call.py),
|
||||
run by `python -m unittest tests.test_first_call` in `packages/cultcache-py` with `src` on `PYTHONPATH`
|
||||
and `msgpack` installed (the store needs it). A second test fails if the block below stops matching the
|
||||
file. More: `packages/cultcache-py/README.md`.
|
||||
|
||||
```python
|
||||
import tempfile
|
||||
from dataclasses import asdict, dataclass
|
||||
from pathlib import Path
|
||||
|
||||
from cultcache_py import CultCache, SingleFileMessagePackBackingStore, define_document_type
|
||||
|
||||
|
||||
@dataclass
|
||||
class Item:
|
||||
name: str
|
||||
category: str
|
||||
value: int
|
||||
|
||||
|
||||
item_doc = define_document_type(
|
||||
"item",
|
||||
encode=lambda item: asdict(item),
|
||||
decode=lambda raw: Item(**raw),
|
||||
name="name",
|
||||
indexes={"category": "category"},
|
||||
)
|
||||
|
||||
|
||||
def first_call() -> Item:
|
||||
path = Path(tempfile.mkdtemp()) / "first-call.cc"
|
||||
|
||||
cache = (
|
||||
CultCache.builder()
|
||||
.register_document_type(item_doc)
|
||||
.add_generic_store(SingleFileMessagePackBackingStore(path))
|
||||
.build()
|
||||
)
|
||||
cache.pull_all_backing_stores()
|
||||
cache.put(item_doc, "item:potion", Item("Potion", "Consumable", 50)) # written to the .cc file
|
||||
|
||||
reopened = (
|
||||
CultCache.builder()
|
||||
.register_document_type(item_doc)
|
||||
.add_generic_store(SingleFileMessagePackBackingStore(path))
|
||||
.build()
|
||||
)
|
||||
reopened.pull_all_backing_stores() # read it back
|
||||
assert reopened.get_by_name(item_doc, "Potion") == reopened.get_required(item_doc, "item:potion")
|
||||
return reopened.get_required(item_doc, "item:potion")
|
||||
```
|
||||
|
||||
## Where next
|
||||
|
||||
- Smallest durable mesh path: `src/GameCult.Mesh/docs/durable-node-quickstart.md`.
|
||||
|
|
|
|||
67
packages/cultcache-py/tests/test_first_call.py
Normal file
67
packages/cultcache-py/tests/test_first_call.py
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
"""The Python CultCache first call from docs/start/README.md. The docs show the region between the
|
||||
`snippet` markers verbatim, and test_docs_show_the_tested_snippet fails when they differ."""
|
||||
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
# snippet:begin
|
||||
import tempfile
|
||||
from dataclasses import asdict, dataclass
|
||||
from pathlib import Path
|
||||
|
||||
from cultcache_py import CultCache, SingleFileMessagePackBackingStore, define_document_type
|
||||
|
||||
|
||||
@dataclass
|
||||
class Item:
|
||||
name: str
|
||||
category: str
|
||||
value: int
|
||||
|
||||
|
||||
item_doc = define_document_type(
|
||||
"item",
|
||||
encode=lambda item: asdict(item),
|
||||
decode=lambda raw: Item(**raw),
|
||||
name="name",
|
||||
indexes={"category": "category"},
|
||||
)
|
||||
|
||||
|
||||
def first_call() -> Item:
|
||||
path = Path(tempfile.mkdtemp()) / "first-call.cc"
|
||||
|
||||
cache = (
|
||||
CultCache.builder()
|
||||
.register_document_type(item_doc)
|
||||
.add_generic_store(SingleFileMessagePackBackingStore(path))
|
||||
.build()
|
||||
)
|
||||
cache.pull_all_backing_stores()
|
||||
cache.put(item_doc, "item:potion", Item("Potion", "Consumable", 50)) # written to the .cc file
|
||||
|
||||
reopened = (
|
||||
CultCache.builder()
|
||||
.register_document_type(item_doc)
|
||||
.add_generic_store(SingleFileMessagePackBackingStore(path))
|
||||
.build()
|
||||
)
|
||||
reopened.pull_all_backing_stores() # read it back
|
||||
assert reopened.get_by_name(item_doc, "Potion") == reopened.get_required(item_doc, "item:potion")
|
||||
return reopened.get_required(item_doc, "item:potion")
|
||||
# snippet:end
|
||||
|
||||
|
||||
class FirstCallTests(unittest.TestCase):
|
||||
def test_first_call_round_trips_through_a_second_cache(self) -> None:
|
||||
self.assertEqual(first_call(), Item("Potion", "Consumable", 50))
|
||||
|
||||
def test_docs_show_the_tested_snippet(self) -> None:
|
||||
source = Path(__file__).read_text(encoding="utf-8")
|
||||
snippet = source.split("# snippet:begin\n", 1)[1].split("# snippet:end\n", 1)[0]
|
||||
docs = (Path(__file__).parents[3] / "docs" / "start" / "README.md").read_text(encoding="utf-8")
|
||||
self.assertIn("```python\n" + snippet + "```", docs)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
56
packages/cultcache-rs/tests/first_call.rs
Normal file
56
packages/cultcache-rs/tests/first_call.rs
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
//! The Rust CultCache first call from docs/start/README.md. The docs show the region between the
|
||||
//! `snippet` markers verbatim, and `docs_show_the_tested_snippet` fails when they differ.
|
||||
|
||||
use cultcache_rs::{CultCache, DatabaseEntry, SingleFileMessagePackBackingStore};
|
||||
|
||||
// snippet:begin
|
||||
#[derive(Clone, Debug, PartialEq, Eq, DatabaseEntry)]
|
||||
#[cultcache(type = "item")]
|
||||
struct Item {
|
||||
#[cultcache(key = 0)]
|
||||
name: String,
|
||||
#[cultcache(key = 1)]
|
||||
category: String,
|
||||
#[cultcache(key = 2, default)]
|
||||
value: u32,
|
||||
}
|
||||
|
||||
fn first_call() -> anyhow::Result<()> {
|
||||
let dir = tempfile::tempdir()?;
|
||||
let path = dir.path().join("first-call.cc");
|
||||
|
||||
let mut cache = CultCache::new();
|
||||
cache.register_entry_type::<Item>()?;
|
||||
cache.add_generic_backing_store(SingleFileMessagePackBackingStore::new(&path));
|
||||
cache.pull_all_backing_stores()?;
|
||||
cache.put("item:potion", &Item {
|
||||
name: "Potion".to_string(),
|
||||
category: "Consumable".to_string(),
|
||||
value: 50,
|
||||
})?; // written to the .cc file before this returns
|
||||
|
||||
let mut reopened = CultCache::new();
|
||||
reopened.register_entry_type::<Item>()?;
|
||||
reopened.add_generic_backing_store(SingleFileMessagePackBackingStore::new(&path));
|
||||
reopened.pull_all_backing_stores()?; // read it back
|
||||
let potion = reopened.get_required::<Item>("item:potion")?;
|
||||
assert_eq!(potion.name, "Potion");
|
||||
assert_eq!(potion.value, 50);
|
||||
Ok(())
|
||||
}
|
||||
// snippet:end
|
||||
|
||||
#[test]
|
||||
fn first_call_round_trips_through_a_second_cache() {
|
||||
first_call().unwrap();
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn docs_show_the_tested_snippet() {
|
||||
let source = std::fs::read_to_string(concat!(env!("CARGO_MANIFEST_DIR"), "/tests/first_call.rs")).unwrap();
|
||||
let begin = source.find("// snippet:begin\n").unwrap() + "// snippet:begin\n".len();
|
||||
let end = source.find("// snippet:end\n").unwrap();
|
||||
let docs = std::fs::read_to_string(concat!(env!("CARGO_MANIFEST_DIR"), "/../../docs/start/README.md")).unwrap();
|
||||
let block = format!("```rust\n{}```", &source[begin..end]);
|
||||
assert!(docs.contains(&block), "docs/start/README.md no longer shows tests/first_call.rs verbatim");
|
||||
}
|
||||
Loading…
Reference in a new issue